]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: hard links in diskfs — diskfs_link() with shared storage, nlink tracking, updat...
authorTulio A M Mendes <[email protected]>
Thu, 12 Feb 2026 03:40:48 +0000 (00:40 -0300)
committerTulio A M Mendes <[email protected]>
Fri, 13 Feb 2026 02:20:50 +0000 (23:20 -0300)
include/diskfs.h
src/kernel/diskfs.c
src/kernel/syscall.c

index 4caca51dc1a916419198adca7f89c3a73852f854..4e97031dee58115ec701d9d00844bd661c611120 100644 (file)
@@ -15,6 +15,7 @@ int diskfs_mkdir(const char* rel_path);
 int diskfs_unlink(const char* rel_path);
 int diskfs_rmdir(const char* rel_path);
 int diskfs_rename(const char* old_rel, const char* new_rel);
+int diskfs_link(const char* old_rel, const char* new_rel);
 
 // Writes fixed-size dirent records into out buffer.
 // Returns number of bytes written or negative errno.
index 8d4b981b83081269b10a2ebd02aacc8cfbbfd47d..90ec0c2d368d29082b8f91f860d9e7add436dcd9 100644 (file)
@@ -38,7 +38,7 @@ enum {
 
 struct diskfs_inode {
     uint8_t type;
-    uint8_t reserved0;
+    uint8_t nlink;          /* hard link count (0 or 1 = single link) */
     uint16_t parent;
     char name[DISKFS_NAME_MAX];
     uint32_t start_lba;
@@ -613,7 +613,71 @@ int diskfs_unlink(const char* rel_path) {
     if (sb.inodes[ino].type == DISKFS_INODE_DIR) return -EISDIR;
     if (sb.inodes[ino].type != DISKFS_INODE_FILE) return -ENOENT;
 
+    uint32_t shared_lba = sb.inodes[ino].start_lba;
     memset(&sb.inodes[ino], 0, sizeof(sb.inodes[ino]));
+
+    /* Check if any other inode still references the same data blocks */
+    int still_referenced = 0;
+    if (shared_lba != 0) {
+        for (uint16_t i = 0; i < DISKFS_MAX_INODES; i++) {
+            if (sb.inodes[i].type == DISKFS_INODE_FILE &&
+                sb.inodes[i].start_lba == shared_lba) {
+                still_referenced = 1;
+                break;
+            }
+        }
+    }
+    (void)still_referenced; /* data blocks are never reclaimed in current impl */
+
+    return diskfs_super_store(&sb);
+}
+
+int diskfs_link(const char* old_rel, const char* new_rel) {
+    if (!g_ready) return -ENODEV;
+    if (!old_rel || old_rel[0] == 0) return -EINVAL;
+    if (!new_rel || new_rel[0] == 0) return -EINVAL;
+
+    struct diskfs_super sb;
+    if (diskfs_super_load(&sb) < 0) return -EIO;
+
+    uint16_t old_ino = 0;
+    int rc = diskfs_lookup_path(&sb, old_rel, &old_ino, 0, 0, 0);
+    if (rc < 0) return rc;
+    if (old_ino >= DISKFS_MAX_INODES) return -EIO;
+    if (sb.inodes[old_ino].type != DISKFS_INODE_FILE) return -EPERM;
+
+    /* Find the new name's parent directory and base name */
+    const char* new_base = new_rel;
+    uint16_t new_parent = 0;
+    for (const char* p = new_rel; *p; p++) {
+        if (*p == '/') new_base = p + 1;
+    }
+    /* For simplicity, new link must be in root directory (parent=0) */
+
+    /* Check new name doesn't already exist */
+    uint16_t dummy = 0;
+    if (diskfs_lookup_path(&sb, new_rel, &dummy, 0, 0, 0) == 0) return -EEXIST;
+
+    /* Find a free inode slot */
+    uint16_t new_ino = 0;
+    for (uint16_t i = 1; i < DISKFS_MAX_INODES; i++) {
+        if (sb.inodes[i].type == DISKFS_INODE_FREE) { new_ino = i; break; }
+    }
+    if (new_ino == 0) return -ENOSPC;
+
+    /* Create new inode sharing same data blocks */
+    sb.inodes[new_ino].type = DISKFS_INODE_FILE;
+    sb.inodes[new_ino].nlink = 2;
+    sb.inodes[new_ino].parent = new_parent;
+    diskfs_strlcpy(sb.inodes[new_ino].name, new_base, DISKFS_NAME_MAX);
+    sb.inodes[new_ino].start_lba = sb.inodes[old_ino].start_lba;
+    sb.inodes[new_ino].size_bytes = sb.inodes[old_ino].size_bytes;
+    sb.inodes[new_ino].cap_sectors = sb.inodes[old_ino].cap_sectors;
+
+    /* Update old inode nlink */
+    if (sb.inodes[old_ino].nlink < 2) sb.inodes[old_ino].nlink = 2;
+    else sb.inodes[old_ino].nlink++;
+
     return diskfs_super_store(&sb);
 }
 
index 41b3dc263a362fc617ec9defd9100090120ac98d..2f7a9ad33cfcf99747f6f649ee994d4fb576437c 100644 (file)
@@ -1725,9 +1725,18 @@ static int syscall_readlink_impl(const char* user_path, char* user_buf, uint32_t
 }
 
 static int syscall_link_impl(const char* user_oldpath, const char* user_newpath) {
-    (void)user_oldpath;
-    (void)user_newpath;
-    return -ENOSYS;
+    if (!user_oldpath || !user_newpath) return -EFAULT;
+    char old_path[128], new_path[128];
+    int rc1 = path_resolve_user(user_oldpath, old_path, sizeof(old_path));
+    if (rc1 < 0) return rc1;
+    int rc2 = path_resolve_user(user_newpath, new_path, sizeof(new_path));
+    if (rc2 < 0) return rc2;
+    extern int diskfs_link(const char*, const char*);
+    const char* old_rel = old_path;
+    const char* new_rel = new_path;
+    if (memcmp(old_path, "/disk/", 6) == 0) old_rel = old_path + 6;
+    if (memcmp(new_path, "/disk/", 6) == 0) new_rel = new_path + 6;
+    return diskfs_link(old_rel, new_rel);
 }
 
 static int syscall_chmod_impl(const char* user_path, uint32_t mode) {