]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: symbolic links (symlink, readlink) and link stub
authorTulio A M Mendes <[email protected]>
Wed, 11 Feb 2026 22:53:00 +0000 (19:53 -0300)
committerTulio A M Mendes <[email protected]>
Fri, 13 Feb 2026 02:20:50 +0000 (23:20 -0300)
- include/fs.h: added FS_SYMLINK type and symlink_target[128] field to fs_node_t
- include/stat.h: added S_IFLNK define
- include/syscall.h: added SYSCALL_LINK(54), SYSCALL_SYMLINK(55), SYSCALL_READLINK(56)
- src/kernel/fs.c: vfs_lookup follows symlinks with depth limit (max 8)
- src/kernel/tmpfs.c: tmpfs_create_symlink creates FS_SYMLINK nodes
- src/kernel/syscall.c: symlink_impl, readlink_impl, link_impl (stub -ENOSYS)
  stat_from_node reports S_IFLNK for symlink nodes
- cppcheck clean, 19/19 smoke tests pass

include/fs.h
include/stat.h
include/syscall.h
include/tmpfs.h
src/kernel/fs.c
src/kernel/syscall.c
src/kernel/tmpfs.c

index a7fda112c6ba0d4a8de5b614a0c63cfbb84f1f71..b6f3c9eac69aaf1e86c9b520526f90ed830b1f38 100644 (file)
@@ -8,6 +8,7 @@
 #define FS_DIRECTORY   0x02
 #define FS_CHARDEVICE  0x03
 #define FS_BLOCKDEVICE 0x04
+#define FS_SYMLINK     0x05
 
 typedef struct fs_node {
     char name[128];
@@ -17,6 +18,7 @@ typedef struct fs_node {
     uint32_t uid;
     uint32_t gid;
     uint32_t mode;
+    char symlink_target[128];
     
     // Function pointers for operations (Polymorphism in C)
     uint32_t (*read)(struct fs_node* node, uint32_t offset, uint32_t size, uint8_t* buffer);
index 77941b86e7fbcb7769f9a38f4cd32b6f8cf0f24b..0c93881d15b9555c407b2589b37dc1466eef45c5 100644 (file)
@@ -7,6 +7,7 @@
 #define S_IFREG 0100000
 #define S_IFDIR 0040000
 #define S_IFCHR 0020000
+#define S_IFLNK 0120000
 
 #define S_IRWXU 00700
 #define S_IRUSR 00400
index 3fbaf68091362efacdcc43c09c618f1f0eb93e14..c7cc0129870d19755808f53d0175744ba7d6dafd 100644 (file)
@@ -72,6 +72,10 @@ enum {
     SYSCALL_CHOWN  = 51,
     SYSCALL_GETUID = 52,
     SYSCALL_GETGID = 53,
+
+    SYSCALL_LINK     = 54,
+    SYSCALL_SYMLINK  = 55,
+    SYSCALL_READLINK = 56,
 };
 
 #endif
index cc5608714a781925c4637f9a57bcd5f02a81e21b..a88b365b1d2dcce304231a2701e0d29afab49fe3 100644 (file)
@@ -9,5 +9,6 @@ int tmpfs_add_file(fs_node_t* root_dir, const char* name, const uint8_t* data, u
 
 int tmpfs_mkdir_p(fs_node_t* root_dir, const char* path);
 fs_node_t* tmpfs_create_file(fs_node_t* root_dir, const char* path, const uint8_t* data, uint32_t len);
+int tmpfs_create_symlink(fs_node_t* root_dir, const char* link_path, const char* target);
 
 #endif
index bd4ed983cae9a68171565ce9381d1012c764cb45..b7ec4a9db30f3a62ea6116824702c7b92a4c469b 100644 (file)
@@ -91,8 +91,15 @@ void vfs_close(fs_node_t* node) {
         node->close(node);
 }
 
+static fs_node_t* vfs_lookup_depth(const char* path, int depth);
+
 fs_node_t* vfs_lookup(const char* path) {
+    return vfs_lookup_depth(path, 0);
+}
+
+static fs_node_t* vfs_lookup_depth(const char* path, int depth) {
     if (!path || !fs_root) return NULL;
+    if (depth > 8) return NULL;
 
     fs_node_t* base = fs_root;
     const char* rel = path;
@@ -137,6 +144,11 @@ fs_node_t* vfs_lookup(const char* path) {
         if (!cur || !cur->finddir) return NULL;
         cur = cur->finddir(cur, part);
         if (!cur) return NULL;
+
+        if (cur->flags == FS_SYMLINK && cur->symlink_target[0]) {
+            cur = vfs_lookup_depth(cur->symlink_target, depth + 1);
+            if (!cur) return NULL;
+        }
     }
 
     return cur;
index a22344d53c41f305610d7b04c193dc724af727c4..0e0489a5449779bf15394807fb6f2d5334cb0387 100644 (file)
@@ -11,6 +11,7 @@
 #include "tty.h"
 #include "pty.h"
 #include "diskfs.h"
+#include "tmpfs.h"
 
 #include "errno.h"
 #include "shm.h"
@@ -533,6 +534,7 @@ static int stat_from_node(const fs_node_t* node, struct stat* st) {
     uint32_t mode = node->mode & 07777;
     if (node->flags == FS_DIRECTORY) mode |= S_IFDIR;
     else if (node->flags == FS_CHARDEVICE) mode |= S_IFCHR;
+    else if (node->flags == FS_SYMLINK) mode |= S_IFLNK;
     else mode |= S_IFREG;
     if ((mode & 07777) == 0) mode |= 0755;
     st->st_mode = mode;
@@ -1609,6 +1611,85 @@ static uintptr_t syscall_brk_impl(uintptr_t addr) {
     return addr;
 }
 
+static int syscall_symlink_impl(const char* user_target, const char* user_linkpath) {
+    if (!user_target || !user_linkpath) return -EFAULT;
+
+    char target[128], linkpath[128];
+    if (copy_from_user(target, user_target, sizeof(target)) < 0) return -EFAULT;
+    target[sizeof(target) - 1] = 0;
+
+    int prc = path_resolve_user(user_linkpath, linkpath, sizeof(linkpath));
+    if (prc < 0) return prc;
+
+    /* Find parent directory */
+    char parent[128];
+    char leaf[128];
+    strcpy(parent, linkpath);
+    char* last_slash = NULL;
+    for (char* p = parent; *p; p++) {
+        if (*p == '/') last_slash = p;
+    }
+    if (!last_slash) return -EINVAL;
+    if (last_slash == parent) {
+        parent[1] = 0;
+        strcpy(leaf, linkpath + 1);
+    } else {
+        *last_slash = 0;
+        strcpy(leaf, last_slash + 1);
+    }
+    if (leaf[0] == 0) return -EINVAL;
+
+    fs_node_t* dir = vfs_lookup(parent);
+    if (!dir || dir->flags != FS_DIRECTORY) return -ENOENT;
+
+    return tmpfs_create_symlink(dir, leaf, target);
+}
+
+static int syscall_readlink_impl(const char* user_path, char* user_buf, uint32_t bufsz) {
+    if (!user_path || !user_buf) return -EFAULT;
+    if (bufsz == 0) return -EINVAL;
+    if (user_range_ok(user_buf, (size_t)bufsz) == 0) return -EFAULT;
+
+    char path[128];
+    int prc = path_resolve_user(user_path, path, sizeof(path));
+    if (prc < 0) return prc;
+
+    /* readlink must NOT follow the final symlink — look up parent + finddir */
+    char parent[128];
+    char leaf[128];
+    strcpy(parent, path);
+    char* last_slash = NULL;
+    for (char* p = parent; *p; p++) {
+        if (*p == '/') last_slash = p;
+    }
+    if (!last_slash) return -EINVAL;
+    if (last_slash == parent) {
+        parent[1] = 0;
+        strcpy(leaf, path + 1);
+    } else {
+        *last_slash = 0;
+        strcpy(leaf, last_slash + 1);
+    }
+
+    fs_node_t* dir = vfs_lookup(parent);
+    if (!dir || !dir->finddir) return -ENOENT;
+
+    fs_node_t* node = dir->finddir(dir, leaf);
+    if (!node) return -ENOENT;
+    if (node->flags != FS_SYMLINK) return -EINVAL;
+
+    uint32_t len = (uint32_t)strlen(node->symlink_target);
+    if (len > bufsz) len = bufsz;
+    if (copy_to_user(user_buf, node->symlink_target, len) < 0) return -EFAULT;
+    return (int)len;
+}
+
+static int syscall_link_impl(const char* user_oldpath, const char* user_newpath) {
+    (void)user_oldpath;
+    (void)user_newpath;
+    return -ENOSYS;
+}
+
 static int syscall_chmod_impl(const char* user_path, uint32_t mode) {
     if (!user_path) return -EFAULT;
 
@@ -2020,6 +2101,28 @@ void syscall_handler(struct registers* regs) {
         return;
     }
 
+    if (syscall_no == SYSCALL_LINK) {
+        const char* oldpath = (const char*)regs->ebx;
+        const char* newpath = (const char*)regs->ecx;
+        regs->eax = (uint32_t)syscall_link_impl(oldpath, newpath);
+        return;
+    }
+
+    if (syscall_no == SYSCALL_SYMLINK) {
+        const char* target = (const char*)regs->ebx;
+        const char* linkpath = (const char*)regs->ecx;
+        regs->eax = (uint32_t)syscall_symlink_impl(target, linkpath);
+        return;
+    }
+
+    if (syscall_no == SYSCALL_READLINK) {
+        const char* path = (const char*)regs->ebx;
+        char* buf = (char*)regs->ecx;
+        uint32_t bufsz = regs->edx;
+        regs->eax = (uint32_t)syscall_readlink_impl(path, buf, bufsz);
+        return;
+    }
+
     if (syscall_no == SYSCALL_CHMOD) {
         const char* path = (const char*)regs->ebx;
         uint32_t mode = regs->ecx;
index b3b3fbac2fd58be9687a2755796a32a5e1eff11b..a1fd840e8fcc8d7b74f29c4766eef4820e886683 100644 (file)
@@ -313,3 +313,40 @@ fs_node_t* tmpfs_create_file(fs_node_t* root_dir, const char* path, const uint8_
     tmpfs_child_add(cur, f);
     return &f->vfs;
 }
+
+int tmpfs_create_symlink(fs_node_t* root_dir, const char* link_path, const char* target) {
+    if (!root_dir || root_dir->flags != FS_DIRECTORY) return -ENOTDIR;
+    if (!link_path || !target) return -EINVAL;
+
+    struct tmpfs_node* cur = (struct tmpfs_node*)root_dir;
+    const char* p = link_path;
+    char part[128];
+    char leaf[128];
+    leaf[0] = 0;
+
+    while (tmpfs_split_next(&p, part, sizeof(part))) {
+        if (*p == 0) {
+            strcpy(leaf, part);
+            break;
+        }
+        struct tmpfs_node* next = tmpfs_child_ensure_dir(cur, part);
+        if (!next) return -ENOMEM;
+        cur = next;
+    }
+
+    if (leaf[0] == 0) return -EINVAL;
+    if (tmpfs_child_find(cur, leaf)) return -EEXIST;
+
+    struct tmpfs_node* ln = tmpfs_node_alloc(leaf, FS_SYMLINK);
+    if (!ln) return -ENOMEM;
+
+    strcpy(ln->vfs.symlink_target, target);
+    ln->vfs.length = (uint32_t)strlen(target);
+    ln->vfs.read = 0;
+    ln->vfs.write = 0;
+    ln->vfs.finddir = 0;
+    ln->vfs.readdir = 0;
+
+    tmpfs_child_add(cur, ln);
+    return 0;
+}