/* * \brief TAR file system * \author Norman Feske * \date 2011-02-17 */ /* * Copyright (C) 2011-2014 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 _INCLUDE__VFS__TAR_FILE_SYSTEM_H_ #define _INCLUDE__VFS__TAR_FILE_SYSTEM_H_ #include #include #include namespace Vfs { class Tar_file_system; } class Vfs::Tar_file_system : public File_system { enum { verbose = false }; struct Rom_name { enum { ROM_NAME_MAX_LEN = 64 }; char name[ROM_NAME_MAX_LEN]; Rom_name(Xml_node config) { config.attribute("name").value(name, sizeof(name)); } } _rom_name; Genode::Rom_connection _rom; Genode::Dataspace_capability _tar_ds; char *_tar_base; file_size _tar_size; 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; Genode::ascii_to_unsigned_long(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 }; file_size 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; } }; class Tar_vfs_handle : public Vfs_handle { private: Record const *_record; public: Tar_vfs_handle(File_system &fs, int status_flags, Record const *record) : Vfs_handle(fs, fs, status_flags), _record(record) { } Record const *record() const { return _record; } }; struct Scanner_policy_path_element { static bool identifier_char(char c, unsigned /* i */) { return (c != '/') && (c != 0); } }; typedef Genode::Token Path_element_token; struct Node : List, List::Element { char const *name; Record const *record; Node(char const *name, Record const *record) : name(name), record(record) { } Node *lookup(char const *name) { Absolute_path lookup_path(name); if (verbose) PDBG("lookup_path = %s", lookup_path.base()); Node *parent_node = this; Node *child_node; Path_element_token t(lookup_path.base()); while (t) { if (t.type() != Path_element_token::IDENT) { t = t.next(); continue; } char path_element[MAX_PATH_LEN]; t.string(path_element, sizeof(path_element)); if (verbose) PDBG("path_element = %s", path_element); for (child_node = parent_node->first(); child_node; child_node = child_node->next()) { if (verbose) PDBG("comparing with node %s", child_node->name); if (strcmp(child_node->name, path_element) == 0) { if (verbose) PDBG("found matching child node"); parent_node = child_node; break; } } if (!child_node) return 0; t = t.next(); } return parent_node; } Node *lookup_child(int index) { for (Node *child_node = first(); child_node; child_node = child_node->next(), index--) { if (index == 0) return child_node; } return 0; } file_size num_dirent() { file_size count = 0; for (Node *child_node = first(); child_node; child_node = child_node->next(), count++) ; return count; } } _root_node; /* * Create a Node for a tar record and insert it into the node list */ class Add_node_action { private: Node &_root_node; public: Add_node_action(Node &root_node) : _root_node(root_node) { } void operator()(Record const *record) { Absolute_path current_path(record->name()); if (verbose) PDBG("current_path = %s", current_path.base()); char path_element[MAX_PATH_LEN]; Path_element_token t(current_path.base()); Node *parent_node = &_root_node; Node *child_node; while(t) { if (t.type() != Path_element_token::IDENT) { t = t.next(); continue; } Absolute_path remaining_path(t.start()); t.string(path_element, sizeof(path_element)); for (child_node = parent_node->first(); child_node; child_node = child_node->next()) { if (strcmp(child_node->name, path_element) == 0) break; } if (child_node) { if (verbose) PDBG("found node for %s", path_element); if (remaining_path.has_single_element()) { /* Found a node for the record to be inserted. * This is usually a directory node without * record. */ child_node->record = record; } } else { if (remaining_path.has_single_element()) { if (verbose) PDBG("creating node for %s", path_element); /* * TODO: find 'path_element' in 'record->name' * and use the location in the record as name * pointer to save some memory */ Genode::size_t name_size = strlen(path_element) + 1; char *name = (char*)env()->heap()->alloc(name_size); strncpy(name, path_element, name_size); child_node = new (env()->heap()) Node(name, record); } else { if (verbose) PDBG("creating node without record for %s", path_element); /* create a directory node without record */ Genode::size_t name_size = strlen(path_element) + 1; char *name = (char*)env()->heap()->alloc(name_size); strncpy(name, path_element, name_size); child_node = new (env()->heap()) Node(name, 0); } parent_node->insert(child_node); } parent_node = child_node; t = t.next(); } } }; template void _for_each_tar_record_do(Tar_record_action tar_record_action) { /* 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); tar_record_action(record); file_size size = record->size(); /* some datablocks */ /* one metablock */ block_id = block_id + (size / Record::BLOCK_LEN) + 1; /* round up */ if (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; } } struct Num_dirent_cache { Lock lock; Node &root_node; bool valid; /* true after first lookup */ char key[256]; /* key used for lookup */ file_size cached_num_dirent; /* cached value */ Num_dirent_cache(Node &root_node) : root_node(root_node), valid(false), cached_num_dirent(0) { } file_size num_dirent(char const *path) { Lock::Guard guard(lock); /* check for cache miss */ if (!valid || strcmp(path, key) != 0) { Node *node = root_node.lookup(path); if (!node) return 0; strncpy(key, path, sizeof(key)); cached_num_dirent = node->num_dirent(); valid = true; } return cached_num_dirent; } } _cached_num_dirent; public: Tar_file_system(Xml_node config) : _rom_name(config), _rom(_rom_name.name), _tar_ds(_rom.dataspace()), _tar_base(env()->rm_session()->attach(_tar_ds)), _tar_size(Dataspace_client(_tar_ds).size()), _root_node("", 0), _cached_num_dirent(_root_node) { PINF("tar archive '%s' local at %p, size is %llu", _rom_name.name, _tar_base, _tar_size); _for_each_tar_record_do(Add_node_action(_root_node)); } /********************************* ** Directory-service interface ** *********************************/ Dataspace_capability dataspace(char const *path) override { /* * Walk hardlinks until we reach a file */ Record const *record = 0; for (;;) { Node *node = _root_node.lookup(path); if (!node) return Dataspace_capability(); record = node->record; if (record) { if (record->type() == Record::TYPE_HARDLINK) { path = record->linked_name(); continue; } if (record->type() == Record::TYPE_FILE) break; PERR("TAR record \"%s\" has unsupported type %d", record->name(), record->type()); } PERR("TAR record \"%s\" has unsupported type %d", path, Record::TYPE_DIR); return Dataspace_capability(); } try { Ram_dataspace_capability ds_cap = env()->ram_session()->alloc(record->size()); void *local_addr = env()->rm_session()->attach(ds_cap); memcpy(local_addr, record->data(), record->size()); env()->rm_session()->detach(local_addr); return ds_cap; } catch (...) { PDBG("Could not create new dataspace"); } return Dataspace_capability(); } void release(char const *, Dataspace_capability ds_cap) override { env()->ram_session()->free(static_cap_cast(ds_cap)); } Stat_result stat(char const *path, Stat &out) override { if (verbose) PDBG("path = %s", path); Node const *node = 0; Record const *record = 0; /* * Walk hardlinks until we reach a file */ for (;;) { node = _root_node.lookup(path); if (!node) return STAT_ERR_NO_ENTRY; record = node->record; if (record) { if (record->type() == Record::TYPE_HARDLINK) { path = record->linked_name(); continue; } else break; } else { if (verbose) PDBG("found a virtual directoty node"); memset(&out, 0, sizeof(out)); out.mode = STAT_MODE_DIRECTORY; return STAT_OK; } } /* convert TAR record modes to stat modes */ unsigned mode = record->mode(); switch (record->type()) { case Record::TYPE_FILE: mode |= STAT_MODE_FILE; break; case Record::TYPE_SYMLINK: mode |= STAT_MODE_SYMLINK; break; case Record::TYPE_DIR: mode |= STAT_MODE_DIRECTORY; break; default: if (verbose) PDBG("unhandled record type %d", record->type()); } memset(&out, 0, sizeof(out)); out.mode = mode; out.size = record->size(); out.uid = record->uid(); out.gid = record->gid(); out.inode = (unsigned long)node; return STAT_OK; } Dirent_result dirent(char const *path, file_offset index, Dirent &out) override { Node *node = _root_node.lookup(path); if (!node) return DIRENT_ERR_INVALID_PATH; node = node->lookup_child(index); if (!node) { out.type = DIRENT_TYPE_END; return DIRENT_OK; } out.fileno = (unsigned long)node; Record const *record = node->record; if (record) { switch (record->type()) { case 0: out.type = DIRENT_TYPE_FILE; break; case 2: out.type = DIRENT_TYPE_SYMLINK; break; case 5: out.type = DIRENT_TYPE_DIRECTORY; break; default: if (verbose) PDBG("unhandled record type %d", record->type()); } } strncpy(out.name, node->name, sizeof(out.name)); return DIRENT_OK; } Unlink_result unlink(char const *) override { return UNLINK_ERR_NO_PERM; } Readlink_result readlink(char const *path, char *buf, file_size buf_size, file_size &out_len) override { Node *node = _root_node.lookup(path); Record const *record = node ? node->record : 0; if (!record || (record->type() != Record::TYPE_SYMLINK)) return READLINK_ERR_NO_ENTRY; file_size const count = min(buf_size, 100ULL); memcpy(buf, record->linked_name(), count); out_len = count; return READLINK_OK; } Rename_result rename(char const *, char const *) override { return RENAME_ERR_NO_PERM; } Mkdir_result mkdir(char const *, unsigned) override { return MKDIR_ERR_NO_PERM; } Symlink_result symlink(char const *, char const *) override { return SYMLINK_ERR_NO_ENTRY; } file_size num_dirent(char const *path) override { return _cached_num_dirent.num_dirent(path); } bool is_directory(char const *path) override { Node *node = _root_node.lookup(path); if (!node) return false; Record const *record = node->record; return record ? (record->type() == Record::TYPE_DIR) : true; } char const *leaf_path(char const *path) override { /* * Check if path exists within the file system. If this is the * case, return the whole path, which is relative to the root * of this file system. */ Node *node = _root_node.lookup(path); return node ? path : 0; } Open_result open(char const *path, unsigned, Vfs_handle **out_handle) override { Node *node = _root_node.lookup(path); if (!node) return OPEN_ERR_UNACCESSIBLE; *out_handle = new (env()->heap()) Tar_vfs_handle(*this, 0, node->record); return OPEN_OK; } /*************************** ** File_system interface ** ***************************/ static char const *name() { return "tar"; } /******************************** ** File I/O service interface ** ********************************/ Write_result write(Vfs_handle *, char const *, file_size, file_size &) override { PDBG("called\n"); return WRITE_ERR_INVALID; } Read_result read(Vfs_handle *vfs_handle, char *dst, file_size count, file_size &out_count) override { Tar_vfs_handle const *handle = static_cast(vfs_handle); file_size const record_size = handle->record()->size(); file_size const record_bytes_left = record_size >= handle->seek() ? record_size - handle->seek() : 0; count = min(record_bytes_left, count); char const *data = (char *)handle->record()->data() + handle->seek(); memcpy(dst, data, count); out_count = count; return READ_OK; } Ftruncate_result ftruncate(Vfs_handle *handle, file_size) override { PDBG("called\n"); return FTRUNCATE_ERR_NO_PERM; } }; #endif /* _INCLUDE__VFS__TAR_FILE_SYSTEM_H_ */