#ifndef MERCURY_KERNEL_STORAGE_HPP #define MERCURY_KERNEL_STORAGE_HPP #include #include namespace mercury::kernel::storage { typedef uint64_t node_id_t; typedef uint64_t directory_iter_t; enum file_type { regular_file, directory, symlink }; enum io_result { success, mount_point_already_used, not_a_directory, not_supported, out_of_bounds, device_error, fs_corrupt, not_found, bad_path, }; class file_system_instance { public: virtual ~file_system_instance() {} virtual io_result get_root_node(node_id_t &out) = 0; //get_first_child sets iter_out to a "directory iterator" representing the //first child of this node. get_next_child gets the child after the child //represented by the value of iter passed in, and changes iter to represent //that next child. for an empty directory node, get_first_child returns //io_result::out_of_bounds. when iter represents the last child of a node, //get_next_child also returns io_result::out_of_bounds. virtual io_result get_first_child(node_id_t node, node_id_t &out, directory_iter_t &iter_out) = 0; virtual io_result get_next_child(node_id_t node, node_id_t &out, directory_iter_t &iter) = 0; virtual io_result get_child(node_id_t node, node_id_t &out, const char *name, size_t name_len) = 0; virtual io_result get_name_length(node_id_t node, size_t &length_out) = 0; //buffer is assumed to be long enough - call get_name_length first. virtual io_result get_name(node_id_t node, char *buffer, size_t &length_out) = 0; virtual io_result get_file_length(node_id_t node, uint64_t &length_out) = 0; virtual io_result get_file_type(node_id_t node, file_type &out) = 0; }; class block_device { private: uint8_t *block_cache; uint64_t block_cache_i; bool block_cache_dirty; io_result load_cache_block(uint64_t i); protected: //implemented in driver, bounds not checked virtual io_result read_blocks_no_cache(uint64_t start, uint64_t count, void *into) = 0; virtual io_result write_blocks_no_cache(uint64_t start, uint64_t count, const void *from) = 0; //it is assumed that this is only called once, by the driver, after //block_size and block_count have been set, and before read_bytes or //write_bytes can be called (e.g. in the derived class's constructor) void allocate_block_cache(); public: //set by storage component file_system_instance *mounted_as; utility::uuid id; //set by driver uint64_t block_size; uint64_t block_count; virtual ~block_device() { if (block_cache) delete block_cache; } io_result read_bytes(uint64_t start, uint64_t count, void *into); io_result write_bytes(uint64_t start, uint64_t count, const void *from); }; typedef io_result (*file_system_mounter)(block_device *bd, file_system_instance *&fs_out); extern utility::list *block_devices; void init_storage(); //a canon path contains no . or empty directory names, and //contains no .. except for at the start of a relative path. struct canon_path { bool absolute; unsigned parent_count; utility::vector segments; void parent(); void rel(const canon_path &r); utility::string to_string(bool trailing_slash) const; }; void canonize_path(const char *str, size_t len, canon_path &out); //path must be absolute. io_result mount_device(block_device *bd, const canon_path &path, file_system_mounter mounter); //path must be absolute. symlinks are always resolved on nodes other than the //final one. they are resolved on the final node if and only if resolve final //node is set to true. mount points are always traversed, including on the //final node. this assumes that the path is under some mount point, and the //same for any symlinks traversed (e.g. / is mounted). io_result look_up_absolute_path(const canon_path &path, block_device *&bd_out, node_id_t &node_out, bool resolve_final_node, canon_path &path_without_symlinks_out); } #endif