diff --git a/kernel/include/kernel/fs/ramfs.h b/kernel/include/kernel/fs/ramfs.h
index 30df6af..08d08c7 100644
--- a/kernel/include/kernel/fs/ramfs.h
+++ b/kernel/include/kernel/fs/ramfs.h
@@ -30,6 +30,8 @@ int seek (inode* node, file* f, size_t offset, int whence);
int getdents (inode* node, file* f, void* buf, size_t count);
int istat (inode* node, stat* buf);
int fstat (inode* node, file* f, stat* buf);
+int close (inode* node, file* f);
+int unlink (inode* node);
typedef struct {
char* c_name;
diff --git a/kernel/include/kernel/fs/vfs.h b/kernel/include/kernel/fs/vfs.h
index 5067977..937752a 100644
--- a/kernel/include/kernel/fs/vfs.h
+++ b/kernel/include/kernel/fs/vfs.h
@@ -51,6 +51,7 @@ typedef struct {
int (*create) (char*, inode**, inode*);
int (*mkdir) (char*, inode**, inode*);
int (*stat) (inode*, stat*);
+ int (*unlink) (inode*);
} inode_operations;
typedef struct {
@@ -93,6 +94,7 @@ int do_chdir (const char* path);
int do_getcwd (char* buf, size_t size);
int do_create (char* filename, inode** result, inode* parent);
int do_lookup (char* filename, inode** result, inode* root, inode* cwd);
+int do_unlink (const char* path);
int do_read (struct file* f, void* buf, size_t size);
int do_seek (struct file* f, size_t offset, int whence);
@@ -116,6 +118,7 @@ uint64_t sys_getcwd (uint64_t buf, uint64_t size);
uint64_t sys_fstat (uint64_t fd, uint64_t buf);
uint64_t sys_stat (uint64_t path, uint64_t buf);
uint64_t sys_ioctl (uint64_t fd, uint64_t req, uint64_t arg);
+uint64_t sys_unlink (uint64_t path);
inode* get_absolute_root (void);
void init_vfs (inode* absolute_root);
diff --git a/kernel/src/kernel/fs/ramfs/ramfs.c b/kernel/src/kernel/fs/ramfs/ramfs.c
index f3b2279..6b5f720 100644
--- a/kernel/src/kernel/fs/ramfs/ramfs.c
+++ b/kernel/src/kernel/fs/ramfs/ramfs.c
@@ -28,12 +28,13 @@ static inode_operations i_ops = {.lookup = lookup,
.lookup_by_ino = lookup_by_ino,
.mkdir = mkdir,
.create = create,
- .stat = istat};
+ .stat = istat,
+ .unlink = unlink};
static file_operations f_ops = {.read = read,
.write = write,
.seek = seek,
.open = nullptr,
- .close = nullptr,
+ .close = close,
.getdents = getdents,
.fstat = fstat};
@@ -48,6 +49,7 @@ int mkdir (char* dirname, inode** result, inode* root) {
new_dir->i_fops = &f_ops;
new_dir->i_no = next_inode++;
new_dir->i_parent = root;
+ new_dir->i_cnt = 1;
// manually add the '.' and '..' entries
((dir_content_t*)new_dir->i_pvt)->d_count = 2;
@@ -85,6 +87,7 @@ int create (char* filename, inode** result, inode* root) {
new_file->i_fops = &f_ops;
new_file->i_no = next_inode++;
new_file->i_parent = root;
+ new_file->i_cnt = 1;
// construct parent replacement structures
dir_content_t* parent_pvt = (dir_content_t*)root->i_pvt;
@@ -272,6 +275,48 @@ int fstat (inode* node, file* f, stat* buf) {
return istat (node, buf);
}
+static void delete_node (inode* node) {
+ inode* parent_node = node->i_parent;
+ if (parent_node == node || !parent_node) goto parent_unlinked;
+
+ dir_content_t* dir_content = (dir_content_t*)parent_node->i_pvt;
+ for (uint64_t i = 0; i < dir_content->d_count; i++) {
+ child_t* d_child = &dir_content->d_children[i];
+ if (!d_child->c_inode || !d_child->c_name) continue;
+
+ if (d_child->c_inode == node) {
+ kfree (d_child->c_name);
+ dir_content->d_children[i] = dir_content->d_children[--dir_content->d_count];
+ void* tmp =
+ krealloc (dir_content->d_children, (dir_content->d_count) * sizeof (child_t));
+ if (tmp)
+ dir_content->d_children = tmp;
+ else
+ kmemset (&dir_content->d_children[dir_content->d_count], 0, sizeof (child_t));
+ break;
+ }
+ }
+
+parent_unlinked:
+ if (node->i_pvt) kfree (node->i_pvt);
+ if (node->i_fsinfo) kfree (node->i_fsinfo);
+ if (node->i_info.ramfs_info) kfree (node->i_info.ramfs_info);
+
+ kfree (node);
+}
+
+int close (inode* node, file* f) {
+ (void)f;
+ if (node->i_cnt == 0 && node->i_type != DIRECTORY) delete_node (node);
+ return 0;
+}
+
+int unlink (inode* node) {
+ node->i_cnt--;
+ if (node->i_cnt == 0) delete_node (node);
+ return 0;
+}
+
inode* init_ramfs_root (void) {
root_inode = kmalloc (sizeof (inode));
kmemset ((void*)root_inode, 0, sizeof (inode));
@@ -281,6 +326,7 @@ inode* init_ramfs_root (void) {
root_inode->i_fops = &f_ops;
root_inode->i_no = next_inode++;
root_inode->i_parent = root_inode;
+ root_inode->i_cnt = 1; // the single reference is us, COS
// manually add the '.' and '..' entries
((dir_content_t*)root_inode->i_pvt)->d_count = 2;
diff --git a/kernel/src/kernel/fs/vfs/close.c b/kernel/src/kernel/fs/vfs/close.c
index 531d9e5..984ae89 100644
--- a/kernel/src/kernel/fs/vfs/close.c
+++ b/kernel/src/kernel/fs/vfs/close.c
@@ -27,8 +27,8 @@
int do_close (struct file* fd) {
if (!fd) return -EINVAL;
if (--fd->f_cnt == 0) {
- if (fd->f_fops && fd->f_fops->close) fd->f_fops->close (fd->f_inode, fd);
fd->f_inode->i_cnt--;
+ if (fd->f_fops && fd->f_fops->close) fd->f_fops->close (fd->f_inode, fd);
kfree (fd);
}
return 0;
diff --git a/kernel/src/kernel/fs/vfs/unlink.c b/kernel/src/kernel/fs/vfs/unlink.c
new file mode 100644
index 0000000..1e55705
--- /dev/null
+++ b/kernel/src/kernel/fs/vfs/unlink.c
@@ -0,0 +1,43 @@
+/*
+ * unlink.c
+ * Copyright (C) 2026 Aditya Kumar
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program; if
+ * not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+int do_unlink (const char* path) {
+ if (!path) return -EINVAL;
+
+ process* current = get_current_process ();
+ inode* node = nullptr;
+
+ int error = do_lookup ((char*)path, &node, current->p_root, current->p_wd);
+ if (error != 0) return error;
+
+ if (node->i_type == DIRECTORY) return -EISDIR;
+ if (!node->i_iops || !node->i_iops->unlink) return -ENOSYS;
+ return node->i_iops->unlink (node);
+}
+
+uint64_t sys_unlink (uint64_t path) {
+ const char* path_us = kstrdup ((const char*)path);
+ int error = do_unlink (path_us);
+ kfree ((void*)path_us);
+ return error;
+}
diff --git a/kernel/src/kernel/fs/vfs/vfs.c b/kernel/src/kernel/fs/vfs/vfs.c
index 10a75b7..24dfec7 100644
--- a/kernel/src/kernel/fs/vfs/vfs.c
+++ b/kernel/src/kernel/fs/vfs/vfs.c
@@ -41,4 +41,5 @@ void init_vfs (inode* absolute_root) {
register_syscall (SYSCALL_SYS_CHDIR, SYS1 (sys_chdir));
register_syscall (SYSCALL_SYS_GETCWD, SYS2 (sys_getcwd));
register_syscall (SYSCALL_SYS_IOCTL, SYS3 (sys_ioctl));
+ register_syscall (SYSCALL_SYS_UNLINK, SYS1 (sys_unlink));
}
\ No newline at end of file
diff --git a/lib/cos/src/unlink.c b/lib/cos/src/unlink.c
new file mode 100644
index 0000000..511abb9
--- /dev/null
+++ b/lib/cos/src/unlink.c
@@ -0,0 +1,22 @@
+/*
+ * unlink.c
+ * Copyright (C) 2026 Aditya Kumar
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program; if
+ * not, see .
+ */
+
+#include
+#include
+
+int unlink (const char* __path) {
+ return (int)syscall_ret ((long)syscall1 (SYSCALL_SYS_UNLINK, (uint64_t)__path));
+}
\ No newline at end of file
diff --git a/user/cosh/include/builtin.h b/user/cosh/include/builtin.h
index ff8a022..2ae2f7c 100644
--- a/user/cosh/include/builtin.h
+++ b/user/cosh/include/builtin.h
@@ -33,3 +33,4 @@ int builtin_source (int argc, char** argv);
int builtin_stat (int argc, char** argv);
int builtin_test (int argc, char** argv);
int builtin_touch (int argc, char** argv);
+int builtin_unlink (int argc, char** argv);
diff --git a/user/cosh/src/builtin/dispatch_builtin.c b/user/cosh/src/builtin/dispatch_builtin.c
index efe7fb5..67880d3 100644
--- a/user/cosh/src/builtin/dispatch_builtin.c
+++ b/user/cosh/src/builtin/dispatch_builtin.c
@@ -51,6 +51,8 @@ int dispatch_builtin (size_t argc, char** argv) {
return builtin_test (argc, argv);
else if (strcmp (argv[0], "clear") == 0)
return builtin_clear (argc, argv);
+ else if (strcmp (argv[0], "unlink") == 0)
+ return builtin_unlink (argc, argv);
return -1;
}
diff --git a/user/cosh/src/builtin/unlink.c b/user/cosh/src/builtin/unlink.c
new file mode 100644
index 0000000..92b8e3b
--- /dev/null
+++ b/user/cosh/src/builtin/unlink.c
@@ -0,0 +1,27 @@
+/*
+ * unlink.c
+ * Copyright (C) 2026 Aditya Kumar
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program; if
+ * not, see .
+ */
+
+#include
+#include
+#include
+
+int builtin_unlink (int argc, char** argv) {
+ if (argc == 1) {
+ printf ("usage: unlink file\n");
+ return 64;
+ }
+ return unlink (argv[1]);
+}
\ No newline at end of file
diff --git a/user/cosh/src/repl_main.c b/user/cosh/src/repl_main.c
index 2fbb267..6c347a9 100644
--- a/user/cosh/src/repl_main.c
+++ b/user/cosh/src/repl_main.c
@@ -145,7 +145,7 @@ int repl_loop (void) {
cmdbuf[strcspn (cmdbuf, "\n")] = '\0';
if (cmdbuf[0] == '\0') return 0;
last_exit = run_line (cmdbuf);
- if (last_exit != 0) printf ("\033[31mexited with non-zero status: %i\n", last_exit);
+ if (last_exit != 0) printf ("\033[31m[%i] ", last_exit);
return 0;
}