/* * \brief Directory file system * \author Norman Feske * \date 2012-04-23 */ /* * Copyright (C) 2011-2013 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 _NOUX__DIR_FILE_SYSTEM_H_ #define _NOUX__DIR_FILE_SYSTEM_H_ /* Genode includes */ #include #include /* Noux includes */ #include #include #include namespace Noux { class Dir_file_system : public File_system { public: enum { MAX_NAME_LEN = 128 }; private: Lock _lock; /* pointer to first child file system */ File_system *_first_file_system; /* add new file system to the list of children */ void _append_file_system(File_system *fs) { if (!_first_file_system) { _first_file_system = fs; return; } File_system *curr = _first_file_system; while (curr->next) curr = curr->next; curr->next = fs; } /** * Directory name */ char _name[MAX_NAME_LEN]; bool _is_root() const { return _name[0] == 0; } public: Dir_file_system(Xml_node node, File_system_registry &fs_registry) : _first_file_system(0) { /* remember directory name */ if (node.has_type("fstab")) _name[0] = 0; else node.attribute("name").value(_name, sizeof(_name)); for (unsigned i = 0; i < node.num_sub_nodes(); i++) { Xml_node sub_node = node.sub_node(i); /* traverse into nodes */ if (sub_node.has_type("dir")) { _append_file_system(new Dir_file_system(sub_node, fs_registry)); continue; } File_system_registry::Entry *fs = fs_registry.lookup(sub_node); if (fs) { _append_file_system(fs->create(sub_node)); continue; } char type_name[64]; sub_node.type_name(type_name, sizeof(type_name)); PWRN("unknown fstab node type <%s>", type_name); } } /** * Return portion of the path after the element corresponding to * the current directory. */ char const *_sub_path(char const *path) const { /* do not strip anything from the path when we are root */ if (_is_root()) return path; /* skip heading slash in path if present */ if (path[0] == '/') path++; size_t const name_len = strlen(_name); if (strcmp(path, _name, name_len) != 0) return 0; path += name_len; /* * The first characters of the first path element are equal to * the current directory name. Let's check if the length of the * first path element matches the name length. */ if (*path != 0 && *path != '/') return 0; return path; } /********************************* ** Directory-service interface ** *********************************/ Dataspace_capability dataspace(char const *path) { path = _sub_path(path); if (!path) return Dataspace_capability(); /* * Query sub file systems for dataspace using the path local to * the respective file system */ File_system *fs = _first_file_system; for (; fs; fs = fs->next) { Dataspace_capability ds = fs->dataspace(path); if (ds.valid()) return ds; } return Dataspace_capability(); } void release(char const *path, Dataspace_capability ds_cap) { path = _sub_path(path); if (!path) return; for (File_system *fs = _first_file_system; fs; fs = fs->next) fs->release(path, ds_cap); } bool stat(Sysio *sysio, char const *path) { path = _sub_path(path); sysio->error.stat = Sysio::STAT_ERR_NO_ENTRY; /* path does not match directory name */ if (!path) return false; /* * If path equals directory name, return information about the * current directory. */ if (strlen(path) == 0) { sysio->stat_out.st.size = 0; sysio->stat_out.st.mode = Sysio::STAT_MODE_DIRECTORY | 0755; sysio->stat_out.st.uid = 0; sysio->stat_out.st.gid = 0; return true; } /* * The given path refers to one of our sub directories. * Propagate the request into our file systems. */ for (File_system *fs = _first_file_system; fs; fs = fs->next) { if (fs->stat(sysio, path)) { return true; } else { /* * Keep the most meaningful error code. When using * stacked file systems, most child file systems will * eventually return 'STAT_ERR_NO_ENTRY' (or leave * the error code unchanged). If any of those file * systems has anything more interesting to tell, * return this information. */ if (sysio->error.stat != Sysio::STAT_ERR_NO_ENTRY) return false; } } /* none of our file systems felt responsible for the path */ return false; } /** * The 'path' is relative to the child file systems. */ bool _dirent_of_file_systems(Sysio *sysio, char const *path, off_t index) { int base = 0; for (File_system *fs = _first_file_system; fs; fs = fs->next) { /* * Determine number of matching directory entries within * the current file system. */ int const fs_num_dirent = fs->num_dirent(path); /* * Query directory entry if index lies with the file * system. */ if (index - base < fs_num_dirent) { index = index - base; bool const res = fs->dirent(sysio, path, index); sysio->dirent_out.entry.fileno += base; return res; } /* adjust base index for next file system */ base += fs_num_dirent; } sysio->dirent_out.entry.type = Sysio::DIRENT_TYPE_END; return true; } bool _dirent_of_this_dir_node(Sysio *sysio, off_t index) { if (index == 0) { strncpy(sysio->dirent_out.entry.name, _name, sizeof(sysio->dirent_out.entry.name)); sysio->dirent_out.entry.type = Sysio::DIRENT_TYPE_DIRECTORY; sysio->dirent_out.entry.fileno = 1; } else sysio->dirent_out.entry.type = Sysio::DIRENT_TYPE_END; return true; } bool dirent(Sysio *sysio, char const *path, off_t index) { Lock::Guard guard(_lock); if (_is_root()) { return _dirent_of_file_systems(sysio, path, index); } else { if (strcmp(path, "/") == 0) return _dirent_of_this_dir_node(sysio, index); /* path contains at least one element */ /* remove current element from path */ path = _sub_path(path); if (path) { return _dirent_of_file_systems(sysio, path, index); } else { /* path does not lie within our tree */ return false; } } } /* * Accumulate number of directory entries that match in any of * our sub file systems. */ size_t _sum_dirents_of_file_systems(char const *path) { size_t cnt = 0; for (File_system *fs = _first_file_system; fs; fs = fs->next) { cnt += fs->num_dirent(path); } return cnt; } size_t num_dirent(char const *path) { Lock::Guard guard(_lock); if (_is_root()) { return _sum_dirents_of_file_systems(path); } else { if (strcmp(path, "/") == 0) return 1; /* * The path contains at least one element. Remove current * element from path. */ path = _sub_path(path); /* * If the resulting 'path' is non-NULL, the path lies * within our tree. In this case, determine the sum of * matching dirents of all our file systems. Otherwise, * the specified path lies outside our directory node. */ return path ? _sum_dirents_of_file_systems(path) : 0; } } bool is_directory(char const *path) { Lock::Guard guard(_lock); path = _sub_path(path); if (!path) return false; if (strlen(path) == 0) return true; for (File_system *fs = _first_file_system; fs; fs = fs->next) if (fs->is_directory(path)) return true; return false; } char const *leaf_path(char const *path) { Lock::Guard guard(_lock); path = _sub_path(path); if (!path) return 0; if (strlen(path) == 0) return path; for (File_system *fs = _first_file_system; fs; fs = fs->next) { char const *leaf_path = fs->leaf_path(path); if (leaf_path) return leaf_path; } return 0; } Vfs_handle *open(Sysio *sysio, char const *path) { /* * If 'path' is a directory, we create a 'Vfs_handle' * for the root directory so that subsequent 'dirent' calls * are subjected to the stacked file-system layout. */ if (is_directory(path)) return new (env()->heap()) Vfs_handle(this, this, 0); /* * If 'path' refers to a non-directory node, create a * 'Vfs_handle' local to the file system that provides the * file. */ Lock::Guard guard(_lock); path = _sub_path(path); /* check if path does not match directory name */ if (!path) return 0; /* path equals directory name */ if (strlen(path) == 0) return new (env()->heap()) Vfs_handle(this, this, 0); /* path refers to any of our sub file systems */ for (File_system *fs = _first_file_system; fs; fs = fs->next) { Vfs_handle *vfs_handle = fs->open(sysio, path); if (vfs_handle) return vfs_handle; } /* path does not match any existing file or directory */ return 0; } bool unlink(Sysio *sysio, char const *path) { path = _sub_path(path); sysio->error.unlink = Sysio::UNLINK_ERR_NO_ENTRY; /* path does not match directory name */ if (!path) return false; /* * Prevent unlinking if path equals directory name defined * via the static fstab configuration. */ if (strlen(path) == 0) { sysio->error.unlink = Sysio::UNLINK_ERR_NO_PERM; return false; } /* * The given path refers to at least one of our sub * directories. Propagate the request into all of our file * systems. If at least one unlink operation succeeded, we * return success. */ for (File_system *fs = _first_file_system; fs; fs = fs->next) if (fs->unlink(sysio, path)) { return true; } else { /* * Keep the most meaningful error code. When using * stacked file systems, most child file systems will * eventually return 'UNLINK_ERR_NO_ENTRY' (or leave * the error code unchanged). If any of those file * systems has anything more interesting to tell * (in particular 'UNLINK_ERR_NO_PERM'), return * this information. */ if (sysio->error.unlink != Sysio::UNLINK_ERR_NO_ENTRY) return false; } /* none of our file systems could successfully unlink the path */ return false; } bool readlink(Sysio *sysio, char const *path) { path = _sub_path(path); sysio->error.readlink = Sysio::READLINK_ERR_NO_ENTRY; /* path does not match directory name */ if (!path) return false; /* path refers to any of our sub file systems */ for (File_system *fs = _first_file_system; fs; fs = fs->next) { if (fs->readlink(sysio, path)) { return true; } else { /* * Keep the most meaningful error code. When using * stacked file systems, most child file systems will * eventually return 'READLINK_ERR_NO_ENTRY' (or leave * the error code unchanged). If any of those file * systems has anything more interesting to tell, * return this information. */ if (sysio->error.readlink != Sysio::READLINK_ERR_NO_ENTRY) return false; } } /* none of our file systems could read the link */ return false; } bool rename(Sysio *sysio, char const *from_path, char const *to_path) { from_path = _sub_path(from_path); sysio->error.rename = Sysio::RENAME_ERR_NO_ENTRY; /* path does not match directory name */ if (!from_path) return false; /* * Prevent renaming if path equals directory name defined * via the static fstab configuration. */ if (strlen(from_path) == 0) { sysio->error.rename = Sysio::RENAME_ERR_NO_PERM; return false; } /* * Check if destination path resides within the same file * system instance as the source path. */ to_path = _sub_path(to_path); if (!to_path) { sysio->error.rename = Sysio::RENAME_ERR_CROSS_FS; return false; } /* path refers to any of our sub file systems */ for (File_system *fs = _first_file_system; fs; fs = fs->next) { if (fs->rename(sysio, from_path, to_path)) { return true; } else { /* * Keep the most meaningful error code. When using * stacked file systems, most child file systems will * eventually return 'RENAME_ERR_NO_ENTRY' (or leave * the error code unchanged). If any of those file * systems has anything more interesting to tell, * return this information. */ if (sysio->error.rename != Sysio::RENAME_ERR_NO_ENTRY) return false; } } /* none of our file systems could successfully rename the path */ return false; } bool symlink(Sysio *sysio, char const *path) { path = _sub_path(path); sysio->error.symlink = Sysio::SYMLINK_ERR_NO_ENTRY; /* path does not match directory name */ if (!path) return false; /* * Prevent symlink of path that equals directory name defined * via the static fstab configuration. */ if (strlen(path) == 0) { sysio->error.symlink = Sysio::SYMLINK_ERR_EXISTS; return false; } /* path refers to any of our sub file systems */ for (File_system *fs = _first_file_system; fs; fs = fs->next) { if (fs->symlink(sysio, path)) { return true; } else { /* * Keep the most meaningful error code. When using * stacked file systems, most child file systems will * eventually return 'SYMLINK_ERR_NO_ENTRY' (or leave * the error code unchanged). If any of those file * systems has anything more interesting to tell, * return this information. */ if (sysio->error.symlink != Sysio::SYMLINK_ERR_NO_ENTRY) return false; } } /* none of our file systems could create the symlink */ return false; } bool mkdir(Sysio *sysio, char const *path) { path = _sub_path(path); sysio->error.mkdir = Sysio::MKDIR_ERR_NO_ENTRY; /* path does not match directory name */ if (!path) return false; /* * Prevent mkdir of path that equals directory name defined * via the static fstab configuration. */ if (strlen(path) == 0) { sysio->error.mkdir = Sysio::MKDIR_ERR_EXISTS; return false; } /* path refers to any of our sub file systems */ for (File_system *fs = _first_file_system; fs; fs = fs->next) { if (fs->mkdir(sysio, path)) { return true; } else { /* * Keep the most meaningful error code. When using * stacked file systems, most child file systems will * eventually return 'MKDIR_ERR_NO_ENTRY' (or leave * the error code unchanged). If any of those file * systems has anything more interesting to tell, * return this information. */ if (sysio->error.mkdir != Sysio::MKDIR_ERR_NO_ENTRY) return false; } } /* none of our file systems could create the directory */ return false; } /*************************** ** File_system interface ** ***************************/ char const *name() const { return "dir"; } /** * Synchronize all file systems */ void sync() { for (File_system *fs = _first_file_system; fs; fs = fs->next) fs->sync(); } /******************************** ** File I/O service interface ** ********************************/ bool write(Sysio *sysio, Vfs_handle *handle) { return false; } bool read(Sysio *sysio, Vfs_handle *vfs_handle) { return false; } bool ftruncate(Sysio *sysio, Vfs_handle *vfs_handle) { return false; } }; } #endif /* _NOUX__DIR_FILE_SYSTEM_H_ */