From 3084c6f5008778582ff7e266fe8e45b153dcbd0a Mon Sep 17 00:00:00 2001 From: Christian Prochaska Date: Tue, 21 Aug 2012 17:22:21 +0200 Subject: [PATCH] TAR file system service This patch implements a service which provides the contents of a tar archive via the 'File_system::Session' interface. Configuration: Fixes #333. --- libports/run/libc_fs_tar_fs.run | 102 ++++ libports/src/test/libc_fs_tar_fs/main.cc | 108 ++++ libports/src/test/libc_fs_tar_fs/target.mk | 3 + os/src/server/tar_fs/README | 11 + os/src/server/tar_fs/directory.h | 87 +++ os/src/server/tar_fs/file.h | 64 +++ os/src/server/tar_fs/lookup.h | 124 ++++ os/src/server/tar_fs/main.cc | 592 ++++++++++++++++++++ os/src/server/tar_fs/node.h | 56 ++ os/src/server/tar_fs/node_handle_registry.h | 137 +++++ os/src/server/tar_fs/record.h | 76 +++ os/src/server/tar_fs/target.mk | 4 + os/src/server/tar_fs/util.h | 107 ++++ 13 files changed, 1471 insertions(+) create mode 100644 libports/run/libc_fs_tar_fs.run create mode 100644 libports/src/test/libc_fs_tar_fs/main.cc create mode 100644 libports/src/test/libc_fs_tar_fs/target.mk create mode 100644 os/src/server/tar_fs/README create mode 100644 os/src/server/tar_fs/directory.h create mode 100644 os/src/server/tar_fs/file.h create mode 100644 os/src/server/tar_fs/lookup.h create mode 100644 os/src/server/tar_fs/main.cc create mode 100644 os/src/server/tar_fs/node.h create mode 100644 os/src/server/tar_fs/node_handle_registry.h create mode 100644 os/src/server/tar_fs/record.h create mode 100644 os/src/server/tar_fs/target.mk create mode 100644 os/src/server/tar_fs/util.h diff --git a/libports/run/libc_fs_tar_fs.run b/libports/run/libc_fs_tar_fs.run new file mode 100644 index 000000000..5b7498190 --- /dev/null +++ b/libports/run/libc_fs_tar_fs.run @@ -0,0 +1,102 @@ +# +# \brief Test for using the libc_fs plugin with the TAR file system +# \author Christian Prochaska +# \date 2012-08-20 +# + +# +# Build +# + +build { + core init + drivers/timer + server/tar_fs + test/libc_fs_tar_fs +} + +create_boot_directory + +# +# Generate config +# + +set config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +append config { + +} + +install_config $config + +# +# Create tar archive +# + +exec mkdir -p bin/libc_fs_tar_fs/testdir/testdir +exec echo -n "a single line of text" > bin/libc_fs_tar_fs/testdir/testdir/test.tst +exec tar cfv bin/libc_fs_tar_fs.tar -h -C bin/libc_fs_tar_fs . + +# +# Boot modules +# + +# generic modules +set boot_modules { + core init timer tar_fs + ld.lib.so libc.lib.so libc_log.lib.so libc_fs.lib.so + test-libc_fs_tar_fs libc_fs_tar_fs.tar +} + +build_boot_image $boot_modules + +# +# Execute test case +# + +# +# Qemu +# +append qemu_args " -m 128 -nographic " + +run_genode_until {.*child exited with exit value 0.*} 60 + +#exec rm -rf bin/libc_fs_tar_fs +#exec rm -rf bin/libc_fs_tar_fs.tar + +puts "\ntest succeeded\n" + +# vi: set ft=tcl : diff --git a/libports/src/test/libc_fs_tar_fs/main.cc b/libports/src/test/libc_fs_tar_fs/main.cc new file mode 100644 index 000000000..04716a467 --- /dev/null +++ b/libports/src/test/libc_fs_tar_fs/main.cc @@ -0,0 +1,108 @@ +/* + * \brief libc plugin read-only test + * \author Christian Prochaska + * \author Norman Feske + * \date 2011-08-20 + */ + +/* + * Copyright (C) 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 + +/* libc includes */ +#include +#include +#include +#include +#include +#include +#include +#include + + +#define CALL_AND_CHECK(ret, operation, condition, info_string, ...) \ + printf("calling " #operation " " info_string "\n", ##__VA_ARGS__); \ + ret = operation; \ + if (condition) { \ + printf(#operation " succeeded\n"); \ + } else { \ + printf(#operation " failed, " #ret "=%ld, errno=%d\n", (long)ret, errno); \ + return -1; \ + } + + +int main(int argc, char *argv[]) +{ + int ret, fd; + ssize_t count; + + char const *dir_name = "/testdir"; + char const *file_name = "test.tst"; + char const *pattern = "a single line of text"; + + size_t pattern_size = strlen(pattern); + + unsigned int iterations = 1; + + try { + Genode::config()->xml_node().sub_node("iterations").attribute("value").value(&iterations); + } catch(...) { } + + for (unsigned int i = 0; i < iterations; i++) { + + /* change to new directory */ + CALL_AND_CHECK(ret, chdir(dir_name), ret == 0, "dir_name=%s", dir_name); + + /* query file status of file */ + struct stat stat_buf; + CALL_AND_CHECK(ret, stat(file_name, &stat_buf), ret == 0, "file_name=%s", file_name); + printf("file size: %u bytes\n", (unsigned)stat_buf.st_size); + struct tm *file_time = gmtime(&stat_buf.st_mtime); + printf("last modified: %04u-%02u-%02u %02u:%02u:%02u\n", + file_time->tm_year, file_time->tm_mon, file_time->tm_mday, + file_time->tm_hour, file_time->tm_min, file_time->tm_sec); + + /* read and verify file content */ + CALL_AND_CHECK(fd, open(file_name, O_RDONLY), fd >= 0, "file_name=%s", file_name); + static char buf[512]; + CALL_AND_CHECK(count, read(fd, buf, sizeof(buf)), (size_t)count == pattern_size, ""); + CALL_AND_CHECK(ret, close(fd), ret == 0, ""); + printf("content of file: \"%s\"\n", buf); + if (strcmp(buf, pattern) != 0) { + printf("unexpected content of file\n"); + return -1; + } else { + printf("file content is correct\n"); + } + + /* read directory entries */ + DIR *dir; + CALL_AND_CHECK(dir, opendir(dir_name), dir, "dir_name=\"%s\"", dir_name); + printf("calling readdir()\n"); + for (;;) { + struct dirent *dirent = readdir(dir); + if (dirent) { + if (dirent->d_type == DT_DIR) + printf("found directory %s\n", dirent->d_name); + else + printf("found file %s\n", dirent->d_name); + } else { + printf("no (more) direntries found\n"); + break; + } + } + + if (i < (iterations - 1)) + sleep(2); + } + + printf("test finished\n"); + + return 0; +} diff --git a/libports/src/test/libc_fs_tar_fs/target.mk b/libports/src/test/libc_fs_tar_fs/target.mk new file mode 100644 index 000000000..3f435dc63 --- /dev/null +++ b/libports/src/test/libc_fs_tar_fs/target.mk @@ -0,0 +1,3 @@ +TARGET = test-libc_fs_tar_fs +LIBS = cxx env libc libc_log libc_fs +SRC_CC = main.cc diff --git a/os/src/server/tar_fs/README b/os/src/server/tar_fs/README new file mode 100644 index 000000000..956117bbe --- /dev/null +++ b/os/src/server/tar_fs/README @@ -0,0 +1,11 @@ +This directory contains a service that provides the contents of a TAR archive +via the 'File_system::Session' interface. + +Configuration: + +! +! +! +! + +For an example, please refer to the 'libports/run/libc_fs_tar_fs.run' script. diff --git a/os/src/server/tar_fs/directory.h b/os/src/server/tar_fs/directory.h new file mode 100644 index 000000000..3cecf78d3 --- /dev/null +++ b/os/src/server/tar_fs/directory.h @@ -0,0 +1,87 @@ +/* + * \brief TAR file-system directory node + * \author Christian Prochaska + * \author Norman Feske + * \date 2012-08-20 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _DIRECTORY_H_ +#define _DIRECTORY_H_ + +/* local includes */ +#include +#include + + +namespace File_system { + + class Directory : public Node + { + public: + + Directory(Record *record) : Node(record) { } + + size_t read(char *dst, size_t len, seek_off_t seek_offset) + { + bool verbose = false; + + if (verbose) + PDBG("len = %zu, seek_offset = %llu", len, seek_offset); + + if (len < sizeof(Directory_entry)) { + PERR("read buffer too small for directory entry"); + return 0; + } + + if (seek_offset % sizeof(Directory_entry)) { + PERR("seek offset not alighed to sizeof(Directory_entry)"); + return 0; + } + + int64_t index = seek_offset / sizeof(Directory_entry); + + Lookup_member_of_path lookup_criterion(_record->name(), index); + Record *record = _lookup(&lookup_criterion); + if (!record) + return 0; + + Absolute_path absolute_path(record->name()); + absolute_path.keep_only_last_element(); + absolute_path.remove_trailing('/'); + + Directory_entry *e = (Directory_entry *)(dst); + + strncpy(e->name, absolute_path.base(), sizeof(e->name)); + + switch (record->type()) { + case Record::TYPE_DIR: e->type = Directory_entry::TYPE_DIRECTORY; break; + case Record::TYPE_FILE: e->type = Directory_entry::TYPE_FILE; break; + case Record::TYPE_SYMLINK: e->type = Directory_entry::TYPE_SYMLINK; break; + default: + if (verbose) + PDBG("unhandled record type %d", record->type()); + } + + if (verbose) + PDBG("found dir entry: %s", e->name); + + return sizeof(Directory_entry); + } + + size_t write(char const *src, size_t len, seek_off_t) + { + /* writing to directory nodes is not supported */ + return 0; + } + + }; +} + +#endif /* _DIRECTORY_H_ */ diff --git a/os/src/server/tar_fs/file.h b/os/src/server/tar_fs/file.h new file mode 100644 index 000000000..e488e7911 --- /dev/null +++ b/os/src/server/tar_fs/file.h @@ -0,0 +1,64 @@ +/* + * \brief TAR file-system file node + * \author Christian Prochaska + * \author Norman Feske + * \date 2012-08-20 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _FILE_H_ +#define _FILE_H_ + +/* local includes */ +#include + + +namespace File_system { + + class File : public Node + { + public: + + File(Record *record) : Node(record) { } + + size_t read(char *dst, size_t len, seek_off_t seek_offset) + { + bool verbose = false; + + if (verbose) + PDBG("len = %zu, seek_offset = %llu", len, seek_offset); + + size_t const record_size = _record->size(); + + size_t const record_bytes_left = record_size >= seek_offset + ? record_size - seek_offset : 0; + + size_t const count = min(record_bytes_left, len); + + char const *data = (char *)_record->data() + seek_offset; + + memcpy(dst, data, count); + + return count; + } + + size_t write(char const *src, size_t len, seek_off_t seek_offset) + { + bool verbose = false; + + if (verbose) + PDBG("len = %zu, seek_offset = %llu", len, seek_offset); + + return -1; + } + + }; +} + +#endif /* _FILE_H_ */ diff --git a/os/src/server/tar_fs/lookup.h b/os/src/server/tar_fs/lookup.h new file mode 100644 index 000000000..71f3698e7 --- /dev/null +++ b/os/src/server/tar_fs/lookup.h @@ -0,0 +1,124 @@ +/* + * \brief TAR record lookup function + * \author Norman Feske + * \author Christian Prochaska + * \date 2012-08-20 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _LOOKUP_H_ +#define _LOOKUP_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include + +namespace File_system { + + extern char *_tar_base; + extern size_t _tar_size; + + typedef Genode::Path Absolute_path; + + struct Lookup_criterion { virtual bool match(char const *path) = 0; }; + + struct Lookup_exact : public Lookup_criterion + { + Absolute_path _match_path; + + Lookup_exact(char const *match_path) + : _match_path(match_path) + { + _match_path.remove_trailing('/'); + } + + bool match(char const *path) + { + Absolute_path test_path(path); + test_path.remove_trailing('/'); + return _match_path.equals(test_path); + } + }; + + + /** + * Lookup the Nth record in the specified path + */ + struct Lookup_member_of_path : public Lookup_criterion + { + Absolute_path _dir_path; + int const index; + int cnt; + + Lookup_member_of_path(char const *dir_path, int index) + : _dir_path(dir_path), index(index), cnt(0) + { + _dir_path.remove_trailing('/'); + } + + bool match(char const *path) + { + Absolute_path test_path(path); + + if (!test_path.strip_prefix(_dir_path.base())) + return false; + + if (!test_path.has_single_element()) + return false; + + cnt++; + + /* match only if index is reached */ + if (cnt - 1 != index) + return false; + + return true; + } + }; + + Record *_lookup(Lookup_criterion *criterion) + { + /* measure size of archive in blocks */ + unsigned block_id = 0, block_cnt = _tar_size/Record::BLOCK_LEN; + + /* scan metablocks of archive */ + while (block_id < block_cnt) { + + Record *record = (Record *)(_tar_base + block_id*Record::BLOCK_LEN); + + /* get infos about current file */ + if (criterion->match(record->name())) + return record; + + size_t file_size = record->size(); + + /* some datablocks */ /* one metablock */ + block_id = block_id + (file_size / Record::BLOCK_LEN) + 1; + + /* round up */ + if (file_size % Record::BLOCK_LEN != 0) block_id++; + + /* check for end of tar archive */ + if (block_id*Record::BLOCK_LEN >= _tar_size) + break; + + /* lookout for empty eof-blocks */ + if (*(_tar_base + (block_id*Record::BLOCK_LEN)) == 0x00) + if (*(_tar_base + (block_id*Record::BLOCK_LEN + 1)) == 0x00) + break; + } + + return 0; + } + +} + +#endif /* _LOOKUP_H_ */ diff --git a/os/src/server/tar_fs/main.cc b/os/src/server/tar_fs/main.cc new file mode 100644 index 000000000..dbe5d26a3 --- /dev/null +++ b/os/src/server/tar_fs/main.cc @@ -0,0 +1,592 @@ +/* + * \brief TAR file system + * \author Christian Prochaska + * \author Norman Feske + * \date 2012-08-20 + */ + +/* + * Copyright (C) 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 +#include +#include +#include +#include +#include + +/* local includes */ +#include +#include +#include +#include +#include + + +using namespace Genode; + + +static bool const verbose = false; +#define PDBGV(...) if (verbose) PDBG(__VA_ARGS__) + + +/************************************* + ** Helpers for dispatching signals ** + *************************************/ + +namespace Genode { + + struct Signal_dispatcher_base : Signal_context + { + virtual void dispatch(int num) = 0; + }; + + + template + class Signal_dispatcher : private Signal_dispatcher_base, + public Signal_context_capability + { + private: + + T &obj; + void (T::*member) (int); + Signal_receiver &sig_rec; + + public: + + /** + * Constructor + * + * \param sig_rec signal receiver to associate the signal + * handler with + * \param obj,member object and member function to call when + * the signal occurs + */ + Signal_dispatcher(Signal_receiver &sig_rec, + T &obj, void (T::*member)(int)) + : + Signal_context_capability(sig_rec.manage(this)), + obj(obj), member(member), + sig_rec(sig_rec) + { } + + ~Signal_dispatcher() { sig_rec.dissolve(this); } + + void dispatch(int num) { (obj.*member)(num); } + }; +} + + +/************************* + ** File-system service ** + *************************/ + +namespace File_system { + + char *_tar_base; + size_t _tar_size; + + class Session_component : public Session_rpc_object + { + private: + + Directory &_root; + Node_handle_registry _handle_registry; + + Signal_dispatcher _process_packet_dispatcher; + + + /****************************** + ** Packet-stream processing ** + ******************************/ + + /** + * Perform packet operation + * + * \return true on success, false on failure + */ + void _process_packet_op(Packet_descriptor &packet, Node &node) + { + void * const content = tx_sink()->packet_content(packet); + size_t const length = packet.length(); + seek_off_t const offset = packet.position(); + + if (!content || (packet.length() > packet.size())) { + packet.succeeded(false); + return; + } + + /* resulting length */ + size_t res_length = 0; + + switch (packet.operation()) { + + case Packet_descriptor::READ: + PDBGV("READ"); + res_length = node.read((char *)content, length, offset); + break; + + case Packet_descriptor::WRITE: + PDBGV("WRITE"); + res_length = node.write((char const *)content, length, offset); + break; + } + + packet.length(res_length); + packet.succeeded(res_length > 0); + } + + void _process_packet() + { + Packet_descriptor packet = tx_sink()->get_packet(); + + /* assume failure by default */ + packet.succeeded(false); + + try { + Node *node = _handle_registry.lookup(packet.handle()); + _process_packet_op(packet, *node); + } + catch (Invalid_handle) { PERR("Invalid_handle"); } + catch (Size_limit_reached) { PERR("Size_limit_reached"); } + + /* + * The 'acknowledge_packet' function cannot block because we + * checked for 'ready_to_ack' in '_process_packets'. + */ + tx_sink()->acknowledge_packet(packet); + } + + /** + * Called by signal dispatcher, executed in the context of the main + * thread (not serialized with the RPC functions) + */ + void _process_packets(int) + { + while (tx_sink()->packet_avail()) { + + /* + * Make sure that the '_process_packet' function does not + * block. + * + * If the acknowledgement queue is full, we defer packet + * processing until the client processed pending + * acknowledgements and thereby emitted a ready-to-ack + * signal. Otherwise, the call of 'acknowledge_packet()' + * in '_process_packet' would infinitely block the context + * of the main thread. The main thread is however needed + * for receiving any subsequent 'ready-to-ack' signals. + */ + if (!tx_sink()->ready_to_ack()) + return; + + _process_packet(); + } + } + + /** + * Check if string represents a valid path (most start with '/') + */ + static void _assert_valid_path(char const *path) + { + if (!valid_path(path)) { + PWRN("malformed path '%s'", path); + throw Lookup_failed(); + } + } + + public: + + /** + * Constructor + */ + Session_component(size_t tx_buf_size, Rpc_entrypoint &ep, + Signal_receiver &sig_rec, + Directory &root) + : + Session_rpc_object(env()->ram_session()->alloc(tx_buf_size), ep), + _root(root), + _process_packet_dispatcher(sig_rec, *this, + &Session_component::_process_packets) + { + /* + * Register '_process_packets' dispatch function as signal + * handler for packet-avail and ready-to-ack signals. + */ + _tx.sigh_packet_avail(_process_packet_dispatcher); + _tx.sigh_ready_to_ack(_process_packet_dispatcher); + } + + /** + * Destructor + */ + ~Session_component() + { + Dataspace_capability ds = tx_sink()->dataspace(); + env()->ram_session()->free(static_cap_cast(ds)); + } + + + /*************************** + ** File_system interface ** + ***************************/ + + File_handle file(Dir_handle dir_handle, Name const &name, + Mode mode, bool create) + { + PDBGV("_root = %s, dir_name = %s, name = %s, create = %d", + _root.record()->name(), + _handle_registry.lookup(dir_handle)->record()->name(), + name.string(), + create); + + if (!valid_filename(name.string())) + throw Lookup_failed(); + + if (create) + throw Permission_denied(); + + Directory *dir = _handle_registry.lookup(dir_handle); + + Absolute_path abs_path(dir->record()->name()); + try { + abs_path.append("/"); + abs_path.append(name.base()); + } catch (Path_base::Path_too_long) { + throw Name_too_long(); + } + + PDBGV("abs_path = %s", abs_path.base()); + + Lookup_exact lookup_criterion(abs_path.base()); + Record *record = _lookup(&lookup_criterion); + + if (!record) { + PERR("Could not find record for %s", abs_path.base()); + throw Lookup_failed(); + } + + if (record->type() != Record::TYPE_FILE) + throw Lookup_failed(); + + File *file_node = new (env()->heap()) File(record); + return _handle_registry.alloc(file_node); + } + + Symlink_handle symlink(Dir_handle, Name const &name, bool create) + { + /* not supported */ + return Symlink_handle(-1); + } + + Dir_handle dir(Path const &path, bool create) + { + PDBGV("_root = %s, path = %s, create = %d", + _root.record()->name(), path.string(), create); + + _assert_valid_path(path.string()); + + if (create) + throw Permission_denied(); + + Absolute_path abs_path(_root.record()->name()); + try { + abs_path.append(path.string()); + } catch (Path_base::Path_too_long) { + throw Name_too_long(); + } + + Lookup_exact lookup_criterion(abs_path.base()); + Record *record = _lookup(&lookup_criterion); + + if (!record) { + PERR("Could not find record for %s", path.string()); + throw Lookup_failed(); + } + + if (record->type() != Record::TYPE_DIR) + throw Lookup_failed(); + + Directory *dir_node = new (env()->heap()) Directory(record); + + return _handle_registry.alloc(dir_node); + } + + Node_handle node(Path const &path) + { + PDBGV("path = %s", path.string()); + + if (!valid_path(path.string()) && + !valid_filename(path.string())) + throw Lookup_failed(); + + Absolute_path abs_path(_root.record()->name()); + try { + abs_path.append(path.string()); + } catch (Path_base::Path_too_long) { + throw Lookup_failed(); + } + + PDBGV("abs_path = %s", abs_path.base()); + + Lookup_exact lookup_criterion(abs_path.base()); + Record *record = _lookup(&lookup_criterion); + + if (!record) { + PERR("Could not find record for %s", path.string()); + throw Lookup_failed(); + } + + Node *node = new (env()->heap()) Node(record); + + return _handle_registry.alloc(node); + } + + void close(Node_handle handle) + { + Node *node; + + try { + node = _handle_registry.lookup(handle); + } catch(Invalid_handle) { + PERR("close() called with invalid handle"); + return; + } + + PDBGV("name = %s", node->record()->name()); + + /* free the handle */ + _handle_registry.free(handle); + + Directory *dir = dynamic_cast(node); + if (dir) { + /* free the node */ + destroy(env()->heap(), dir); + return; + } + + File *file = dynamic_cast(node); + if (file) { + /* free the node */ + destroy(env()->heap(), file); + return; + } + + /* free the node */ + destroy(env()->heap(), node); + } + + Status status(Node_handle node_handle) + { + Status status; + status.inode = 1; + status.size = 0; + status.mode = 0; + + Node *node = _handle_registry.lookup(node_handle); + + status.size = node->record()->size(); + + /* convert TAR record modes to stat modes */ + switch (node->record()->type()) { + case Record::TYPE_DIR: status.mode |= Status::MODE_DIRECTORY; break; + case Record::TYPE_FILE: status.mode |= Status::MODE_FILE; break; + case Record::TYPE_SYMLINK: status.mode |= Status::MODE_SYMLINK; break; + default: + if (verbose) + PWRN("unhandled record type %d", node->record()->type()); + } + + PDBGV("name = %s", node->record()->name()); + + return status; + } + + void control(Node_handle, Control) { } + + void unlink(Dir_handle dir_handle, Name const &name) + { + PDBGV("name = %s", name.string()); + + throw Permission_denied(); + } + + void truncate(File_handle file_handle, file_size_t size) + { + PDBGV("truncate()"); + + throw Permission_denied(); + } + + void move(Dir_handle from_dir_handle, Name const &from_name, + Dir_handle to_dir_handle, Name const &to_name) + { + PDBGV("from_name = %s, to_name = %s", from_name.string(), to_name.string()); + + throw Permission_denied(); + } + }; + + + class Root : public Root_component + { + private: + + Rpc_entrypoint &_channel_ep; + Signal_receiver &_sig_rec; + Directory &_root_dir; + + protected: + + Session_component *_create_session(const char *args) + { + /* + * Determine client-specific policy defined implicitly by + * the client's label. + */ + Directory *session_root_dir = 0; + + enum { ROOT_MAX_LEN = 256 }; + char root[ROOT_MAX_LEN]; + root[0] = 0; + + try { + Session_policy policy(args); + + /* + * Determine directory that is used as root directory of + * the session. + */ + try { + policy.attribute("root").value(root, sizeof(root)); + if (is_root(root)) { + session_root_dir = &_root_dir; + } else { + /* + * Make sure the root path is specified with a + * leading path delimiter. For performing the + * lookup, we skip the first character. + */ + if (root[0] != '/') + throw Lookup_failed(); + + /* TODO: lookup root directory */ + Lookup_exact lookup_criterion(root); + Record *record = _lookup(&lookup_criterion); + if (!record) { + PERR("Could not find record for %s", root); + throw Lookup_failed(); + } + + session_root_dir = new (env()->heap()) Directory(record); + } + } catch (Xml_node::Nonexistent_attribute) { + PERR("Missing \"root\" attribute in policy definition"); + throw Root::Unavailable(); + } catch (Lookup_failed) { + PERR("Session root directory \"%s\" does not exist", root); + throw Root::Unavailable(); + } + + } catch (Session_policy::No_policy_defined) { + PERR("Invalid session request, no matching policy"); + throw Root::Unavailable(); + } + + size_t ram_quota = + Arg_string::find_arg(args, "ram_quota" ).ulong_value(0); + size_t tx_buf_size = + Arg_string::find_arg(args, "tx_buf_size").ulong_value(0); + + /* + * Check if donated ram quota suffices for session data, + * and communication buffer. + */ + size_t session_size = sizeof(Session_component) + tx_buf_size; + if (max((size_t)4096, session_size) > ram_quota) { + PERR("insufficient 'ram_quota', got %zd, need %zd", + ram_quota, session_size); + throw Root::Quota_exceeded(); + } + return new (md_alloc()) + Session_component(tx_buf_size, _channel_ep, _sig_rec, + *session_root_dir); + } + + public: + + /** + * Constructor + * + * \param session_ep session entrypoint + * \param sig_rec signal receiver used for handling the + * data-flow signals of packet streams + * \param md_alloc meta-data allocator + */ + Root(Rpc_entrypoint &session_ep, Allocator &md_alloc, + Signal_receiver &sig_rec, Directory &root_dir) + : + Root_component(&session_ep, &md_alloc), + _channel_ep(session_ep), _sig_rec(sig_rec), _root_dir(root_dir) + { } + }; +}; + + +int main(int, char **) +{ + using namespace File_system; + + enum { STACK_SIZE = 3*sizeof(addr_t)*1024 }; + static Cap_connection cap; + static Rpc_entrypoint ep(&cap, STACK_SIZE, "tar_fs_ep"); + static Sliced_heap sliced_heap(env()->ram_session(), env()->rm_session()); + static Signal_receiver sig_rec; + + /* 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"); + try { + archive_node.attribute("name").value(tar_filename, sizeof(tar_filename)); + } catch (...) { + PERR("Could not read 'name' attribute of 'archive' config node"); + return -1; + } + } catch (...) { + PERR("Could not read 'archive' config node"); + return -1; + } + + /* obtain dataspace of tar archive from ROM service */ + 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); + + static Record root_record; /* every member is 0 */ + static Directory root_dir(&root_record); + + static File_system::Root root(ep, sliced_heap, sig_rec, root_dir); + + env()->parent()->announce(ep.manage(&root)); + + for (;;) { + Signal s = sig_rec.wait_for_signal(); + static_cast(s.context())->dispatch(s.num()); + } + + return 0; +} diff --git a/os/src/server/tar_fs/node.h b/os/src/server/tar_fs/node.h new file mode 100644 index 000000000..49838dcc6 --- /dev/null +++ b/os/src/server/tar_fs/node.h @@ -0,0 +1,56 @@ +/* + * \brief TAR file-system node + * \author Christian Prochaska + * \author Norman Feske + * \date 2012-08-20 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _NODE_H_ +#define _NODE_H_ + +/* local includes */ +#include + +namespace File_system { + + class Node + { + protected: + + Record *_record; + + public: + + Node(Record *record) : _record(record) { } + + Record const *record() const { return _record; } + + /* + * A generic Node object can be created to represent a file or + * directory by its name without opening it, so the functions + * of this class must not be abstract. + */ + + virtual size_t read(char *dst, size_t len, seek_off_t) + { + PERR("read() called on generic Node object"); + return 0; + } + + virtual size_t write(char const *src, size_t len, seek_off_t) + { + PERR("write() called on generic Node object"); + return 0; + } + }; + +} + +#endif /* _NODE_H_ */ diff --git a/os/src/server/tar_fs/node_handle_registry.h b/os/src/server/tar_fs/node_handle_registry.h new file mode 100644 index 000000000..e9b410007 --- /dev/null +++ b/os/src/server/tar_fs/node_handle_registry.h @@ -0,0 +1,137 @@ +/* + * \brief Facility for managing the session-local node-handle namespace + * \author Norman Feske + * \date 2012-04-11 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _NODE_HANDLE_REGISTRY_H_ +#define _NODE_HANDLE_REGISTRY_H_ + +namespace File_system { + + class Node; + class Directory; + class File; + class Symlink; + + /** + * Type trait for determining the node type for a given handle type + */ + template struct Node_type; + template<> struct Node_type { typedef Node Type; }; + template<> struct Node_type { typedef Directory Type; }; + template<> struct Node_type { typedef File Type; }; + template<> struct Node_type { typedef Symlink Type; }; + + + /** + * Type trait for determining the handle type for a given node type + */ + template struct Handle_type; + template<> struct Handle_type { typedef Node_handle Type; }; + template<> struct Handle_type { typedef Dir_handle Type; }; + template<> struct Handle_type { typedef File_handle Type; }; + template<> struct Handle_type { typedef Symlink_handle Type; }; + + + class Node_handle_registry + { + private: + + /* maximum number of open nodes per session */ + enum { MAX_NODE_HANDLES = 128U }; + + Lock mutable _lock; + + Node *_nodes[MAX_NODE_HANDLES]; + + /** + * Allocate node handle + * + * \throw Out_of_node_handles + */ + int _alloc(Node *node) + { + Lock::Guard guard(_lock); + + for (unsigned i = 0; i < MAX_NODE_HANDLES; i++) + if (!_nodes[i]) { + _nodes[i] = node; + return i; + } + + throw Out_of_node_handles(); + } + + bool _in_range(int handle) const + { + return ((handle >= 0) && (handle < MAX_NODE_HANDLES)); + } + + public: + + Node_handle_registry() + { + for (unsigned i = 0; i < MAX_NODE_HANDLES; i++) + _nodes[i] = 0; + } + + template + typename Handle_type::Type alloc(NODE_TYPE *node) + { + typedef typename Handle_type::Type Handle; + return Handle(_alloc(node)); + } + + /** + * Release node handle + */ + void free(Node_handle handle) + { + Lock::Guard guard(_lock); + + if (_in_range(handle.value)) + _nodes[handle.value] = 0; + } + + /** + * Lookup node using its handle as key + * + * \throw Invalid_handle + */ + template + typename Node_type::Type *lookup(HANDLE_TYPE handle) + { + Lock::Guard guard(_lock); + + if (!_in_range(handle.value)) + throw Invalid_handle(); + + typedef typename Node_type::Type Node; + Node *node = dynamic_cast(_nodes[handle.value]); + if (!node) + throw Invalid_handle(); + + return node; + } + + bool refer_to_same_node(Node_handle h1, Node_handle h2) const + { + Lock::Guard guard(_lock); + + if (!_in_range(h1.value) || !_in_range(h2.value)) + throw Invalid_handle(); + + return _nodes[h1.value] == _nodes[h2.value]; + } + }; +} + +#endif /* _NODE_HANDLE_REGISTRY_H_ */ diff --git a/os/src/server/tar_fs/record.h b/os/src/server/tar_fs/record.h new file mode 100644 index 000000000..027cb42b9 --- /dev/null +++ b/os/src/server/tar_fs/record.h @@ -0,0 +1,76 @@ +/* + * \brief TAR record + * \author Norman Feske + * \date 2012-08-20 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _RECORD_H_ +#define _RECORD_H_ + +/* Genode includes */ +#include + +namespace File_system { + + class Record + { + private: + + char _name[100]; + char _mode[8]; + char _uid[8]; + char _gid[8]; + char _size[12]; + char _mtime[12]; + char _checksum[8]; + char _type[1]; + char _linked_name[100]; + + /** + * Convert ASCII-encoded octal number to unsigned value + */ + template + unsigned long _read(T const &field) const + { + /* + * Copy-out ASCII string to temporary buffer that is + * large enough to host an additional zero. + */ + char buf[sizeof(field) + 1]; + strncpy(buf, field, sizeof(buf)); + + unsigned long value = 0; + ascii_to(buf, &value, 8); + return value; + } + + public: + + /* length of on data block in tar */ + enum { BLOCK_LEN = 512 }; + + /* record type values */ + enum { TYPE_FILE = 0, TYPE_HARDLINK = 1, + TYPE_SYMLINK = 2, TYPE_DIR = 5 }; + + size_t size() const { return _read(_size); } + unsigned uid() const { return _read(_uid); } + unsigned gid() const { return _read(_gid); } + unsigned mode() const { return _read(_mode); } + unsigned type() const { return _read(_type); } + char const *name() const { return _name; } + char const *linked_name() const { return _linked_name; } + + void *data() const { return (char *)this + BLOCK_LEN; } + }; + +} + +#endif /* _RECORD_H_ */ diff --git a/os/src/server/tar_fs/target.mk b/os/src/server/tar_fs/target.mk new file mode 100644 index 000000000..874b5c3b9 --- /dev/null +++ b/os/src/server/tar_fs/target.mk @@ -0,0 +1,4 @@ +TARGET = tar_fs +SRC_CC = main.cc +LIBS = cxx env server signal +INC_DIR += $(PRG_DIR) diff --git a/os/src/server/tar_fs/util.h b/os/src/server/tar_fs/util.h new file mode 100644 index 000000000..0b6aa30f3 --- /dev/null +++ b/os/src/server/tar_fs/util.h @@ -0,0 +1,107 @@ +/* + * \brief Utilities + * \author Norman Feske + * \author Christian Prochaska + * \date 2012-04-11 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _UTIL_H_ +#define _UTIL_H_ + +/* Genode includes */ +#include + + +/** + * Return true if character 'c' occurs in null-terminated string 'str' + */ +static inline bool string_contains(char const *str, char c) +{ + for (; *str; str++) + if (*str == c) + return true; + return false; +} + + +/** + * Return true if null-terminated string 'substr' occurs in null-terminated + * string 'str' + */ +static bool string_contains(char const *str, char const *substr) +{ + using namespace Genode; + + size_t str_len = strlen(str); + size_t substr_len = strlen(substr); + + if (str_len < substr_len) + return false; + + for (size_t i = 0; i <= (str_len - substr_len); i++) + if (strcmp(&str[i], substr, substr_len) == 0) + return true; + + return false; +} + + +/** + * Return true if 'str' is a valid file name + */ +static inline bool valid_filename(char const *str) +{ + if (!str) return false; + + /* must have at least one character */ + if (str[0] == 0) return false; + + /* must not contain '/' or '\' or ':' */ + if (string_contains(str, '/') || + string_contains(str, '\\') || + string_contains(str, ':')) + return false; + + return true; +} + + +/** + * Return true if 'str' is a valid path + */ +static inline bool valid_path(char const *str) +{ + if (!str) return false; + + /* must start with '/' */ + if (str[0] != '/') + return false; + + /* must not contain '\' or ':' */ + if (string_contains(str, '\\') || + string_contains(str, ':')) + return false; + + /* must not contain "/../" */ + if (string_contains(str, "/../")) return false; + + return true; +} + + +/** + * Return true if 'str' is "/" + */ +static inline bool is_root(const char *str) +{ + return (Genode::strcmp(str, "/") == 0); +} + +#endif /* _UTIL_H_ */