/*
 * \brief  Service that provides files of a TAR archive as ROM sessions
 * \author Martin stein
 * \author Norman Feske
 * \date   2010-04-21
 */

/*
 * Copyright (C) 2010-2012 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

/* Genode includes */
#include <rom_session/rom_session.h>
#include <root/component.h>
#include <cap_session/connection.h>
#include <util/arg_string.h>
#include <base/rpc_server.h>
#include <base/sleep.h>
#include <base/env.h>
#include <base/printf.h>
#include <os/config.h>


/**
 * A 'Rom_session_component' exports a single file of the tar archive
 */
class Rom_session_component : public Genode::Rpc_object<Genode::Rom_session>
{
	private:

		const char *_tar_addr, *_filename, *_file_addr;
		Genode::size_t _file_size, _tar_size;
		Genode::Ram_dataspace_capability _file_ds;

		enum {
			/* length of on data block in tar */
			_BLOCK_LEN = 512,

			/* length of the header field "file-size" in tar */
			_FIELD_SIZE_LEN = 124
		};

		/**
		 * Copy file content into dataspace
		 *
		 * \param dst  destination dataspace
		 */
		void _copy_content_to_dataspace(Genode::Dataspace_capability dst)
		{
			using namespace Genode;

			/* map dataspace locally */
			char *dst_addr = env()->rm_session()->attach(dst);
			Dataspace_client dst_client(dst);

			/* copy content */
			size_t dst_ds_size   = Dataspace_client(dst).size();
			size_t bytes_to_copy = min(_file_size, dst_ds_size);
			memcpy(dst_addr, _file_addr, bytes_to_copy);

			/* unmap dataspace */
			env()->rm_session()->detach(dst_addr);
		}

		/**
		 * Initialize dataspace containing the content of the archived file
		 */
		Genode::Ram_dataspace_capability _init_file_ds()
		{
			bool file_found = false;

			/* measure size of archive in blocks */
			unsigned block_id = 0, block_cnt = _tar_size/_BLOCK_LEN;

			/* scan metablocks of archive */
			while (block_id < block_cnt) {

				unsigned long file_size = 0;
				Genode::ascii_to(_tar_addr + block_id*_BLOCK_LEN + _FIELD_SIZE_LEN,
				                 &file_size, 8);

				/* get infos about current file */
				if (Genode::strcmp(_filename, _tar_addr + block_id*_BLOCK_LEN) == 0) {
					_file_size = file_size;
					_file_addr = _tar_addr + (block_id+1) * _BLOCK_LEN;
					file_found = true;
					break;
				}

				/* some datablocks */       /* one metablock */
				block_id = block_id + (file_size / _BLOCK_LEN) + 1;

				/* round up */
				if (file_size % _BLOCK_LEN != 0) block_id++;

				/* check for end of tar archive */
				if (block_id*_BLOCK_LEN >= _tar_size)
					break;

				/* lookout for empty eof-blocks */
				if (*(_tar_addr + (block_id*_BLOCK_LEN)) == 0x00)
					if (*(_tar_addr + (block_id*_BLOCK_LEN + 1)) == 0x00)
						break;
			}

			if (!file_found) {
				PERR("couldn't find file '%s', empty result", _filename);
				return Genode::Ram_dataspace_capability();
			}

			/* try to allocate memory for file */
			Genode::Ram_dataspace_capability file_ds;
			try {
				file_ds = Genode::env()->ram_session()->alloc(_file_size);

				/* get content of file copied into dataspace and return */
				_copy_content_to_dataspace(file_ds);
			} catch (...) {
				PERR("couldn't allocate memory for file, empty result\n");
				return file_ds;
			}

			return file_ds;
		}

	public:

		/**
		 * Constructor scans and seeks to file
		 *
		 * \param  tar_addr  local address to tar archive
		 * \param  tar_size  size of tar archive in bytes
		 * \param  filename  name of the requested file
		 */
		Rom_session_component(const char *tar_addr, unsigned tar_size,
		                      const char *filename)
		:
			_tar_addr(tar_addr), _filename(filename), _file_addr(0), _file_size(0),
			_tar_size(tar_size),
			_file_ds(_init_file_ds())
		{ }

		/**
		 * Destructor
		 */
		~Rom_session_component() { Genode::env()->ram_session()->free(_file_ds); }

		/**
		 * Return dataspace with content of file
		 */
		Genode::Rom_dataspace_capability dataspace()
		{
			Genode::Dataspace_capability ds = _file_ds;
			return Genode::static_cap_cast<Genode::Rom_dataspace>(ds);
		}

		void sigh(Genode::Signal_context_capability) { }
};


class Rom_root : public Genode::Root_component<Rom_session_component>
{
	private:

		char    *_tar_addr;
		unsigned _tar_size;

		Rom_session_component *_create_session(const char *args)
		{
			enum { FILENAME_MAX_LEN = 128 };
			char filename[FILENAME_MAX_LEN];
			Genode::Arg_string::find_arg(args, "filename").string(filename, sizeof(filename), "");

			PINF("connection for file '%s' requested\n", filename);

			/* create new session for the requested file */
			return new (md_alloc()) Rom_session_component(_tar_addr, _tar_size, filename);
		}

	public:

		/**
		 * Constructor
		 *
		 * \param  entrypoint  entrypoint to be used for ROM sessions
		 * \param  md_alloc    meta-data allocator used for ROM sessions
		 * \param  tar_base    local address of tar archive
		 * \param  tar_size    size of tar archive in bytes
		 */
		Rom_root(Genode::Rpc_entrypoint *entrypoint,
		         Genode::Allocator      *md_alloc,
		         char *tar_addr, Genode::size_t tar_size)
		:
			Genode::Root_component<Rom_session_component>(entrypoint, md_alloc),
			_tar_addr(tar_addr), _tar_size(tar_size)
		{ }
};


using namespace Genode;

int main(void)
{
	/* read name of tar archive from config */
	enum { TAR_FILENAME_MAX_LEN = 64 };
	static char tar_filename[TAR_FILENAME_MAX_LEN];
	try {
		Xml_node archive_node =
			config()->xml_node().sub_node("archive");
		archive_node.attribute("name").value(tar_filename, sizeof(tar_filename));
	} catch (...) {
		PERR("Could not read 'filename' argument from config");
		return -1;
	}

	/* obtain dataspace of tar archive from ROM service */
	static char  *tar_base = 0;
	static size_t tar_size = 0;
	try {
		static Rom_connection tar_rom(tar_filename);
		tar_base = env()->rm_session()->attach(tar_rom.dataspace());
		tar_size = Dataspace_client(tar_rom.dataspace()).size();
	} catch (...) {
		PERR("Could not obtain tar archive from ROM service");
		return -2;
	}

	PINF("using tar archive '%s' with size %zd", tar_filename, tar_size);

	/* connection to capability service needed to create capabilities */
	static Cap_connection cap;

	/* creation of the entrypoint and the root interface */
	static Sliced_heap sliced_heap(env()->ram_session(),
	                               env()->rm_session());

	enum { STACK_SIZE = 8*1024 };
	static Rpc_entrypoint ep(&cap, STACK_SIZE, "tar_rom_ep");
	static Rom_root rom_root(&ep, &sliced_heap, tar_base, tar_size);

	/* announce server*/
	env()->parent()->announce(ep.manage(&rom_root));

	/* wait for activation through client */
	sleep_forever();
	return 0;

}
