/* * \brief TAR file system * \author Norman Feske * \date 2011-02-17 */ /* * Copyright (C) 2011-2017 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. */ #ifndef _INCLUDE__VFS__TAR_FILE_SYSTEM_H_ #define _INCLUDE__VFS__TAR_FILE_SYSTEM_H_ #include #include #include #include namespace Vfs { class Tar_file_system; } class Vfs::Tar_file_system : public File_system { Genode::Env &_env; Genode::Allocator &_alloc; typedef Genode::String<64> Rom_name; Rom_name _rom_name; Genode::Attached_rom_dataspace _tar_ds { _env, _rom_name.string() }; char *_tar_base = _tar_ds.local_addr(); file_size const _tar_size = _tar_ds.size(); /* * Noncopyable */ Tar_file_system(Tar_file_system const &); Tar_file_system &operator = (Tar_file_system const &); 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(buf, value, 8); return value; } char const *_data_begin() const { return (char const *)this + BLOCK_LEN; } /* * GNU extension for long path names, which support unlimited sizes using * separate records */ bool _long_name() const { return _type[0] == TYPE_LONG_LINK || _type[0] == TYPE_LONG_NAME; } /* * Round up up next block */ file_size _block_align(file_size size) const { return Genode::align_addr(size, BLOCK_SHIFT); } /* * Next record header */ Record *_next() const { return (Record *)(_data_begin() + _block_align(_read(_size))); } public: /* length of one data block in tar */ enum { BLOCK_SHIFT = 9, /* 512 bytes */ BLOCK_LEN = 1ul << BLOCK_SHIFT, }; /* record type values */ enum { TYPE_FILE = 0, TYPE_HARDLINK = 1, TYPE_SYMLINK = 2, TYPE_DIR = 5, /* GNU extensions */ TYPE_LONG_LINK = 75, TYPE_LONG_NAME = 76 }; file_size size() const { return _long_name() ? _next()->size() : _read(_size); } long long mtime() const { return _long_name() ? _next()->mtime() : _read(_mtime); } unsigned uid() const { return _long_name() ? _next()->uid() : _read(_uid); } unsigned gid() const { return _long_name() ? _next()->gid() : _read(_gid); } unsigned mode() const { return _long_name() ? _next()->mode() : _read(_mode); } unsigned type() const { return _long_name() ? _next()->type() : _read(_type); } void *data() const { return _long_name() ? _next()->data() : (void *)_data_begin(); } Node_rwx rwx() const { unsigned const mode_bits = mode(); return { .readable = (mode_bits & 0400) != 0, .writeable = (mode_bits & 0200) != 0, .executable = (mode_bits & 0100) != 0 }; } char const *name() const { return _long_name() ? _data_begin() : _name; } unsigned max_name_len() const { return _long_name() ? MAX_PATH_LEN : 100; } char const *linked_name() const { return _long_name() ? _data_begin() : _linked_name; } file_size storage_size() { if (_long_name()) { /* this size + next header + next size */ return _block_align(_read(_size)) + BLOCK_LEN + _block_align(_next()->size()); } return _read(_size); } }; class Node; class Tar_vfs_handle : public Vfs_handle { private: /* * Noncopyable */ Tar_vfs_handle(Tar_vfs_handle const &); Tar_vfs_handle &operator = (Tar_vfs_handle const &); protected: Node const *_node; public: Tar_vfs_handle(File_system &fs, Allocator &alloc, int status_flags, Node const *node) : Vfs_handle(fs, fs, alloc, status_flags), _node(node) { } virtual Read_result read(char *dst, file_size count, file_size &out_count) = 0; }; struct Tar_vfs_file_handle : Tar_vfs_handle { using Tar_vfs_handle::Tar_vfs_handle; Read_result read(char *dst, file_size count, file_size &out_count) override { file_size const record_size = _node->record->size(); file_size const record_bytes_left = record_size >= seek() ? record_size - seek() : 0; count = min(record_bytes_left, count); char const *data = (char *)_node->record->data() + seek(); memcpy(dst, data, count); out_count = count; return READ_OK; } }; struct Tar_vfs_dir_handle : Tar_vfs_handle { using Tar_vfs_handle::Tar_vfs_handle; Read_result read(char *dst, file_size count, file_size &out_count) override { if (count < sizeof(Dirent)) return READ_ERR_INVALID; Dirent &dirent = *(Dirent*)dst; file_offset const index = seek() / sizeof(Dirent); Node const *node_ptr = _node->lookup_child(index); if (!node_ptr) { dirent = Dirent { }; out_count = 0; return READ_OK; } Node const &node = *node_ptr; Record const *record_ptr = node.record; while (record_ptr && (record_ptr->type() == Record::TYPE_HARDLINK)) { Tar_file_system &tar_fs = static_cast(fs()); Node const *target = tar_fs.dereference(record_ptr->linked_name()); record_ptr = target ? target->record : 0; } using Dirent_type = Vfs::Directory_service::Dirent_type; /* if no record exists, assume it is a directory */ if (!record_ptr) { dirent = { .fileno = (Genode::addr_t)node_ptr, .type = Dirent_type::DIRECTORY, .rwx = Node_rwx::rx(), .name = { node.name } }; out_count = sizeof(Dirent); return READ_OK; } Record const &record = *record_ptr; auto node_type = [&] () { switch (record.type()) { case Record::TYPE_FILE: return Dirent_type::CONTINUOUS_FILE; case Record::TYPE_SYMLINK: return Dirent_type::SYMLINK; case Record::TYPE_DIR: return Dirent_type::DIRECTORY; }; Genode::warning("unhandled record type ", record.type(), " " "for ", node.name); return Dirent_type::END; }; dirent = { .fileno = (Genode::addr_t)node_ptr, .type = node_type(), .rwx = { .readable = true, .writeable = false, .executable = record.rwx().executable }, .name = { node.name } }; out_count = sizeof(Dirent); return READ_OK; } }; struct Tar_vfs_symlink_handle : Tar_vfs_handle { using Tar_vfs_handle::Tar_vfs_handle; Read_result read(char *buf, file_size buf_size, file_size &out_count) override { Record const *record = _node->record; file_size const count = min(buf_size, 100ULL); memcpy(buf, record->linked_name(), count); out_count = count; return READ_OK; } }; 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); 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)); for (child_node = parent_node->first(); child_node; child_node = child_node->next()) { if (strcmp(child_node->name, path_element) == 0) { parent_node = child_node; break; } } if (!child_node) return 0; t = t.next(); } return parent_node; } Node const *lookup_child(int index) const { for (Node const *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: Genode::Allocator &_alloc; Node &_root_node; public: Add_node_action(Genode::Allocator &alloc, Node &root_node) : _alloc(alloc), _root_node(root_node) { } void operator()(Record const *record) { Absolute_path current_path; char path_element[MAX_PATH_LEN]; if (record->max_name_len() > 100 || record->name()[99] == 0) current_path.import(record->name()); /* * GNU tar does not null terminate names of length 100 */ else { strncpy(path_element, record->name(), 101); current_path.import(path_element); } 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 (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()) { /* * 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*)_alloc.alloc(name_size); strncpy(name, path_element, name_size); child_node = new (_alloc) Node(name, record); } else { /* create a directory node without record */ Genode::size_t name_size = strlen(path_element) + 1; char *name = (char*)_alloc.alloc(name_size); strncpy(name, path_element, name_size); child_node = new (_alloc) 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->storage_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; /** * Walk hardlinks until we reach a file */ Node const *dereference(char const *path) { Node const *node = _root_node.lookup(path); Node const *slow_node = node; int i = 0; while (node) { Record const *record = node->record; if (!record || record->type() != Record::TYPE_HARDLINK) break; /* got it */ /* * The `node` pointer is followed every iteration and * `slow_node` every-other iteration. If there is a * loop then eventually we catch it as the faster * laps the slower. */ node = _root_node.lookup(record->linked_name()); if (i++ & 1) { slow_node = _root_node.lookup(slow_node->record->linked_name()); if (node == slow_node) { Genode::error(_rom_name, " contains a hard-link loop at '", path, "'"); node = nullptr; } } } return node; } public: Tar_file_system(Vfs::Env &env, Genode::Xml_node config) : _env(env.env()), _alloc(env.alloc()), _rom_name(config.attribute_value("name", Rom_name())), _root_node("", 0), _cached_num_dirent(_root_node) { Genode::log("tar archive '", _rom_name, "' " "local at ", (void *)_tar_base, ", size is ", _tar_size); _for_each_tar_record_do(Add_node_action(_alloc, _root_node)); } /********************************* ** Directory-service interface ** *********************************/ Dataspace_capability dataspace(char const *path) override { Node const *node = dereference(path); if (!node || !node->record) return Dataspace_capability(); Record const *record = node->record; if (record->type() != Record::TYPE_FILE) { Genode::error("TAR record \"", path, "\" has " "unsupported type ", record->type()); return Dataspace_capability(); } try { Ram_dataspace_capability ds_cap = _env.ram().alloc(record->size()); void *local_addr = _env.rm().attach(ds_cap); memcpy(local_addr, record->data(), record->size()); _env.rm().detach(local_addr); return ds_cap; } catch (...) { Genode::warning(__func__, " could not create new dataspace"); } return Dataspace_capability(); } void release(char const *, Dataspace_capability ds_cap) override { _env.ram().free(static_cap_cast(ds_cap)); } Stat_result stat(char const *path, Stat &out) override { out = Stat { }; Node const *node_ptr = dereference(path); if (!node_ptr) return STAT_ERR_NO_ENTRY; if (!node_ptr->record) { out.type = Node_type::DIRECTORY; out.rwx = Node_rwx::rx(); return STAT_OK; } Record const &record = *node_ptr->record; auto node_type = [&] () { switch (record.type()) { case Record::TYPE_FILE: return Node_type::CONTINUOUS_FILE; case Record::TYPE_SYMLINK: return Node_type::SYMLINK; case Record::TYPE_DIR: return Node_type::DIRECTORY; }; return Node_type::DIRECTORY; }; out = { .size = record.size(), .type = node_type(), .rwx = { .readable = true, .writeable = false, .executable = record.rwx().executable }, .inode = (Genode::addr_t)node_ptr, .device = (Genode::addr_t)this, .modification_time = { record.mtime() } }; return STAT_OK; } Unlink_result unlink(char const *path) override { Node const *node = dereference(path); if (!node) return UNLINK_ERR_NO_ENTRY; else return UNLINK_ERR_NO_PERM; } Rename_result rename(char const *from, char const *to) override { if (_root_node.lookup(from) || _root_node.lookup(to)) return RENAME_ERR_NO_PERM; return RENAME_ERR_NO_ENTRY; } file_size num_dirent(char const *path) override { return _cached_num_dirent.num_dirent(path); } bool directory(char const *path) override { Node const *node = dereference(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, Genode::Allocator& alloc) override { Node const *node = dereference(path); if (!node || !node->record || node->record->type() != Record::TYPE_FILE) return OPEN_ERR_UNACCESSIBLE; try { *out_handle = new (alloc) Tar_vfs_file_handle(*this, alloc, 0, node); return OPEN_OK; } catch (Genode::Out_of_ram) { return OPEN_ERR_OUT_OF_RAM; } catch (Genode::Out_of_caps) { return OPEN_ERR_OUT_OF_CAPS; } } Opendir_result opendir(char const *path, bool /* create */, Vfs_handle **out_handle, Genode::Allocator& alloc) override { Node const *node = dereference(path); if (!node || (node->record && (node->record->type() != Record::TYPE_DIR))) return OPENDIR_ERR_LOOKUP_FAILED; try { *out_handle = new (alloc) Tar_vfs_dir_handle(*this, alloc, 0, node); return OPENDIR_OK; } catch (Genode::Out_of_ram) { return OPENDIR_ERR_OUT_OF_RAM; } catch (Genode::Out_of_caps) { return OPENDIR_ERR_OUT_OF_CAPS; } } Openlink_result openlink(char const *path, bool /* create */, Vfs_handle **out_handle, Allocator &alloc) override { Node const *node = dereference(path); if (!node || !node->record || node->record->type() != Record::TYPE_SYMLINK) return OPENLINK_ERR_LOOKUP_FAILED; try { *out_handle = new (alloc) Tar_vfs_symlink_handle(*this, alloc, 0, node); return OPENLINK_OK; } catch (Genode::Out_of_ram) { return OPENLINK_ERR_OUT_OF_RAM; } catch (Genode::Out_of_caps) { return OPENLINK_ERR_OUT_OF_CAPS; } } void close(Vfs_handle *vfs_handle) override { Tar_vfs_handle *tar_handle = static_cast(vfs_handle); if (tar_handle) destroy(vfs_handle->alloc(), tar_handle); } /*************************** ** File_system interface ** ***************************/ static char const *name() { return "tar"; } char const *type() override { return "tar"; } /******************************** ** File I/O service interface ** ********************************/ Write_result write(Vfs_handle *, char const *, file_size, file_size &) override { return WRITE_ERR_INVALID; } Read_result complete_read(Vfs_handle *vfs_handle, char *dst, file_size count, file_size &out_count) override { out_count = 0; Tar_vfs_handle *handle = static_cast(vfs_handle); if (!handle) return READ_ERR_INVALID; return handle->read(dst, count, out_count); } Ftruncate_result ftruncate(Vfs_handle *, file_size) override { return FTRUNCATE_ERR_NO_PERM; } bool read_ready(Vfs_handle *) override { return true; } }; #endif /* _INCLUDE__VFS__TAR_FILE_SYSTEM_H_ */