diff --git a/kernel/arch/aarch64/syscall/linux_syscalls.h b/kernel/arch/aarch64/syscall/linux_syscalls.h index 9a0f88f9..a13b86a3 100644 --- a/kernel/arch/aarch64/syscall/linux_syscalls.h +++ b/kernel/arch/aarch64/syscall/linux_syscalls.h @@ -5,10 +5,13 @@ namespace syscall::linux_nr { +constexpr uint64_t GETCWD = 17; constexpr uint64_t FCNTL = 25; constexpr uint64_t IOCTL = 29; constexpr uint64_t UNLINKAT = 35; constexpr uint64_t FTRUNCATE = 46; +constexpr uint64_t CHDIR = 49; +constexpr uint64_t FCHDIR = 50; constexpr uint64_t OPENAT = 56; constexpr uint64_t CLOSE = 57; constexpr uint64_t GETDENTS64 = 61; diff --git a/kernel/arch/x86_64/syscall/linux_syscalls.h b/kernel/arch/x86_64/syscall/linux_syscalls.h index 0b322ecf..42dafd5c 100644 --- a/kernel/arch/x86_64/syscall/linux_syscalls.h +++ b/kernel/arch/x86_64/syscall/linux_syscalls.h @@ -27,6 +27,9 @@ constexpr uint64_t SOCKETPAIR = 53; constexpr uint64_t EXIT = 60; constexpr uint64_t FCNTL = 72; constexpr uint64_t FTRUNCATE = 77; +constexpr uint64_t GETCWD = 79; +constexpr uint64_t CHDIR = 80; +constexpr uint64_t FCHDIR = 81; constexpr uint64_t ARCH_PRCTL = 158; constexpr uint64_t GETDENTS64 = 217; constexpr uint64_t SET_TID_ADDRESS = 218; diff --git a/kernel/fs/fs.cpp b/kernel/fs/fs.cpp index 10ca3f01..c6ad25be 100644 --- a/kernel/fs/fs.cpp +++ b/kernel/fs/fs.cpp @@ -136,26 +136,92 @@ __PRIVILEGED_CODE static mount_point* find_mount_for_instance(instance* inst) { return nullptr; } -__PRIVILEGED_CODE static int32_t resolve_path(const char* path, node** out) { - if (!path || path[0] != '/') return ERR_INVAL; - if (!g_root_mount || !g_root_instance) return ERR_INVAL; +__PRIVILEGED_CODE static void release_node_ref(node* n) { + if (!n) { + return; + } + if (n->release()) { + node::ref_destroy(n); + } +} + +__PRIVILEGED_CODE static bool is_global_root_node(node* n) { + return g_root_instance && n == g_root_instance->root(); +} + +__PRIVILEGED_CODE static int32_t acquire_global_root(node** out_root) { + if (!out_root || !g_root_mount || !g_root_instance) { + return ERR_INVAL; + } node* cur = g_root_instance->root(); - if (!cur) return ERR_INVAL; + if (!cur) { + return ERR_INVAL; + } + cur->add_ref(); while (cur->mounted_here()) { - cur = cur->mounted_here()->root(); + node* mounted_root = cur->mounted_here()->root(); + mounted_root->add_ref(); + release_node_ref(cur); + cur = mounted_root; } - cur->add_ref(); + *out_root = cur; + return OK; +} + +__PRIVILEGED_CODE static int32_t acquire_start_node( + node* base_dir, const char* path, node** out_start +) { + if (!path || !out_start) { + return ERR_INVAL; + } + + if (path[0] == '/') { + return acquire_global_root(out_start); + } + + node* start = base_dir; + if (!start) { + return acquire_global_root(out_start); + } + + start->add_ref(); + while (start->mounted_here()) { + node* mounted_root = start->mounted_here()->root(); + mounted_root->add_ref(); + release_node_ref(start); + start = mounted_root; + } + + *out_start = start; + return OK; +} + +__PRIVILEGED_CODE static int32_t resolve_path_at_internal( + node* base_dir, const char* path, node** out +) { + if (!path || !out) { + return ERR_INVAL; + } + if (path[0] == '\0') { + return ERR_NOENT; + } + + node* cur = nullptr; + int32_t start_err = acquire_start_node(base_dir, path, &cur); + if (start_err != OK) { + return start_err; + } path_iterator it(path); - const char* comp; - size_t comp_len; + const char* comp = nullptr; + size_t comp_len = 0; while (it.next(comp, comp_len)) { if (comp_len > NAME_MAX) { - if (cur->release()) node::ref_destroy(cur); + release_node_ref(cur); return ERR_NAMETOOLONG; } @@ -165,39 +231,42 @@ __PRIVILEGED_CODE static int32_t resolve_path(const char* path, node** out) { mount_point* mnt = find_mount_for_instance(cur->filesystem()); if (mnt && mnt->mountpoint) { next = mnt->mountpoint->parent(); - if (!next) next = mnt->mountpoint; + if (!next) { + next = mnt->mountpoint; + } } else { next = cur->parent() ? cur->parent() : cur; } } else { next = cur->parent() ? cur->parent() : cur; } + next->add_ref(); - if (cur->release()) node::ref_destroy(cur); + release_node_ref(cur); cur = next; continue; } if (cur->type() != node_type::directory) { - if (cur->release()) node::ref_destroy(cur); + release_node_ref(cur); return ERR_NOTDIR; } node* child = nullptr; int32_t err = cur->lookup(comp, comp_len, &child); if (err != OK) { - if (cur->release()) node::ref_destroy(cur); + release_node_ref(cur); return err; } while (child->mounted_here()) { node* mounted_root = child->mounted_here()->root(); mounted_root->add_ref(); - if (child->release()) node::ref_destroy(child); + release_node_ref(child); child = mounted_root; } - if (cur->release()) node::ref_destroy(cur); + release_node_ref(cur); cur = child; } @@ -205,35 +274,82 @@ __PRIVILEGED_CODE static int32_t resolve_path(const char* path, node** out) { return OK; } -/** - * Resolve parent directory and extract last component name. - * On success, *out_parent has add_ref() called. - * @note Privilege: **required** - */ -__PRIVILEGED_CODE static int32_t resolve_parent( - const char* path, node** out_parent, - const char** out_name, size_t* out_name_len +__PRIVILEGED_CODE static int32_t split_parent_path( + const char* path, + const char** out_name, + size_t* out_name_len, + size_t* out_parent_len +) { + if (!path || !out_name || !out_name_len || !out_parent_len) { + return ERR_INVAL; + } + + size_t len = string::strnlen(path, PATH_MAX); + if (len == 0) { + return ERR_INVAL; + } + + size_t end = len; + while (end > 0 && path[end - 1] == '/') { + end--; + } + if (end == 0) { + return ERR_INVAL; + } + + size_t name_start = end; + while (name_start > 0 && path[name_start - 1] != '/') { + name_start--; + } + + size_t name_len = end - name_start; + if (name_len > NAME_MAX) { + return ERR_NAMETOOLONG; + } + + *out_name = path + name_start; + *out_name_len = name_len; + *out_parent_len = name_start; + return OK; +} + +__PRIVILEGED_CODE static int32_t resolve_parent_at_internal( + node* base_dir, + const char* path, + node** out_parent, + const char** out_name, + size_t* out_name_len ) { - const char* name; - size_t name_len; - size_t parent_len; + const char* name = nullptr; + size_t name_len = 0; + size_t parent_len = 0; - int32_t err = path_parent(path, name, name_len, parent_len); - if (err != OK) return err; + int32_t err = split_parent_path(path, &name, &name_len, &parent_len); + if (err != OK) { + return err; + } - // Resolve the parent path by constructing a temporary null-terminated copy char parent_buf[PATH_MAX]; - if (parent_len >= PATH_MAX) return ERR_NAMETOOLONG; - string::memcpy(parent_buf, path, parent_len); - parent_buf[parent_len] = '\0'; + if (parent_len >= PATH_MAX) { + return ERR_NAMETOOLONG; + } + + if (parent_len == 0) { + parent_buf[0] = '.'; + parent_buf[1] = '\0'; + } else { + string::memcpy(parent_buf, path, parent_len); + parent_buf[parent_len] = '\0'; + } - err = resolve_path(parent_buf, out_parent); - if (err != OK) return err; + err = resolve_path_at_internal(base_dir, parent_buf, out_parent); + if (err != OK) { + return err; + } if ((*out_parent)->type() != node_type::directory) { - if ((*out_parent)->release()) { - node::ref_destroy(*out_parent); - } + release_node_ref(*out_parent); + *out_parent = nullptr; return ERR_NOTDIR; } @@ -242,17 +358,139 @@ __PRIVILEGED_CODE static int32_t resolve_parent( return OK; } +__PRIVILEGED_CODE int32_t path_from_node( + node* target, char* out_path, size_t out_cap +) { + if (!target || !out_path || out_cap == 0) { + return ERR_INVAL; + } + if (!g_root_instance) { + return ERR_INVAL; + } + + char* path_buf = static_cast(heap::kzalloc(PATH_MAX)); + if (!path_buf) { + return ERR_NOMEM; + } + size_t pos = PATH_MAX; + path_buf[--pos] = '\0'; + + node* cur = target; + cur->add_ref(); + + while (true) { + if (is_global_root_node(cur)) { + break; + } + + if (cur->filesystem() && cur == cur->filesystem()->root()) { + mount_point* mnt = find_mount_for_instance(cur->filesystem()); + if (!mnt || !mnt->mountpoint) { + release_node_ref(cur); + heap::kfree(path_buf); + return ERR_NOENT; + } + + node* next = mnt->mountpoint; + next->add_ref(); + release_node_ref(cur); + cur = next; + continue; + } + + const char* name = cur->name(); + size_t name_len = string::strnlen(name, NAME_MAX); + if (name_len == 0) { + release_node_ref(cur); + heap::kfree(path_buf); + return ERR_NOENT; + } + if (pos < name_len + 1) { + release_node_ref(cur); + heap::kfree(path_buf); + return ERR_NAMETOOLONG; + } + + pos -= name_len; + string::memcpy(path_buf + pos, name, name_len); + path_buf[--pos] = '/'; + + node* parent = cur->parent(); + if (!parent) { + release_node_ref(cur); + heap::kfree(path_buf); + return ERR_NOENT; + } + + parent->add_ref(); + release_node_ref(cur); + cur = parent; + } + + release_node_ref(cur); + + if (pos == PATH_MAX - 1) { + if (out_cap < 2) { + heap::kfree(path_buf); + return ERR_NAMETOOLONG; + } + out_path[0] = '/'; + out_path[1] = '\0'; + heap::kfree(path_buf); + return OK; + } + + size_t path_len = PATH_MAX - pos; + if (out_cap < path_len) { + heap::kfree(path_buf); + return ERR_NAMETOOLONG; + } + + string::memcpy(out_path, path_buf + pos, path_len); + heap::kfree(path_buf); + return OK; +} + __PRIVILEGED_CODE int32_t lookup(const char* path, node** out) { - if (!path || !out) return ERR_INVAL; - return resolve_path(path, out); + if (!path || !out) { + return ERR_INVAL; + } + if (path[0] != '/') { + return ERR_INVAL; + } + return resolve_path_at_internal(nullptr, path, out); +} + +__PRIVILEGED_CODE int32_t lookup_at(node* base_dir, const char* path, node** out) { + if (!path || !out) { + return ERR_INVAL; + } + return resolve_path_at_internal(base_dir, path, out); } __PRIVILEGED_CODE int32_t resolve_parent_path( const char* path, node** out_parent, const char** out_name, size_t* out_name_len ) { - if (!path || !out_parent || !out_name || !out_name_len) return ERR_INVAL; - return resolve_parent(path, out_parent, out_name, out_name_len); + if (!path || !out_parent || !out_name || !out_name_len) { + return ERR_INVAL; + } + if (path[0] != '/') { + return ERR_INVAL; + } + return resolve_parent_at_internal( + nullptr, path, out_parent, out_name, out_name_len); +} + +__PRIVILEGED_CODE int32_t resolve_parent_path_at( + node* base_dir, const char* path, node** out_parent, + const char** out_name, size_t* out_name_len +) { + if (!path || !out_parent || !out_name || !out_name_len) { + return ERR_INVAL; + } + return resolve_parent_at_internal( + base_dir, path, out_parent, out_name, out_name_len); } @@ -299,7 +537,7 @@ __PRIVILEGED_CODE int32_t mount(const char* source, const char* target, // Non-root mount: resolve target node* target_node = nullptr; - err = resolve_path(target, &target_node); + err = lookup(target, &target_node); if (err != OK) { heap::kfree_delete(inst); return err; @@ -368,7 +606,8 @@ file* open(const char* path, uint32_t flags, int32_t* out_err) { const char* name; size_t name_len; - err = resolve_parent(path, &parent, &name, &name_len); + err = resolve_parent_at_internal( + nullptr, path, &parent, &name, &name_len); if (err == OK) { err = parent->lookup(name, name_len, &n); if (err == ERR_NOENT) { @@ -382,7 +621,7 @@ file* open(const char* path, uint32_t flags, int32_t* out_err) { } } } else { - err = resolve_path(path, &n); + err = lookup(path, &n); } }); @@ -484,7 +723,7 @@ int32_t stat(const char* path, vattr* attr) { int32_t err; RUN_ELEVATED({ - err = resolve_path(path, &n); + err = lookup(path, &n); if (err == OK) { err = n->getattr(attr); if (n->release()) { @@ -514,7 +753,8 @@ int32_t mkdir(const char* path, uint32_t mode) { const char* name; size_t name_len; - err = resolve_parent(path, &parent, &name, &name_len); + err = resolve_parent_at_internal( + nullptr, path, &parent, &name, &name_len); if (err == OK) { node* child = nullptr; err = parent->mkdir(name, name_len, mode, &child); @@ -540,7 +780,8 @@ int32_t rmdir(const char* path) { const char* name; size_t name_len; - err = resolve_parent(path, &parent, &name, &name_len); + err = resolve_parent_at_internal( + nullptr, path, &parent, &name, &name_len); if (err == OK) { err = parent->rmdir(name, name_len); if (parent->release()) { @@ -560,7 +801,8 @@ int32_t unlink(const char* path) { const char* name; size_t name_len; - err = resolve_parent(path, &parent, &name, &name_len); + err = resolve_parent_at_internal( + nullptr, path, &parent, &name, &name_len); if (err == OK) { err = parent->unlink(name, name_len); if (parent->release()) { diff --git a/kernel/fs/fs.h b/kernel/fs/fs.h index 2dcb24c6..96df8b44 100644 --- a/kernel/fs/fs.h +++ b/kernel/fs/fs.h @@ -68,6 +68,34 @@ int32_t rmdir(const char* path); int32_t unlink(const char* path); ssize_t readdir(file* f, dirent* entries, size_t count); +/** + * @brief Resolve path relative to base_dir when path is not absolute. + * If path is absolute, base_dir is ignored. + * On success, *out has add_ref() called; caller must release. + * @note Privilege: **required** + */ +__PRIVILEGED_CODE int32_t lookup_at(node* base_dir, const char* path, node** out); + +/** + * @brief Resolve parent directory of path relative to base_dir. + * If path is absolute, base_dir is ignored. + * On success, *out_parent has add_ref() called; caller must release. + * @note Privilege: **required** + */ +__PRIVILEGED_CODE int32_t resolve_parent_path_at( + node* base_dir, const char* path, node** out_parent, + const char** out_name, size_t* out_name_len +); + +/** + * @brief Reconstruct absolute path for an existing node. + * Writes a null-terminated absolute path into out_path. + * @note Privilege: **required** + */ +__PRIVILEGED_CODE int32_t path_from_node( + node* target, char* out_path, size_t out_cap +); + /** * @brief Resolve a path to a node. * On success, the returned node has add_ref() called. diff --git a/kernel/fs/path.cpp b/kernel/fs/path.cpp index 5bf685ed..5b678129 100644 --- a/kernel/fs/path.cpp +++ b/kernel/fs/path.cpp @@ -1,5 +1,4 @@ #include "fs/path.h" -#include "fs/fs.h" #include "common/string.h" namespace fs { @@ -36,47 +35,4 @@ bool path_iterator::next(const char*& out_name, size_t& out_len) { } } -int32_t path_parent(const char* path, const char*& out_name, - size_t& out_name_len, size_t& out_parent_len) { - if (!path || path[0] != '/') { - return ERR_INVAL; - } - - size_t len = string::strnlen(path, PATH_MAX); - if (len == 0) { - return ERR_INVAL; - } - - // Find the last slash (ignoring trailing slashes) - size_t end = len; - while (end > 0 && path[end - 1] == '/') { - end--; - } - if (end == 0) { - return ERR_INVAL; // path is only slashes, no component - } - - // Find start of last component - size_t name_start = end; - while (name_start > 0 && path[name_start - 1] != '/') { - name_start--; - } - - out_name = path + name_start; - out_name_len = end - name_start; - - if (out_name_len > NAME_MAX) { - return ERR_NAMETOOLONG; - } - - // Parent is everything up to (but not including) the last component - // But keep at least one '/' for the root case - out_parent_len = name_start; - if (out_parent_len == 0) { - out_parent_len = 1; // parent is "/" - } - - return OK; -} - } // namespace fs diff --git a/kernel/fs/path.h b/kernel/fs/path.h index b40769ac..3879f7e0 100644 --- a/kernel/fs/path.h +++ b/kernel/fs/path.h @@ -36,19 +36,6 @@ class path_iterator { size_t m_len; }; -/** - * Extract the parent path and final component from a full path. - * E.g. "/foo/bar/baz" -> parent_end=8 (points past "/foo/bar"), name="baz", name_len=3. - * - * @param path Full absolute path. - * @param out_name Pointer to the last component within path. - * @param out_name_len Length of the last component. - * @param out_parent_len Length of the parent portion (including leading /). - * @return OK on success, ERR_INVAL if path is empty or has no components. - */ -int32_t path_parent(const char* path, const char*& out_name, - size_t& out_name_len, size_t& out_parent_len); - } // namespace fs #endif // STELLUX_FS_PATH_H diff --git a/kernel/resource/providers/proc_provider.cpp b/kernel/resource/providers/proc_provider.cpp index 9888450c..efbbdf7f 100644 --- a/kernel/resource/providers/proc_provider.cpp +++ b/kernel/resource/providers/proc_provider.cpp @@ -4,6 +4,7 @@ #include "mm/vma.h" #include "mm/vmm.h" #include "mm/heap.h" +#include "fs/node.h" #include "common/logging.h" namespace resource::proc_provider { @@ -126,6 +127,12 @@ __PRIVILEGED_CODE proc_resource* get_proc_resource(resource_object* obj) { __PRIVILEGED_CODE void destroy_unstarted_task(sched::task* t) { resource::close_all(t); + if (t->cwd) { + if (t->cwd->release()) { + fs::node::ref_destroy(t->cwd); + } + t->cwd = nullptr; + } if (t->exec.mm_ctx) { mm::mm_context_release(t->exec.mm_ctx); diff --git a/kernel/sched/sched.cpp b/kernel/sched/sched.cpp index 46aeeba4..a35f297a 100644 --- a/kernel/sched/sched.cpp +++ b/kernel/sched/sched.cpp @@ -23,6 +23,7 @@ #include "common/string.h" #include "resource/resource.h" #include "resource/providers/proc_provider.h" +#include "fs/node.h" DEFINE_PER_CPU(sched::task*, current_task); DEFINE_PER_CPU(bool, percpu_is_elevated); @@ -137,6 +138,12 @@ __PRIVILEGED_CODE static rc::reaper::cleanup_result reap_task(sched::task* t) { } resource::close_all(t); + if (t->cwd) { + if (t->cwd->release()) { + fs::node::ref_destroy(t->cwd); + } + t->cwd = nullptr; + } if (t->exec.mm_ctx) { mm::mm_context_release(t->exec.mm_ctx); @@ -461,6 +468,7 @@ __PRIVILEGED_CODE task* create_kernel_task( t->reaper_node.init(reap_task_thunk); resource::init_task_handles(t); t->proc_res = nullptr; + t->cwd = nullptr; return t; } @@ -657,6 +665,7 @@ __PRIVILEGED_CODE task* create_user_task( resource::init_task_handles(t); t->proc_res = nullptr; + t->cwd = nullptr; image->mm_ctx = nullptr; image->pt_root = 0; @@ -702,6 +711,7 @@ __PRIVILEGED_CODE int32_t init() { fpu::init_state(&idle->exec.fpu_ctx); resource::init_task_handles(idle); idle->proc_res = nullptr; + idle->cwd = nullptr; this_cpu(current_task) = idle; this_cpu(current_task_exec) = &idle->exec; @@ -762,6 +772,8 @@ __PRIVILEGED_CODE int32_t init_ap(uint32_t cpu_id, uintptr_t task_stack_top, idle->tlb_sync_ticket.armed = 0; fpu::init_state(&idle->exec.fpu_ctx); resource::init_task_handles(idle); + idle->proc_res = nullptr; + idle->cwd = nullptr; this_cpu(current_task) = idle; this_cpu(current_task_exec) = &idle->exec; diff --git a/kernel/sched/task.h b/kernel/sched/task.h index ad4559d0..e8b0c966 100644 --- a/kernel/sched/task.h +++ b/kernel/sched/task.h @@ -7,6 +7,7 @@ #include "resource/handle_table.h" namespace resource::proc_provider { struct proc_resource; } +namespace fs { class node; } namespace sched { @@ -52,6 +53,7 @@ struct task { rc::reaper::dead_node reaper_node; resource::handle_table handles; resource::proc_provider::proc_resource* proc_res; + fs::node* cwd; }; // Assembly accesses task_exec_core fields via offsets from the task pointer. diff --git a/kernel/syscall/handlers/sys_error_map.h b/kernel/syscall/handlers/sys_error_map.h new file mode 100644 index 00000000..67980d92 --- /dev/null +++ b/kernel/syscall/handlers/sys_error_map.h @@ -0,0 +1,43 @@ +#ifndef STELLUX_SYSCALL_HANDLERS_SYS_ERROR_MAP_H +#define STELLUX_SYSCALL_HANDLERS_SYS_ERROR_MAP_H + +#include "syscall/syscall_table.h" +#include "fs/fs.h" + +namespace syscall::error_map { + +inline int64_t map_fs_error(int32_t rc) { + switch (rc) { + case fs::ERR_NOENT: + return syscall::ENOENT; + case fs::ERR_EXIST: + return syscall::EEXIST; + case fs::ERR_NOTDIR: + return syscall::ENOTDIR; + case fs::ERR_ISDIR: + return syscall::EISDIR; + case fs::ERR_NOMEM: + return syscall::ENOMEM; + case fs::ERR_INVAL: + return syscall::EINVAL; + case fs::ERR_NAMETOOLONG: + return syscall::ENAMETOOLONG; + case fs::ERR_NOTEMPTY: + return syscall::ENOTEMPTY; + case fs::ERR_NOSYS: + return syscall::ENOSYS; + case fs::ERR_BUSY: + return syscall::EBUSY; + case fs::ERR_LOOP: + return syscall::ELOOP; + case fs::ERR_BADF: + return syscall::EBADF; + case fs::ERR_IO: + default: + return syscall::EIO; + } +} + +} // namespace syscall::error_map + +#endif // STELLUX_SYSCALL_HANDLERS_SYS_ERROR_MAP_H diff --git a/kernel/syscall/handlers/sys_fd.cpp b/kernel/syscall/handlers/sys_fd.cpp index d80e10f2..b994aa2e 100644 --- a/kernel/syscall/handlers/sys_fd.cpp +++ b/kernel/syscall/handlers/sys_fd.cpp @@ -4,6 +4,7 @@ #include "resource/providers/file_provider.h" #include "resource/providers/shmem_resource_provider.h" #include "resource/providers/shm_provider.h" +#include "syscall/handlers/sys_error_map.h" #include "sched/sched.h" #include "sched/task.h" #include "mm/uaccess.h" @@ -136,38 +137,6 @@ inline int64_t map_resource_error(int64_t rc) { } } -inline int64_t map_fs_error(int32_t rc) { - switch (rc) { - case fs::ERR_NOENT: - return syscall::ENOENT; - case fs::ERR_EXIST: - return syscall::EEXIST; - case fs::ERR_NOTDIR: - return syscall::ENOTDIR; - case fs::ERR_ISDIR: - return syscall::EISDIR; - case fs::ERR_NOMEM: - return syscall::ENOMEM; - case fs::ERR_INVAL: - return syscall::EINVAL; - case fs::ERR_NAMETOOLONG: - return syscall::ENAMETOOLONG; - case fs::ERR_NOTEMPTY: - return syscall::ENOTEMPTY; - case fs::ERR_NOSYS: - return syscall::ENOSYS; - case fs::ERR_BUSY: - return syscall::EBUSY; - case fs::ERR_LOOP: - return syscall::ELOOP; - case fs::ERR_BADF: - return syscall::EBADF; - case fs::ERR_IO: - default: - return syscall::EIO; - } -} - inline uint8_t node_type_to_dirent_type(fs::node_type t) { switch (t) { case fs::node_type::regular: @@ -238,6 +207,350 @@ inline int64_t copy_stat_to_user(const fs::vattr& attr, uint64_t u_stat) { return 0; } +inline void release_node_ref(fs::node* n) { + if (!n) { + return; + } + if (n->release()) { + fs::node::ref_destroy(n); + } +} + +int64_t acquire_task_cwd_node(sched::task* task, fs::node** out_node) { + if (!task || !out_node) { + return syscall::EINVAL; + } + if (task->cwd) { + task->cwd->add_ref(); + *out_node = task->cwd; + return 0; + } + + fs::node* root = nullptr; + int32_t fs_rc = fs::lookup("/", &root); + if (fs_rc != fs::OK) { + return syscall::error_map::map_fs_error(fs_rc); + } + *out_node = root; + return 0; +} + +int64_t replace_task_cwd_node(sched::task* task, fs::node* new_cwd) { + if (!task || !new_cwd) { + return syscall::EINVAL; + } + if (new_cwd->type() != fs::node_type::directory) { + return syscall::ENOTDIR; + } + + new_cwd->add_ref(); + fs::node* old = task->cwd; + task->cwd = new_cwd; + release_node_ref(old); + return 0; +} + +int64_t resolve_dirfd_base_node( + sched::task* task, int64_t dirfd, fs::node** out_base +) { + if (!task || !out_base) { + return syscall::EINVAL; + } + + if (dirfd == AT_FDCWD) { + return acquire_task_cwd_node(task, out_base); + } + + if (dirfd < 0) { + return syscall::EBADF; + } + + resource::resource_object* obj = nullptr; + int32_t rc = resource::get_handle_object( + &task->handles, static_cast(dirfd), 0, &obj); + if (rc != resource::HANDLE_OK) { + return syscall::EBADF; + } + + if (obj->type != resource::resource_type::FILE) { + resource::resource_release(obj); + return syscall::ENOTDIR; + } + + fs::file* kfile = resource::file_provider::get_file(obj); + if (!kfile || !kfile->get_node()) { + resource::resource_release(obj); + return syscall::EIO; + } + + fs::node* dir = kfile->get_node(); + if (dir->type() != fs::node_type::directory) { + resource::resource_release(obj); + return syscall::ENOTDIR; + } + + dir->add_ref(); + resource::resource_release(obj); + *out_base = dir; + return 0; +} + +int64_t normalize_absolute_path( + const char* base_abs, const char* input_path, + char* out_path, size_t out_cap +) { + if (!input_path || !out_path || out_cap < 2) { + return syscall::EINVAL; + } + out_path[0] = '/'; + out_path[1] = '\0'; + size_t out_len = 1; + + auto pop_component = [&]() { + if (out_len <= 1) { + return; + } + + while (out_len > 1 && out_path[out_len - 1] == '/') { + out_len--; + } + while (out_len > 1 && out_path[out_len - 1] != '/') { + out_len--; + } + if (out_len > 1 && out_path[out_len - 1] == '/') { + out_len--; + } + + if (out_len == 0) { + out_len = 1; + out_path[0] = '/'; + } + out_path[out_len] = '\0'; + }; + + auto append_component = [&](const char* src, size_t len) -> int64_t { + if (len > fs::NAME_MAX) { + return syscall::ENAMETOOLONG; + } + + if (out_len > 1) { + if (out_len + 1 >= out_cap) { + return syscall::ENAMETOOLONG; + } + out_path[out_len++] = '/'; + out_path[out_len] = '\0'; + } + + if (out_len + len >= out_cap) { + return syscall::ENAMETOOLONG; + } + string::memcpy(out_path + out_len, src, len); + out_len += len; + out_path[out_len] = '\0'; + return 0; + }; + + auto consume = [&](const char* src) -> int64_t { + size_t pos = 0; + while (src[pos] != '\0') { + while (src[pos] == '/') { + pos++; + } + if (src[pos] == '\0') { + break; + } + + size_t start = pos; + while (src[pos] != '\0' && src[pos] != '/') { + pos++; + } + size_t len = pos - start; + if (len == 0 || (len == 1 && src[start] == '.')) { + continue; + } + if (len == 2 && src[start] == '.' && src[start + 1] == '.') { + pop_component(); + continue; + } + + int64_t append_rc = append_component(src + start, len); + if (append_rc != 0) { + return append_rc; + } + } + return 0; + }; + + if (input_path[0] != '/') { + if (!base_abs || base_abs[0] != '/') { + return syscall::EINVAL; + } + int64_t base_rc = consume(base_abs); + if (base_rc != 0) { + return base_rc; + } + } + + int64_t path_rc = consume(input_path); + if (path_rc != 0) { + return path_rc; + } + + return 0; +} + +int64_t normalize_path_for_dirfd( + sched::task* task, int64_t dirfd, + const char* input_path, char* out_path, size_t out_cap +) { + if (!task || !input_path || !out_path || out_cap == 0) { + return syscall::EINVAL; + } + + if (input_path[0] == '/') { + return normalize_absolute_path(nullptr, input_path, out_path, out_cap); + } + + fs::node* base_node = nullptr; + int64_t base_rc = resolve_dirfd_base_node(task, dirfd, &base_node); + if (base_rc != 0) { + return base_rc; + } + + char* base_path = static_cast(heap::kzalloc(fs::PATH_MAX)); + if (!base_path) { + release_node_ref(base_node); + return syscall::ENOMEM; + } + + int32_t base_path_rc = fs::path_from_node(base_node, base_path, fs::PATH_MAX); + release_node_ref(base_node); + if (base_path_rc != fs::OK) { + heap::kfree(base_path); + return syscall::error_map::map_fs_error(base_path_rc); + } + + int64_t norm_rc = normalize_absolute_path(base_path, input_path, out_path, out_cap); + heap::kfree(base_path); + return norm_rc; +} + +int64_t lookup_node_for_dirfd_path( + sched::task* task, + int64_t dirfd, + const char* input_path, + fs::node** out_node +) { + if (!task || !input_path || !out_node) { + return syscall::EINVAL; + } + + fs::node* base = nullptr; + if (input_path[0] != '/') { + int64_t base_rc = resolve_dirfd_base_node(task, dirfd, &base); + if (base_rc != 0) { + return base_rc; + } + } + + int32_t fs_rc = fs::lookup_at(base, input_path, out_node); + release_node_ref(base); + if (fs_rc != fs::OK) { + return syscall::error_map::map_fs_error(fs_rc); + } + + return 0; +} + +int64_t resolve_parent_for_dirfd_path( + sched::task* task, + int64_t dirfd, + const char* input_path, + fs::node** out_parent, + const char** out_name, + size_t* out_name_len +) { + if (!task || !input_path || !out_parent || !out_name || !out_name_len) { + return syscall::EINVAL; + } + + fs::node* base = nullptr; + if (input_path[0] != '/') { + int64_t base_rc = resolve_dirfd_base_node(task, dirfd, &base); + if (base_rc != 0) { + return base_rc; + } + } + + int32_t fs_rc = fs::resolve_parent_path_at( + base, input_path, out_parent, out_name, out_name_len); + release_node_ref(base); + if (fs_rc != fs::OK) { + return syscall::error_map::map_fs_error(fs_rc); + } + + return 0; +} + +int64_t resolve_open_resource_path( + sched::task* task, + int64_t dirfd, + const char* input_path, + uint32_t open_flags, + char* out_path, + size_t out_cap +) { + if (!task || !input_path || !out_path || out_cap < 2) { + return syscall::EINVAL; + } + + if ((open_flags & fs::O_CREAT) != 0) { + fs::node* parent = nullptr; + const char* name = nullptr; + size_t name_len = 0; + int64_t parent_rc = resolve_parent_for_dirfd_path( + task, dirfd, input_path, &parent, &name, &name_len); + if (parent_rc != 0) { + return parent_rc; + } + + int32_t parent_path_rc = fs::path_from_node(parent, out_path, out_cap); + release_node_ref(parent); + if (parent_path_rc != fs::OK) { + return syscall::error_map::map_fs_error(parent_path_rc); + } + + size_t parent_len = string::strnlen(out_path, out_cap); + bool need_sep = !(parent_len == 1 && out_path[0] == '/'); + size_t needed = parent_len + (need_sep ? 1 : 0) + name_len + 1; + if (needed > out_cap) { + return syscall::ENAMETOOLONG; + } + + size_t pos = parent_len; + if (need_sep) { + out_path[pos++] = '/'; + } + string::memcpy(out_path + pos, name, name_len); + out_path[pos + name_len] = '\0'; + return 0; + } + + fs::node* target = nullptr; + int64_t lookup_rc = lookup_node_for_dirfd_path(task, dirfd, input_path, &target); + if (lookup_rc != 0) { + return lookup_rc; + } + + int32_t path_rc = fs::path_from_node(target, out_path, out_cap); + release_node_ref(target); + if (path_rc != fs::OK) { + return syscall::error_map::map_fs_error(path_rc); + } + + return 0; +} + int64_t do_fstat_common(int64_t fd, uint64_t u_stat) { if (u_stat == 0) { return syscall::EFAULT; @@ -265,7 +578,7 @@ int64_t do_fstat_common(int64_t fd, uint64_t u_stat) { int32_t fs_rc = fs::fstat(kfile, &attr); resource::resource_release(obj); if (fs_rc != fs::OK) { - return map_fs_error(fs_rc); + return syscall::error_map::map_fs_error(fs_rc); } return copy_stat_to_user(attr, u_stat); } @@ -318,20 +631,48 @@ int64_t do_newfstatat_common(int64_t dirfd, uint64_t pathname, uint64_t u_stat, if ((flags & AT_EMPTY_PATH) == 0) { return syscall::ENOENT; } + if (dirfd == AT_FDCWD) { + sched::task* task = sched::current(); + if (!task) { + return syscall::EIO; + } + + fs::node* cwd = nullptr; + int64_t cwd_rc = acquire_task_cwd_node(task, &cwd); + if (cwd_rc != 0) { + return cwd_rc; + } + + fs::vattr attr = {}; + int32_t fs_rc = cwd->getattr(&attr); + release_node_ref(cwd); + if (fs_rc != fs::OK) { + return syscall::error_map::map_fs_error(fs_rc); + } + return copy_stat_to_user(attr, u_stat); + } if (dirfd < 0) { return syscall::EBADF; } return do_fstat_common(dirfd, u_stat); } - if (kpath[0] != '/') { - return syscall::EINVAL; + sched::task* task = sched::current(); + if (!task) { + return syscall::EIO; + } + + fs::node* target = nullptr; + int64_t lookup_rc = lookup_node_for_dirfd_path(task, dirfd, kpath, &target); + if (lookup_rc != 0) { + return lookup_rc; } fs::vattr attr = {}; - int32_t fs_rc = fs::stat(kpath, &attr); + int32_t fs_rc = target->getattr(&attr); + release_node_ref(target); if (fs_rc != fs::OK) { - return map_fs_error(fs_rc); + return syscall::error_map::map_fs_error(fs_rc); } return copy_stat_to_user(attr, u_stat); @@ -340,10 +681,6 @@ int64_t do_newfstatat_common(int64_t dirfd, uint64_t pathname, uint64_t u_stat, int64_t do_open_common(int64_t dirfd, uint64_t pathname, uint64_t flags, uint64_t mode) { (void)mode; - if (dirfd != AT_FDCWD) { - return syscall::EINVAL; - } - char kpath[fs::PATH_MAX]; int32_t copy_rc = mm::uaccess::copy_cstr_from_user( kpath, @@ -357,8 +694,8 @@ int64_t do_open_common(int64_t dirfd, uint64_t pathname, uint64_t flags, uint64_ return syscall::EFAULT; } - if (kpath[0] != '/') { - return syscall::EINVAL; + if (kpath[0] == '\0') { + return syscall::ENOENT; } sched::task* task = sched::current(); @@ -366,13 +703,58 @@ int64_t do_open_common(int64_t dirfd, uint64_t pathname, uint64_t flags, uint64_ return syscall::EIO; } + uint32_t open_flags = static_cast(flags); + char* resolved_path = static_cast(heap::kzalloc(fs::PATH_MAX)); + if (!resolved_path) { + return syscall::ENOMEM; + } + + const char* path_for_open = nullptr; + if (kpath[0] == '/') { + if (resource::shm_provider::is_shm_path(kpath)) { + int64_t norm_rc = normalize_absolute_path( + nullptr, kpath, resolved_path, fs::PATH_MAX); + if (norm_rc != 0) { + heap::kfree(resolved_path); + return norm_rc; + } + path_for_open = resolved_path; + } else { + int64_t resolve_rc = resolve_open_resource_path( + task, dirfd, kpath, open_flags, resolved_path, fs::PATH_MAX); + if (resolve_rc != 0) { + heap::kfree(resolved_path); + return resolve_rc; + } + path_for_open = resolved_path; + } + } else { + int64_t norm_rc = normalize_path_for_dirfd( + task, dirfd, kpath, resolved_path, fs::PATH_MAX); + if (norm_rc != 0) { + heap::kfree(resolved_path); + return norm_rc; + } + + if (!resource::shm_provider::is_shm_path(resolved_path)) { + int64_t resolve_rc = resolve_open_resource_path( + task, dirfd, kpath, open_flags, resolved_path, fs::PATH_MAX); + if (resolve_rc != 0) { + heap::kfree(resolved_path); + return resolve_rc; + } + } + path_for_open = resolved_path; + } + resource::handle_t handle = -1; int32_t rc = resource::open( task, - kpath, - static_cast(flags), + path_for_open, + open_flags, &handle ); + heap::kfree(resolved_path); if (rc != resource::OK) { return map_resource_error(rc); } @@ -532,6 +914,105 @@ DEFINE_SYSCALL4(newfstatat, dirfd, pathname, statbuf, flags) { static_cast(dirfd), pathname, statbuf, flags); } +DEFINE_SYSCALL2(getcwd, buf, size) { + sched::task* task = sched::current(); + if (!task) { + return syscall::EIO; + } + + fs::node* cwd = nullptr; + int64_t cwd_rc = acquire_task_cwd_node(task, &cwd); + if (cwd_rc != 0) { + return cwd_rc; + } + + char cwd_path[fs::PATH_MAX]; + int32_t path_rc = fs::path_from_node(cwd, cwd_path, sizeof(cwd_path)); + release_node_ref(cwd); + if (path_rc != fs::OK) { + return syscall::error_map::map_fs_error(path_rc); + } + + size_t required = string::strnlen(cwd_path, sizeof(cwd_path)) + 1; + if (size == 0 || size < required) { + return syscall::ERANGE; + } + if (buf == 0) { + return syscall::EFAULT; + } + + int32_t copy_rc = mm::uaccess::copy_to_user( + reinterpret_cast(buf), cwd_path, required); + if (copy_rc != mm::uaccess::OK) { + return syscall::EFAULT; + } + + return static_cast(required); +} + +DEFINE_SYSCALL1(chdir, pathname) { + if (pathname == 0) { + return syscall::EFAULT; + } + + char kpath[fs::PATH_MAX]; + int32_t copy_rc = mm::uaccess::copy_cstr_from_user( + kpath, sizeof(kpath), reinterpret_cast(pathname)); + if (copy_rc != mm::uaccess::OK) { + if (copy_rc == mm::uaccess::ERR_NAMETOOLONG) { + return syscall::ENAMETOOLONG; + } + return syscall::EFAULT; + } + if (kpath[0] == '\0') { + return syscall::ENOENT; + } + + sched::task* task = sched::current(); + if (!task) { + return syscall::EIO; + } + + fs::node* base = nullptr; + int64_t base_rc = acquire_task_cwd_node(task, &base); + if (base_rc != 0) { + return base_rc; + } + + fs::node* target = nullptr; + int32_t fs_rc = fs::lookup_at(base, kpath, &target); + release_node_ref(base); + if (fs_rc != fs::OK) { + return syscall::error_map::map_fs_error(fs_rc); + } + + int64_t set_rc = replace_task_cwd_node(task, target); + release_node_ref(target); + return set_rc; +} + +DEFINE_SYSCALL1(fchdir, fd) { + sched::task* task = sched::current(); + if (!task) { + return syscall::EIO; + } + + int64_t dirfd = static_cast(fd); + if (dirfd == AT_FDCWD) { + return syscall::EBADF; + } + + fs::node* dir = nullptr; + int64_t dir_rc = resolve_dirfd_base_node(task, dirfd, &dir); + if (dir_rc != 0) { + return dir_rc; + } + + int64_t set_rc = replace_task_cwd_node(task, dir); + release_node_ref(dir); + return set_rc; +} + DEFINE_SYSCALL3(getdents64, fd, dirp, count) { if (dirp == 0) { return syscall::EFAULT; @@ -594,7 +1075,7 @@ DEFINE_SYSCALL3(getdents64, fd, dirp, count) { if (bytes_written > 0) { return static_cast(bytes_written); } - return map_fs_error(static_cast(nread)); + return syscall::error_map::map_fs_error(static_cast(nread)); } if (nread == 0) { break; @@ -682,10 +1163,6 @@ DEFINE_SYSCALL3(fcntl, fd, cmd, arg) { DEFINE_SYSCALL3(unlinkat, dirfd, pathname, flags_val) { (void)flags_val; - if (static_cast(dirfd) != AT_FDCWD) { - return syscall::EINVAL; - } - char kpath[fs::PATH_MAX]; int32_t copy_rc = mm::uaccess::copy_cstr_from_user( kpath, sizeof(kpath), @@ -697,21 +1174,80 @@ DEFINE_SYSCALL3(unlinkat, dirfd, pathname, flags_val) { return syscall::EFAULT; } - if (kpath[0] != '/') { - return syscall::EINVAL; + if (kpath[0] == '\0') { + return syscall::ENOENT; + } + + sched::task* task = sched::current(); + if (!task) { + return syscall::EIO; } - if (resource::shm_provider::is_shm_path(kpath)) { - int32_t rc = resource::shm_provider::unlink_shm(kpath); + char* normalized_path = nullptr; + const char* shm_path = nullptr; + if (kpath[0] == '/') { + if (resource::shm_provider::is_shm_path(kpath)) { + normalized_path = static_cast(heap::kzalloc(fs::PATH_MAX)); + if (!normalized_path) { + return syscall::ENOMEM; + } + + int64_t norm_rc = normalize_absolute_path( + nullptr, kpath, normalized_path, fs::PATH_MAX); + if (norm_rc != 0) { + heap::kfree(normalized_path); + return norm_rc; + } + shm_path = normalized_path; + } + } else { + normalized_path = static_cast(heap::kzalloc(fs::PATH_MAX)); + if (!normalized_path) { + return syscall::ENOMEM; + } + + int64_t norm_rc = normalize_path_for_dirfd( + task, static_cast(dirfd), kpath, + normalized_path, fs::PATH_MAX); + if (norm_rc != 0) { + heap::kfree(normalized_path); + return norm_rc; + } + + if (resource::shm_provider::is_shm_path(normalized_path)) { + shm_path = normalized_path; + } + } + + if (shm_path) { + int32_t rc = resource::shm_provider::unlink_shm(shm_path); + if (normalized_path) { + heap::kfree(normalized_path); + } if (rc != resource::OK) { return map_resource_error(rc); } return 0; } - int32_t rc = fs::unlink(kpath); + if (normalized_path) { + heap::kfree(normalized_path); + } + + fs::node* parent = nullptr; + const char* name = nullptr; + size_t name_len = 0; + int64_t parent_rc = resolve_parent_for_dirfd_path( + task, static_cast(dirfd), kpath, + &parent, &name, &name_len); + if (parent_rc != 0) { + return parent_rc; + } + + int32_t rc = parent->unlink(name, name_len); + release_node_ref(parent); if (rc != fs::OK) { - return map_fs_error(rc); + return syscall::error_map::map_fs_error(rc); } return 0; } diff --git a/kernel/syscall/handlers/sys_fd.h b/kernel/syscall/handlers/sys_fd.h index 58cc370f..086c5206 100644 --- a/kernel/syscall/handlers/sys_fd.h +++ b/kernel/syscall/handlers/sys_fd.h @@ -14,5 +14,8 @@ DECLARE_SYSCALL(newfstatat); DECLARE_SYSCALL(getdents64); DECLARE_SYSCALL(fcntl); DECLARE_SYSCALL(unlinkat); +DECLARE_SYSCALL(getcwd); +DECLARE_SYSCALL(chdir); +DECLARE_SYSCALL(fchdir); #endif // STELLUX_SYSCALL_HANDLERS_SYS_FD_H diff --git a/kernel/syscall/handlers/sys_proc.cpp b/kernel/syscall/handlers/sys_proc.cpp index 9f79ae02..0800f851 100644 --- a/kernel/syscall/handlers/sys_proc.cpp +++ b/kernel/syscall/handlers/sys_proc.cpp @@ -1,4 +1,5 @@ #include "syscall/handlers/sys_proc.h" +#include "syscall/handlers/sys_error_map.h" #include "resource/providers/proc_provider.h" #include "resource/resource.h" #include "resource/handle_table.h" @@ -6,6 +7,8 @@ #include "sched/task.h" #include "exec/elf.h" #include "mm/uaccess.h" +#include "fs/fs.h" +#include "fs/node.h" #include "fs/fstypes.h" #include "common/string.h" @@ -47,6 +50,11 @@ __PRIVILEGED_CODE static int64_t map_elf_error(int32_t rc) { } // anonymous namespace DEFINE_SYSCALL2(proc_create, u_path, u_argv) { + sched::task* caller = sched::current(); + if (!caller) { + return syscall::EIO; + } + char kpath[fs::PATH_MAX]; int32_t copy_rc = mm::uaccess::copy_cstr_from_user( kpath, sizeof(kpath), @@ -116,6 +124,20 @@ DEFINE_SYSCALL2(proc_create, u_path, u_argv) { return syscall::ENOMEM; } + fs::node* inherited_cwd = nullptr; + int32_t cwd_rc = fs::OK; + if (caller->cwd) { + caller->cwd->add_ref(); + inherited_cwd = caller->cwd; + } else { + cwd_rc = fs::lookup("/", &inherited_cwd); + } + if (cwd_rc != fs::OK || !inherited_cwd) { + resource::proc_provider::destroy_unstarted_task(child); + return syscall::error_map::map_fs_error(cwd_rc); + } + child->cwd = inherited_cwd; + resource::resource_object* obj = nullptr; int32_t pr_rc = resource::proc_provider::create_proc_resource(child, &obj); if (pr_rc != resource::OK) { @@ -123,7 +145,6 @@ DEFINE_SYSCALL2(proc_create, u_path, u_argv) { return syscall::ENOMEM; } - sched::task* caller = sched::current(); resource::handle_t handle = -1; int32_t h_rc = resource::alloc_handle( &caller->handles, obj, resource::resource_type::PROCESS, 0, &handle); diff --git a/kernel/syscall/syscall_table.cpp b/kernel/syscall/syscall_table.cpp index 226c4d9b..fa1bf1b4 100644 --- a/kernel/syscall/syscall_table.cpp +++ b/kernel/syscall/syscall_table.cpp @@ -26,6 +26,9 @@ __PRIVILEGED_CODE void init_syscall_table() { REGISTER_SYSCALL(linux_nr::FSTAT, fstat); REGISTER_SYSCALL(linux_nr::NEWFSTATAT, newfstatat); REGISTER_SYSCALL(linux_nr::GETDENTS64, getdents64); + REGISTER_SYSCALL(linux_nr::GETCWD, getcwd); + REGISTER_SYSCALL(linux_nr::CHDIR, chdir); + REGISTER_SYSCALL(linux_nr::FCHDIR, fchdir); REGISTER_SYSCALL(linux_nr::OPENAT, openat); #if defined(__x86_64__) REGISTER_SYSCALL(linux_nr::OPEN, open); diff --git a/kernel/syscall/syscall_table.h b/kernel/syscall/syscall_table.h index b0bbfb7d..229f6344 100644 --- a/kernel/syscall/syscall_table.h +++ b/kernel/syscall/syscall_table.h @@ -22,6 +22,7 @@ constexpr int64_t EISDIR = -21; constexpr int64_t EINVAL = -22; constexpr int64_t EMFILE = -24; constexpr int64_t ENOTTY = -25; +constexpr int64_t ERANGE = -34; constexpr int64_t ENAMETOOLONG = -36; constexpr int64_t ENOSYS = -38; constexpr int64_t ENOTEMPTY = -39; diff --git a/kernel/tests/fs/fs.test.cpp b/kernel/tests/fs/fs.test.cpp index 87b4aa93..6b66ad70 100644 --- a/kernel/tests/fs/fs.test.cpp +++ b/kernel/tests/fs/fs.test.cpp @@ -9,6 +9,19 @@ TEST_SUITE(fs_test); +namespace { + +void release_node(fs::node* n) { + if (!n) { + return; + } + if (n->release()) { + fs::node::ref_destroy(n); + } +} + +} // anonymous namespace + TEST(fs_test, create_and_close_file) { fs::file* f = fs::open("/test_create", fs::O_CREAT | fs::O_RDWR); ASSERT_NOT_NULL(f); @@ -184,6 +197,110 @@ TEST(fs_test, nested_dir_dotdot) { fs::rmdir("/a"); } +TEST(fs_test, lookup_at_resolves_relative_paths) { + EXPECT_EQ(fs::mkdir("/lookup_at_base", 0), fs::OK); + EXPECT_EQ(fs::mkdir("/lookup_at_base/sub", 0), fs::OK); + + fs::file* f = fs::open("/lookup_at_base/sub/file", fs::O_CREAT | fs::O_RDWR); + ASSERT_NOT_NULL(f); + fs::close(f); + + fs::node* base = nullptr; + ASSERT_EQ(fs::lookup("/lookup_at_base", &base), fs::OK); + + fs::node* resolved = nullptr; + ASSERT_EQ(fs::lookup_at(base, "sub/file", &resolved), fs::OK); + EXPECT_EQ(static_cast(resolved->type()), + static_cast(fs::node_type::regular)); + + release_node(resolved); + release_node(base); + + fs::unlink("/lookup_at_base/sub/file"); + fs::rmdir("/lookup_at_base/sub"); + fs::rmdir("/lookup_at_base"); +} + +TEST(fs_test, lookup_at_absolute_path_ignores_base) { + EXPECT_EQ(fs::mkdir("/lookup_at_abs_a", 0), fs::OK); + EXPECT_EQ(fs::mkdir("/lookup_at_abs_b", 0), fs::OK); + + fs::node* base = nullptr; + ASSERT_EQ(fs::lookup("/lookup_at_abs_a", &base), fs::OK); + + fs::node* resolved = nullptr; + ASSERT_EQ(fs::lookup_at(base, "/lookup_at_abs_b", &resolved), fs::OK); + EXPECT_EQ(static_cast(resolved->type()), + static_cast(fs::node_type::directory)); + + release_node(resolved); + release_node(base); + + fs::rmdir("/lookup_at_abs_a"); + fs::rmdir("/lookup_at_abs_b"); +} + +TEST(fs_test, resolve_parent_path_at_resolves_parent_under_base) { + EXPECT_EQ(fs::mkdir("/parent_at", 0), fs::OK); + EXPECT_EQ(fs::mkdir("/parent_at/sub", 0), fs::OK); + + fs::node* base = nullptr; + ASSERT_EQ(fs::lookup("/parent_at", &base), fs::OK); + + fs::node* parent = nullptr; + const char* name = nullptr; + size_t name_len = 0; + ASSERT_EQ( + fs::resolve_parent_path_at(base, "sub/child", &parent, &name, &name_len), + fs::OK); + ASSERT_NOT_NULL(parent); + ASSERT_NOT_NULL(name); + EXPECT_EQ(name_len, static_cast(5)); + EXPECT_EQ(string::strncmp(name, "child", name_len), 0); + + char parent_path[fs::PATH_MAX]; + ASSERT_EQ(fs::path_from_node(parent, parent_path, sizeof(parent_path)), fs::OK); + EXPECT_STREQ(parent_path, "/parent_at/sub"); + + release_node(parent); + release_node(base); + + fs::rmdir("/parent_at/sub"); + fs::rmdir("/parent_at"); +} + +TEST(fs_test, path_from_node_returns_absolute_path) { + EXPECT_EQ(fs::mkdir("/path_from", 0), fs::OK); + EXPECT_EQ(fs::mkdir("/path_from/node", 0), fs::OK); + + fs::node* n = nullptr; + ASSERT_EQ(fs::lookup("/path_from/node", &n), fs::OK); + + char path_buf[fs::PATH_MAX]; + ASSERT_EQ(fs::path_from_node(n, path_buf, sizeof(path_buf)), fs::OK); + EXPECT_STREQ(path_buf, "/path_from/node"); + + release_node(n); + fs::rmdir("/path_from/node"); + fs::rmdir("/path_from"); +} + +TEST(fs_test, path_from_node_unlinked_node_returns_noent) { + fs::file* f = fs::open("/path_unlinked", fs::O_CREAT | fs::O_RDWR); + ASSERT_NOT_NULL(f); + fs::close(f); + + fs::node* n = nullptr; + ASSERT_EQ(fs::lookup("/path_unlinked", &n), fs::OK); + + ASSERT_EQ(fs::unlink("/path_unlinked"), fs::OK); + + char path_buf[fs::PATH_MAX]; + EXPECT_EQ(fs::path_from_node(n, path_buf, sizeof(path_buf)), fs::ERR_NOENT); + + release_node(n); +} + TEST(fs_test, stat_nonexistent) { fs::vattr attr; EXPECT_EQ(fs::stat("/nope", &attr), fs::ERR_NOENT);