#include //TODO: handle symlink loops nicely in vfile::get_child, // vfile::get_children, and lookup_path. namespace mercury::kernel::vfile { void canon_path::parent() { if (segments.count != 0) --segments.count; else if (!absolute) ++parent_count; } void canon_path::rel(const canon_path &r) { if (r.absolute) { segments.count = 0; absolute = true; parent_count = 0; } for (unsigned i = 0; i < r.parent_count; ++i) parent(); for (unsigned i = 0; i < r.segments.count; ++i) segments.add_end(r.segments.buffer[i]); } void canonize_path(const utility::string &name, canon_path &out) { out.absolute = false; out.parent_count = 0; out.segments.count = 0; const char *str = name.buffer; unsigned len = name.count; if (len == 0) return; if (len == 1 && str[0] == '/') { out.absolute = true; return; } if (str[0] == '/') { out.absolute = true; ++str; --len; } while (len != 0) { unsigned segment_len = utility::find(str, len, '/'); unsigned to_skip = segment_len == len ? segment_len : segment_len + 1; if (segment_len == 0) ; else if (segment_len == 1 && str[0] == '.') ; else if (segment_len == 2 && str[0] == '.' && str[1] == '.') out.parent(); else { utility::string segment(str, segment_len); out.segments.add_end(std::move(segment)); } str += to_skip; len -= to_skip; } } #define RET_NOT_SUC(expr) \ { \ storage::fs_result _result = expr; \ if (_result != storage::fs_result::success) \ return _result; \ } storage::fs_result vfile::follow_symlinks( const vfile &root, std::optional &out ) const { if (dir_entry.type != storage::file_type::symlink) { out = *this; return storage::fs_result::success; } canon_path target_path; canonize_path(dir_entry.target, target_path); canon_path full_path = path; full_path.parent(); full_path.rel(target_path); std::optional next; RET_NOT_SUC(lookup_path(root, full_path, next)) if (!next) { out = {}; return storage::fs_result::success; } next->path = path; return next->follow_symlinks(root, out); } storage::fs_result vfile::get_child( std::optional &out, const utility::string &name ) const { std::optional entry; storage::directory_iter_t iter; RET_NOT_SUC(bd->mounted_as->get_first_child(dir_entry.node, entry, iter)) while (entry) { if (entry->name == name) { vfile vf; vf.bd = bd; vf.dir_entry = std::move(*entry); vf.path = path; vf.path.segments.add_end(name); out = std::move(vf); return storage::fs_result::success; } RET_NOT_SUC(bd->mounted_as->get_next_child(dir_entry.node, entry, iter)) } out = {}; return storage::fs_result::success; } storage::fs_result vfile::get_children(utility::vector &out) const { std::optional entry; storage::directory_iter_t iter; RET_NOT_SUC(bd->mounted_as->get_first_child(dir_entry.node, entry, iter)) while (entry) { vfile vf; vf.bd = bd; vf.path = path; vf.path.segments.add_end(entry->name); vf.dir_entry = std::move(*entry); out.add_end(std::move(vf)); RET_NOT_SUC(bd->mounted_as->get_next_child(dir_entry.node, entry, iter)) } return storage::fs_result::success; } storage::fs_result lookup_path( const vfile &root, const canon_path &path, std::optional &out ) { //assume path is absolute. out = root; for (unsigned i = 0; i < path.segments.count; ++i) { std::optional result; RET_NOT_SUC(out->follow_symlinks(root, result)) out = std::move(result); if (!out) return storage::fs_result::success; RET_NOT_SUC(out->get_child(result, path.segments.buffer[i])) out = std::move(result); if (!out) return storage::fs_result::success; } return storage::fs_result::success; } }