]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
fix: shell/command bugs, new utilities, procfs race condition
authorTulio A M Mendes <[email protected]>
Tue, 17 Feb 2026 04:31:30 +0000 (01:31 -0300)
committerTulio A M Mendes <[email protected]>
Tue, 17 Feb 2026 04:31:30 +0000 (01:31 -0300)
Shell fixes:
- Fix DELETE key showing ~ (handle \x1b[3~ escape sequence + Home/End)
- Fix builtin redirections (echo > file now works via saved fd restore)
- Fix initrd readdir (root cause of ls /bin empty + tab completion broken)

Command fixes:
- Fix cut -dX/-fN combined argument parsing (POSIX style)
- Fix ps showing ? for PIDs: add cmdline[128] to process struct, populate in execve + init
- Fix procfs race condition: use sched_lock for process list traversal
- Make sched_lock non-static for procfs access

New commands (22 total):
- Previous session: mount, umount, env, kill, sleep, clear, ps, df, free, tee, basename, dirname, rmdir
- This session: grep, id, uname, dmesg, printenv, tr, dd, pwd, stat

Arch contamination note: vdso.c includes arch/x86/kernel_va_map.h directly (acceptable for now, only x86 target)

Tests: 89/89 smoke, cppcheck clean

37 files changed:
Makefile
include/errno.h
include/process.h
src/arch/x86/arch_platform.c
src/arch/x86/elf.c
src/drivers/initrd.c
src/kernel/overlayfs.c
src/kernel/procfs.c
src/kernel/scheduler.c
src/kernel/syscall.c
src/kernel/tmpfs.c
user/basename.c [new file with mode: 0644]
user/clear.c [new file with mode: 0644]
user/cut.c
user/dd.c [new file with mode: 0644]
user/df.c [new file with mode: 0644]
user/dirname.c [new file with mode: 0644]
user/dmesg.c [new file with mode: 0644]
user/env.c [new file with mode: 0644]
user/free.c [new file with mode: 0644]
user/grep.c [new file with mode: 0644]
user/id.c [new file with mode: 0644]
user/kill.c [new file with mode: 0644]
user/mount.c [new file with mode: 0644]
user/printenv.c [new file with mode: 0644]
user/ps.c [new file with mode: 0644]
user/pwd.c [new file with mode: 0644]
user/rmdir.c [new file with mode: 0644]
user/sh.c
user/sleep.c [new file with mode: 0644]
user/stat.c [new file with mode: 0644]
user/tee.c [new file with mode: 0644]
user/tr.c [new file with mode: 0644]
user/ulibc/include/termios.h [new file with mode: 0644]
user/ulibc/src/unistd.c
user/umount.c [new file with mode: 0644]
user/uname.c [new file with mode: 0644]

index 9e264f41228fba815b6927765264c2193cd9f094..68d94b5069004ef45911c02bf198cfc6cfd75c5d 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -96,6 +96,28 @@ ifeq ($(ARCH),x86)
     DATE_ELF := user/date.elf
     HOSTNAME_ELF := user/hostname.elf
     UPTIME_ELF := user/uptime.elf
+    MOUNT_ELF := user/mount.elf
+    UMOUNT_ELF := user/umount.elf
+    ENV_ELF := user/env.elf
+    KILL_ELF := user/kill.elf
+    SLEEP_ELF := user/sleep.elf
+    CLEAR_ELF := user/clear.elf
+    PS_ELF := user/ps.elf
+    DF_ELF := user/df.elf
+    FREE_ELF := user/free.elf
+    TEE_ELF := user/tee.elf
+    BASENAME_ELF := user/basename.elf
+    DIRNAME_ELF := user/dirname.elf
+    RMDIR_ELF := user/rmdir.elf
+    GREP_ELF := user/grep.elf
+    ID_ELF := user/id.elf
+    UNAME_ELF := user/uname.elf
+    DMESG_ELF := user/dmesg.elf
+    PRINTENV_ELF := user/printenv.elf
+    TR_ELF := user/tr.elf
+    DD_ELF := user/dd.elf
+    PWD_ELF := user/pwd.elf
+    STAT_ELF := user/stat.elf
     INIT_ELF := user/init.elf
     LDSO_ELF := user/ld.so
     ULIBC_SO := user/ulibc/libc.so
@@ -289,6 +311,94 @@ $(INIT_ELF): user/init.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
        @$(DYN_CC) -c user/init.c -o user/init.o
        @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/init.o -lc
 
+$(MOUNT_ELF): user/mount.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/mount.c -o user/mount.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/mount.o -lc
+
+$(UMOUNT_ELF): user/umount.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/umount.c -o user/umount.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/umount.o -lc
+
+$(ENV_ELF): user/env.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/env.c -o user/env.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/env.o -lc
+
+$(KILL_ELF): user/kill.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/kill.c -o user/kill.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/kill.o -lc
+
+$(SLEEP_ELF): user/sleep.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/sleep.c -o user/sleep.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/sleep.o -lc
+
+$(CLEAR_ELF): user/clear.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/clear.c -o user/clear.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/clear.o -lc
+
+$(PS_ELF): user/ps.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/ps.c -o user/ps.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/ps.o -lc
+
+$(DF_ELF): user/df.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/df.c -o user/df.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/df.o -lc
+
+$(FREE_ELF): user/free.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/free.c -o user/free.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/free.o -lc
+
+$(TEE_ELF): user/tee.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/tee.c -o user/tee.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/tee.o -lc
+
+$(BASENAME_ELF): user/basename.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/basename.c -o user/basename.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/basename.o -lc
+
+$(DIRNAME_ELF): user/dirname.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/dirname.c -o user/dirname.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/dirname.o -lc
+
+$(RMDIR_ELF): user/rmdir.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/rmdir.c -o user/rmdir.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/rmdir.o -lc
+
+$(GREP_ELF): user/grep.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/grep.c -o user/grep.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/grep.o -lc
+
+$(ID_ELF): user/id.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/id.c -o user/id.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/id.o -lc
+
+$(UNAME_ELF): user/uname.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/uname.c -o user/uname.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/uname.o -lc
+
+$(DMESG_ELF): user/dmesg.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/dmesg.c -o user/dmesg.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/dmesg.o -lc
+
+$(PRINTENV_ELF): user/printenv.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/printenv.c -o user/printenv.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/printenv.o -lc
+
+$(TR_ELF): user/tr.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/tr.c -o user/tr.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/tr.o -lc
+
+$(DD_ELF): user/dd.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/dd.c -o user/dd.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/dd.o -lc
+
+$(PWD_ELF): user/pwd.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/pwd.c -o user/pwd.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/pwd.o -lc
+
+$(STAT_ELF): user/stat.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$(DYN_CC) -c user/stat.c -o user/stat.o
+       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/stat.o -lc
+
 $(LDSO_ELF): user/ldso.c user/ldso_linker.ld
        @i686-elf-gcc -m32 -ffreestanding -fno-pie -no-pie -nostdlib -Wl,-T,user/ldso_linker.ld -o $(LDSO_ELF) user/ldso.c
 
@@ -306,6 +416,11 @@ USER_CMDS := $(ECHO_ELF) $(SH_ELF) $(CAT_ELF) $(LS_ELF) $(MKDIR_ELF) $(RM_ELF) \
              $(HEAD_ELF) $(TAIL_ELF) $(WC_ELF) $(SORT_ELF) $(UNIQ_ELF) $(CUT_ELF) \
              $(CHMOD_ELF) $(CHOWN_ELF) $(CHGRP_ELF) \
              $(DATE_ELF) $(HOSTNAME_ELF) $(UPTIME_ELF) \
+             $(MOUNT_ELF) $(UMOUNT_ELF) $(ENV_ELF) $(KILL_ELF) $(SLEEP_ELF) \
+             $(CLEAR_ELF) $(PS_ELF) $(DF_ELF) $(FREE_ELF) $(TEE_ELF) \
+             $(BASENAME_ELF) $(DIRNAME_ELF) $(RMDIR_ELF) \
+             $(GREP_ELF) $(ID_ELF) $(UNAME_ELF) $(DMESG_ELF) \
+             $(PRINTENV_ELF) $(TR_ELF) $(DD_ELF) $(PWD_ELF) $(STAT_ELF) \
              $(INIT_ELF)
 
 FSTAB := rootfs/etc/fstab
@@ -318,6 +433,14 @@ INITRD_FILES := $(FULLTEST_ELF):sbin/fulltest \
     $(SORT_ELF):bin/sort $(UNIQ_ELF):bin/uniq $(CUT_ELF):bin/cut \
     $(CHMOD_ELF):bin/chmod $(CHOWN_ELF):bin/chown $(CHGRP_ELF):bin/chgrp \
     $(DATE_ELF):bin/date $(HOSTNAME_ELF):bin/hostname $(UPTIME_ELF):bin/uptime \
+    $(MOUNT_ELF):bin/mount $(UMOUNT_ELF):bin/umount $(ENV_ELF):bin/env \
+    $(KILL_ELF):bin/kill $(SLEEP_ELF):bin/sleep $(CLEAR_ELF):bin/clear \
+    $(PS_ELF):bin/ps $(DF_ELF):bin/df $(FREE_ELF):bin/free \
+    $(TEE_ELF):bin/tee $(BASENAME_ELF):bin/basename $(DIRNAME_ELF):bin/dirname \
+    $(RMDIR_ELF):bin/rmdir \
+    $(GREP_ELF):bin/grep $(ID_ELF):bin/id $(UNAME_ELF):bin/uname \
+    $(DMESG_ELF):bin/dmesg $(PRINTENV_ELF):bin/printenv $(TR_ELF):bin/tr \
+    $(DD_ELF):bin/dd $(PWD_ELF):bin/pwd $(STAT_ELF):bin/stat \
     $(LDSO_ELF):lib/ld.so $(ULIBC_SO):lib/libc.so \
     $(PIE_SO):lib/libpietest.so $(PIE_ELF):bin/pie_test \
     $(FSTAB):etc/fstab
index 77335b1adbe1ea36a7d91966807e104d751bd236..f52c2f20e0ddbd14651435812b63dcca15c93599 100644 (file)
@@ -38,6 +38,7 @@
 #define ENOLCK 37
 #define EBUSY 16
 #define EMSGSIZE 90
+#define EROFS 30
 #define EWOULDBLOCK EAGAIN
 
 #endif
index 2ac7d48fca782e9d16ff043f829b185fcb232fb7..89247c138a499d0bf90cf08311bc180ad7e6b970 100644 (file)
@@ -102,6 +102,7 @@ struct process {
     uintptr_t heap_break;
 
     char cwd[128];
+    char cmdline[128];
     uint32_t umask;
 
     int waiting;
index e89d8bd930266c84fa053b56cea9cd428e0bd0d4..de8e28c806edc8da9c6734b86d4120037bc57602 100644 (file)
@@ -12,6 +12,7 @@
 
 #include "process.h"
 #include "heap.h"
+#include "utils.h"
 
 #include "hal/cpu.h"
 #include "hal/uart.h"
@@ -57,6 +58,8 @@ static void userspace_init_thread(void) {
     current_process->addr_space = user_as;
     current_process->heap_start = heap_brk;
     current_process->heap_break = heap_brk;
+    strncpy(current_process->cmdline, init_path, sizeof(current_process->cmdline) - 1);
+    current_process->cmdline[sizeof(current_process->cmdline) - 1] = '\0';
     vmm_as_activate(user_as);
 
     /* Register this process as "init" for orphan reparenting */
@@ -85,6 +88,53 @@ static void userspace_init_thread(void) {
         }
     }
 
+    /* If the binary uses a dynamic linker (PT_INTERP), ld.so needs a
+     * proper stack layout: [argc][argv...][NULL][envp...][NULL][auxv...]
+     * The execve path does this in syscall.c; for the init thread we
+     * must build it here since there is no prior execve. */
+    {
+        elf32_auxv_t auxv_buf[8];
+        int auxv_n = elf32_pop_pending_auxv(auxv_buf, 8);
+        if (auxv_n > 0) {
+            /* Build the stack top-down in the user address space.
+             * Layout (grows downward):
+             *   auxv[]         (auxv_n * 8 bytes)
+             *   NULL           (4 bytes — envp terminator)
+             *   NULL           (4 bytes — argv terminator)
+             *   argv[0] ptr    (4 bytes — points to init_path string)
+             *   argc = 1       (4 bytes)
+             *   [init_path string bytes at bottom] */
+            size_t path_len = 0;
+            for (const char* s = init_path; *s; s++) path_len++;
+            path_len++; /* include NUL */
+
+            /* Copy init_path string onto user stack */
+            user_sp -= (path_len + 3U) & ~3U; /* align to 4 */
+            uintptr_t path_va = user_sp;
+            memcpy((void*)path_va, init_path, path_len);
+
+            /* auxv array */
+            user_sp -= (uintptr_t)(auxv_n * (int)sizeof(elf32_auxv_t));
+            memcpy((void*)user_sp, auxv_buf, (size_t)auxv_n * sizeof(elf32_auxv_t));
+
+            /* envp NULL terminator */
+            user_sp -= 4;
+            *(uint32_t*)user_sp = 0;
+
+            /* argv NULL terminator */
+            user_sp -= 4;
+            *(uint32_t*)user_sp = 0;
+
+            /* argv[0] = pointer to init_path string */
+            user_sp -= 4;
+            *(uint32_t*)user_sp = (uint32_t)path_va;
+
+            /* argc = 1 */
+            user_sp -= 4;
+            *(uint32_t*)user_sp = 1;
+        }
+    }
+
     kprintf("[ELF] starting %s\n", init_path);
 
     kprintf("[ELF] user_range_ok(entry)=%c user_range_ok(stack)=%c\n",
index afa3e9bab599c54d8c0c1efbef6af5787c517856..27dae9bffe8fd2bb4ffdcaf5a04dd65b6394ef7b 100644 (file)
@@ -314,7 +314,7 @@ static int elf32_load_needed_libs(const uint8_t* file, uint32_t file_len,
         uintptr_t seg_end = 0;
         int rc = elf32_load_shared_lib_at(path, as, lib_base, &seg_end);
         if (rc == 0) {
-            kprintf("[ELF] loaded shared lib: %s at 0x%x\n", path, (unsigned)lib_base);
+            /* shared lib loaded silently */
             lib_base = (seg_end + 0xFFFU) & ~(uintptr_t)0xFFFU;
             loaded++;
         } else {
@@ -456,7 +456,7 @@ int elf32_load_user_from_initrd(const char* filename, uintptr_t* entry_out, uint
             if (irc == 0) {
                 real_entry = interp_entry;
                 has_interp = 1;
-                kprintf("[ELF] loaded interp: %s\n", interp_path);
+                /* interp loaded silently */
             }
             break;
         }
index 51a619df439d9d9a0b20bf3d2378c0dac85e1bff..94696442dd468689fb28c7d283d1834cd8605cb2 100644 (file)
@@ -200,8 +200,58 @@ static const struct file_operations initrd_file_ops = {
 
 static const struct file_operations initrd_dir_ops = {0};
 
+static int initrd_readdir(struct fs_node* node, uint32_t* inout_index, void* buf, uint32_t buf_len) {
+    if (!node || !inout_index || !buf) return -1;
+    if (node->flags != FS_DIRECTORY) return -1;
+    if (buf_len < sizeof(struct vfs_dirent)) return -1;
+
+    int parent = (int)node->inode;
+    if (parent < 0 || parent >= entry_count) return -1;
+
+    uint32_t idx = *inout_index;
+    uint32_t cap = buf_len / (uint32_t)sizeof(struct vfs_dirent);
+    struct vfs_dirent* ents = (struct vfs_dirent*)buf;
+    uint32_t written = 0;
+
+    while (written < cap) {
+        struct vfs_dirent e;
+        memset(&e, 0, sizeof(e));
+
+        if (idx == 0) {
+            e.d_ino = node->inode;
+            e.d_type = FS_DIRECTORY;
+            strcpy(e.d_name, ".");
+        } else if (idx == 1) {
+            int pi = entries[parent].parent;
+            e.d_ino = (pi >= 0) ? (uint32_t)pi : node->inode;
+            e.d_type = FS_DIRECTORY;
+            strcpy(e.d_name, "..");
+        } else {
+            /* Walk the child linked list to find the (idx-2)th child */
+            uint32_t skip = idx - 2;
+            int c = entries[parent].first_child;
+            while (c != -1 && skip > 0) {
+                c = entries[c].next_sibling;
+                skip--;
+            }
+            if (c == -1) break;
+            e.d_ino = (uint32_t)c;
+            e.d_type = (uint8_t)entries[c].flags;
+            strcpy(e.d_name, entries[c].name);
+        }
+
+        e.d_reclen = (uint16_t)sizeof(e);
+        ents[written++] = e;
+        idx++;
+    }
+
+    *inout_index = idx;
+    return (int)(written * (uint32_t)sizeof(struct vfs_dirent));
+}
+
 static const struct inode_operations initrd_dir_iops = {
-    .lookup = initrd_finddir,
+    .lookup  = initrd_finddir,
+    .readdir = initrd_readdir,
 };
 
 static void initrd_finalize_nodes(void) {
index c769fbf5b44152bee8b6e8304cf76dbee20e31d0..8a4b5b0b83a11a4a77cd59ebdc1a51a8859940f3 100644 (file)
@@ -1,5 +1,6 @@
 #include "overlayfs.h"
 
+#include "errno.h"
 #include "heap.h"
 #include "utils.h"
 #include "tmpfs.h"
@@ -23,6 +24,10 @@ struct overlay_node {
 
 static struct fs_node* overlay_finddir_impl(struct fs_node* node, const char* name);
 static int overlay_readdir_impl(struct fs_node* node, uint32_t* inout_index, void* buf, uint32_t buf_len);
+static int overlay_mkdir_impl(struct fs_node* dir, const char* name);
+static int overlay_unlink_impl(struct fs_node* dir, const char* name);
+static int overlay_rmdir_impl(struct fs_node* dir, const char* name);
+static int overlay_create_impl(struct fs_node* dir, const char* name, uint32_t flags, struct fs_node** out);
 
 static void overlay_str_copy_n(char* dst, size_t dst_sz, const char* src, size_t src_n) {
     if (!dst || dst_sz == 0) return;
@@ -99,6 +104,10 @@ static const struct file_operations overlay_dir_ops = {
 static const struct inode_operations overlay_dir_iops = {
     .lookup  = overlay_finddir_impl,
     .readdir = overlay_readdir_impl,
+    .mkdir   = overlay_mkdir_impl,
+    .unlink  = overlay_unlink_impl,
+    .rmdir   = overlay_rmdir_impl,
+    .create  = overlay_create_impl,
 };
 
 static fs_node_t* overlay_wrap_child(struct overlay_node* parent, const char* name, fs_node_t* lower_child, fs_node_t* upper_child) {
@@ -158,19 +167,73 @@ static fs_node_t* overlay_wrap_child(struct overlay_node* parent, const char* na
     return &c->vfs;
 }
 
+static int overlay_count_upper(struct overlay_node* dir) {
+    if (!dir->upper || !dir->upper->i_ops || !dir->upper->i_ops->readdir) return 0;
+    int count = 0;
+    uint32_t idx = 0;
+    struct vfs_dirent tmp;
+    while (1) {
+        int rc = dir->upper->i_ops->readdir(dir->upper, &idx, &tmp, sizeof(tmp));
+        if (rc <= 0) break;
+        count += rc / (int)sizeof(struct vfs_dirent);
+    }
+    return count;
+}
+
+static int overlay_upper_has_name(struct overlay_node* dir, const char* name) {
+    if (!dir->upper || !dir->upper->i_ops || !dir->upper->i_ops->lookup) return 0;
+    fs_node_t* found = dir->upper->i_ops->lookup(dir->upper, name);
+    return found ? 1 : 0;
+}
+
 static int overlay_readdir_impl(struct fs_node* node, uint32_t* inout_index, void* buf, uint32_t buf_len) {
     if (!node || !inout_index || !buf) return -1;
     if (node->flags != FS_DIRECTORY) return -1;
     if (buf_len < sizeof(struct vfs_dirent)) return -1;
 
     struct overlay_node* dir = (struct overlay_node*)node;
+    uint32_t idx = *inout_index;
+    uint32_t cap = buf_len / (uint32_t)sizeof(struct vfs_dirent);
+    struct vfs_dirent* ents = (struct vfs_dirent*)buf;
+    uint32_t written = 0;
+
+    /* Phase 1: emit upper layer entries */
+    if (dir->upper && dir->upper->i_ops && dir->upper->i_ops->readdir) {
+        uint32_t upper_idx = idx;
+        int rc = dir->upper->i_ops->readdir(dir->upper, &upper_idx, ents, buf_len);
+        if (rc > 0) {
+            written = (uint32_t)rc / (uint32_t)sizeof(struct vfs_dirent);
+            *inout_index = upper_idx;
+            return rc;
+        }
+        /* Upper exhausted — switch to lower layer.
+         * Count total upper entries to know offset for lower phase. */
+    }
 
-    // Prefer upper layer readdir; fall back to lower.
-    fs_node_t* src = dir->upper ? dir->upper : dir->lower;
-    if (!src) return 0;
-    if (src->i_ops && src->i_ops->readdir)
-        return src->i_ops->readdir(src, inout_index, buf, buf_len);
-    return 0;
+    /* Phase 2: emit lower layer entries, skipping those already in upper */
+    if (!dir->lower || !dir->lower->i_ops || !dir->lower->i_ops->readdir) {
+        *inout_index = idx;
+        return 0;
+    }
+
+    int upper_total = overlay_count_upper(dir);
+    /* Lower-phase index: subtract upper_total from our global index */
+    uint32_t lower_idx = (idx >= (uint32_t)upper_total) ? idx - (uint32_t)upper_total : 0;
+
+    while (written < cap) {
+        struct vfs_dirent tmp;
+        uint32_t tmp_idx = lower_idx;
+        int rc = dir->lower->i_ops->readdir(dir->lower, &tmp_idx, &tmp, sizeof(tmp));
+        if (rc <= 0) break;
+        lower_idx = tmp_idx;
+        /* Skip . and .. (already emitted by upper) and entries that exist in upper */
+        if (strcmp(tmp.d_name, ".") == 0 || strcmp(tmp.d_name, "..") == 0) continue;
+        if (overlay_upper_has_name(dir, tmp.d_name)) continue;
+        ents[written++] = tmp;
+    }
+
+    *inout_index = (uint32_t)upper_total + lower_idx;
+    return (int)(written * (uint32_t)sizeof(struct vfs_dirent));
 }
 
 static struct fs_node* overlay_finddir_impl(struct fs_node* node, const char* name) {
@@ -191,6 +254,46 @@ static struct fs_node* overlay_finddir_impl(struct fs_node* node, const char* na
     return overlay_wrap_child(dir, name, lower_child, upper_child);
 }
 
+static int overlay_mkdir_impl(struct fs_node* dir, const char* name) {
+    if (!dir || !name) return -EINVAL;
+    struct overlay_node* on = (struct overlay_node*)dir;
+    if (!on->upper) return -EROFS;
+    if (on->upper->i_ops && on->upper->i_ops->mkdir)
+        return on->upper->i_ops->mkdir(on->upper, name);
+    return -ENOSYS;
+}
+
+static int overlay_unlink_impl(struct fs_node* dir, const char* name) {
+    if (!dir || !name) return -EINVAL;
+    struct overlay_node* on = (struct overlay_node*)dir;
+    /* Try upper layer first */
+    if (on->upper && on->upper->i_ops && on->upper->i_ops->unlink) {
+        int rc = on->upper->i_ops->unlink(on->upper, name);
+        if (rc == 0 || rc != -ENOENT) return rc;
+    }
+    /* File only in lower (read-only) layer — cannot delete */
+    return -EROFS;
+}
+
+static int overlay_rmdir_impl(struct fs_node* dir, const char* name) {
+    if (!dir || !name) return -EINVAL;
+    struct overlay_node* on = (struct overlay_node*)dir;
+    if (on->upper && on->upper->i_ops && on->upper->i_ops->rmdir) {
+        int rc = on->upper->i_ops->rmdir(on->upper, name);
+        if (rc == 0 || rc != -ENOENT) return rc;
+    }
+    return -EROFS;
+}
+
+static int overlay_create_impl(struct fs_node* dir, const char* name, uint32_t flags, struct fs_node** out) {
+    if (!dir || !name || !out) return -EINVAL;
+    struct overlay_node* on = (struct overlay_node*)dir;
+    if (!on->upper) return -EROFS;
+    if (on->upper->i_ops && on->upper->i_ops->create)
+        return on->upper->i_ops->create(on->upper, name, flags, out);
+    return -ENOSYS;
+}
+
 fs_node_t* overlayfs_create_root(fs_node_t* lower_root, fs_node_t* upper_root) {
     if (!lower_root || !upper_root) return NULL;
 
index 7af36042aa5b2f722e16f622c3ad4bda7f2a9ba6..05cb34e1ec0e989afec2fe329e29d8ab57d4ef84 100644 (file)
@@ -1,6 +1,7 @@
 #include "procfs.h"
 
 #include "process.h"
+#include "spinlock.h"
 #include "utils.h"
 #include "heap.h"
 #include "pmm.h"
@@ -23,16 +24,10 @@ static fs_node_t g_pid_maps[PID_NODE_POOL];
 static uint32_t g_pid_pool_idx = 0;
 
 extern struct process* ready_queue_head;
+extern spinlock_t sched_lock;
 
-static struct process* proc_find_pid(uint32_t pid) {
-    if (!ready_queue_head) return NULL;
-    struct process* it = ready_queue_head;
-    const struct process* start = it;
-    do {
-        if (it->pid == pid) return it;
-        it = it->next;
-    } while (it && it != start);
-    return NULL;
+static struct process* proc_find_pid_safe(uint32_t pid) {
+    return process_find_by_pid(pid);
 }
 
 static int proc_snprintf(char* buf, uint32_t sz, const char* key, uint32_t val) {
@@ -137,15 +132,19 @@ static uint32_t proc_meminfo_read(fs_node_t* node, uint32_t offset, uint32_t siz
     char tmp[256];
     uint32_t len = 0;
 
-    /* Count processes */
+    /* Count processes (under sched_lock to avoid race) */
     uint32_t nprocs = 0;
-    if (ready_queue_head) {
-        struct process* it = ready_queue_head;
-        const struct process* start = it;
-        do {
-            nprocs++;
-            it = it->next;
-        } while (it && it != start);
+    {
+        uintptr_t fl = spin_lock_irqsave(&sched_lock);
+        if (ready_queue_head) {
+            struct process* it = ready_queue_head;
+            const struct process* start = it;
+            do {
+                nprocs++;
+                it = it->next;
+            } while (it && it != start);
+        }
+        spin_unlock_irqrestore(&sched_lock, fl);
     }
 
     len += (uint32_t)proc_snprintf(tmp + len, sizeof(tmp) - len, "Processes:\t", nprocs);
@@ -162,7 +161,7 @@ static uint32_t proc_meminfo_read(fs_node_t* node, uint32_t offset, uint32_t siz
 
 static uint32_t proc_pid_status_read(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
     uint32_t pid = node->inode;
-    struct process* p = proc_find_pid(pid);
+    struct process* p = proc_find_pid_safe(pid);
     if (!p) return 0;
 
     char tmp[512];
@@ -198,11 +197,29 @@ static uint32_t proc_pid_status_read(fs_node_t* node, uint32_t offset, uint32_t
     return size;
 }
 
+/* --- per-PID cmdline read (inode == target pid) --- */
+
+static uint32_t proc_pid_cmdline_read(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
+    uint32_t pid = node->inode;
+    struct process* p = proc_find_pid_safe(pid);
+    if (!p) return 0;
+
+    uint32_t len = (uint32_t)strlen(p->cmdline);
+    char tmp[256];
+    memcpy(tmp, p->cmdline, len);
+    if (len + 1 < sizeof(tmp)) { tmp[len] = '\n'; len++; }
+    if (offset >= len) return 0;
+    uint32_t avail = len - offset;
+    if (size > avail) size = avail;
+    memcpy(buffer, tmp + offset, size);
+    return size;
+}
+
 /* --- per-PID maps read (inode == target pid) --- */
 
 static uint32_t proc_pid_maps_read(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
     uint32_t pid = node->inode;
-    struct process* p = proc_find_pid(pid);
+    struct process* p = proc_find_pid_safe(pid);
     if (!p) return 0;
 
     char tmp[1024];
@@ -238,6 +255,7 @@ static const struct file_operations procfs_pid_dir_fops;
 static const struct inode_operations procfs_pid_dir_iops;
 static const struct file_operations procfs_pid_status_fops;
 static const struct file_operations procfs_pid_maps_fops;
+static const struct file_operations procfs_pid_cmdline_fops;
 
 static fs_node_t* proc_pid_finddir(fs_node_t* node, const char* name) {
     uint32_t pid = node->inode;
@@ -261,6 +279,15 @@ static fs_node_t* proc_pid_finddir(fs_node_t* node, const char* name) {
         g_pid_maps[slot].f_ops = &procfs_pid_maps_fops;
         return &g_pid_maps[slot];
     }
+    if (strcmp(name, "cmdline") == 0) {
+        g_pid_pool_idx = (slot + 1) % PID_NODE_POOL;
+        memset(&g_pid_status[slot], 0, sizeof(fs_node_t));
+        strcpy(g_pid_status[slot].name, "cmdline");
+        g_pid_status[slot].flags = FS_FILE;
+        g_pid_status[slot].inode = pid;
+        g_pid_status[slot].f_ops = &procfs_pid_cmdline_fops;
+        return &g_pid_status[slot];
+    }
     return NULL;
 }
 
@@ -269,9 +296,9 @@ static int proc_pid_readdir(fs_node_t* node, uint32_t* inout_index, void* buf, u
     if (!inout_index || !buf) return -1;
     if (buf_len < sizeof(struct vfs_dirent)) return -1;
 
-    static const char* entries[] = { "status", "maps" };
+    static const char* entries[] = { "status", "maps", "cmdline" };
     uint32_t idx = *inout_index;
-    if (idx >= 2) return 0;
+    if (idx >= 3) return 0;
 
     struct vfs_dirent* d = (struct vfs_dirent*)buf;
     d->d_ino = 300 + idx;
@@ -288,7 +315,7 @@ static int proc_pid_readdir(fs_node_t* node, uint32_t* inout_index, void* buf, u
 }
 
 static fs_node_t* proc_get_pid_dir(uint32_t pid) {
-    if (!proc_find_pid(pid)) return NULL;
+    if (!proc_find_pid_safe(pid)) return NULL;
     uint32_t slot = g_pid_pool_idx;
     g_pid_pool_idx = (slot + 1) % PID_NODE_POOL;
     memset(&g_pid_dir[slot], 0, sizeof(fs_node_t));
@@ -376,31 +403,37 @@ static int proc_root_readdir(fs_node_t* node, uint32_t* inout_index, void* buf,
         return (int)sizeof(struct vfs_dirent);
     }
 
-    /* After fixed entries, list numeric PIDs */
+    /* After fixed entries, list numeric PIDs (under sched_lock) */
     uint32_t pi = idx - 4;
     uint32_t count = 0;
-    if (ready_queue_head) {
-        struct process* it = ready_queue_head;
-        const struct process* start = it;
-        do {
-            if (count == pi) {
-                char num[16];
-                itoa(it->pid, num, 10);
-                d->d_ino = 400 + it->pid;
-                d->d_type = FS_DIRECTORY;
-                d->d_reclen = sizeof(struct vfs_dirent);
-                uint32_t j = 0;
-                while (num[j] && j + 1 < sizeof(d->d_name) && j < sizeof(num) - 1) { d->d_name[j] = num[j]; j++; }
-                d->d_name[j] = 0;
-                *inout_index = idx + 1;
-                return (int)sizeof(struct vfs_dirent);
-            }
-            count++;
-            it = it->next;
-        } while (it && it != start);
+    int found = 0;
+    {
+        uintptr_t fl = spin_lock_irqsave(&sched_lock);
+        if (ready_queue_head) {
+            struct process* it = ready_queue_head;
+            const struct process* start = it;
+            do {
+                if (count == pi) {
+                    char num[16];
+                    itoa(it->pid, num, 10);
+                    d->d_ino = 400 + it->pid;
+                    d->d_type = FS_DIRECTORY;
+                    d->d_reclen = sizeof(struct vfs_dirent);
+                    uint32_t j = 0;
+                    while (num[j] && j + 1 < sizeof(d->d_name) && j < sizeof(num) - 1) { d->d_name[j] = num[j]; j++; }
+                    d->d_name[j] = 0;
+                    *inout_index = idx + 1;
+                    found = 1;
+                    break;
+                }
+                count++;
+                it = it->next;
+            } while (it && it != start);
+        }
+        spin_unlock_irqrestore(&sched_lock, fl);
     }
 
-    return 0;
+    return found ? (int)sizeof(struct vfs_dirent) : 0;
 }
 
 /* --- file_operations tables --- */
@@ -450,6 +483,10 @@ static const struct file_operations procfs_pid_maps_fops = {
     .read = proc_pid_maps_read,
 };
 
+static const struct file_operations procfs_pid_cmdline_fops = {
+    .read = proc_pid_cmdline_read,
+};
+
 fs_node_t* procfs_create_root(void) {
     memset(&g_proc_root, 0, sizeof(g_proc_root));
     strcpy(g_proc_root.name, "proc");
index 504a1b68ec885e4d1d11dd54982a654b54c892c7..b8a55699510796cd16189a6ea5591235b5fbce65 100644 (file)
@@ -22,7 +22,7 @@ struct process* ready_queue_tail = NULL;
 static uint32_t next_pid = 1;
 static uint32_t init_pid = 0;  /* PID of the first userspace process ("init") */
 
-static spinlock_t sched_lock = {0};
+spinlock_t sched_lock = {0};
 static uintptr_t kernel_as = 0;
 
 /*
index ab4b86998dccdd58f47cb1dfca19f8b9b873a5bb..a9cb43e2fa5bc1dcc84cbc1f941b4ff539fcc975 100644 (file)
@@ -1950,6 +1950,8 @@ static int syscall_execve_impl(struct registers* regs, const char* user_path, co
     current_process->addr_space = new_as;
     current_process->heap_start = heap_brk;
     current_process->heap_break = heap_brk;
+    strncpy(current_process->cmdline, path, sizeof(current_process->cmdline) - 1);
+    current_process->cmdline[sizeof(current_process->cmdline) - 1] = '\0';
     vmm_as_activate(new_as);
 
     // Build a minimal initial user stack: argc, argv pointers, envp pointers, strings.
index 124c27799ae2d700f33359865f22511ab7f9078f..dffc7c0c06dce38767352de3e3395d8880c339c2 100644 (file)
@@ -20,6 +20,10 @@ static struct fs_node* tmpfs_finddir_impl(struct fs_node* node, const char* name
 static int tmpfs_readdir_impl(struct fs_node* node, uint32_t* inout_index, void* buf, uint32_t buf_len);
 static uint32_t tmpfs_read_impl(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer);
 static uint32_t tmpfs_write_impl(fs_node_t* node, uint32_t offset, uint32_t size, const uint8_t* buffer);
+static int tmpfs_mkdir_impl(struct fs_node* dir, const char* name);
+static int tmpfs_unlink_impl(struct fs_node* dir, const char* name);
+static int tmpfs_rmdir_impl(struct fs_node* dir, const char* name);
+static int tmpfs_create_impl(struct fs_node* dir, const char* name, uint32_t flags, struct fs_node** out);
 
 static const struct file_operations tmpfs_file_ops = {
     .read    = tmpfs_read_impl,
@@ -31,6 +35,10 @@ static const struct file_operations tmpfs_dir_ops = {0};
 static const struct inode_operations tmpfs_dir_iops = {
     .lookup  = tmpfs_finddir_impl,
     .readdir = tmpfs_readdir_impl,
+    .mkdir   = tmpfs_mkdir_impl,
+    .unlink  = tmpfs_unlink_impl,
+    .rmdir   = tmpfs_rmdir_impl,
+    .create  = tmpfs_create_impl,
 };
 
 static struct tmpfs_node* tmpfs_node_alloc(const char* name, uint32_t flags) {
@@ -202,6 +210,73 @@ static int tmpfs_readdir_impl(struct fs_node* node, uint32_t* inout_index, void*
     return (int)(written * (uint32_t)sizeof(struct vfs_dirent));
 }
 
+static int tmpfs_mkdir_impl(struct fs_node* dir, const char* name) {
+    if (!dir || !name || dir->flags != FS_DIRECTORY) return -EINVAL;
+    struct tmpfs_node* d = (struct tmpfs_node*)dir;
+    if (tmpfs_child_find(d, name)) return -EEXIST;
+    struct tmpfs_node* nd = tmpfs_node_alloc(name, FS_DIRECTORY);
+    if (!nd) return -ENOMEM;
+    nd->vfs.f_ops = &tmpfs_dir_ops;
+    nd->vfs.i_ops = &tmpfs_dir_iops;
+    tmpfs_child_add(d, nd);
+    return 0;
+}
+
+static int tmpfs_unlink_impl(struct fs_node* dir, const char* name) {
+    if (!dir || !name || dir->flags != FS_DIRECTORY) return -EINVAL;
+    struct tmpfs_node* d = (struct tmpfs_node*)dir;
+    struct tmpfs_node* prev = NULL;
+    struct tmpfs_node* c = d->first_child;
+    while (c) {
+        if (strcmp(c->vfs.name, name) == 0) break;
+        prev = c;
+        c = c->next_sibling;
+    }
+    if (!c) return -ENOENT;
+    if (c->vfs.flags == FS_DIRECTORY) return -EISDIR;
+    if (prev) prev->next_sibling = c->next_sibling;
+    else d->first_child = c->next_sibling;
+    if (c->data) kfree(c->data);
+    kfree(c);
+    return 0;
+}
+
+static int tmpfs_rmdir_impl(struct fs_node* dir, const char* name) {
+    if (!dir || !name || dir->flags != FS_DIRECTORY) return -EINVAL;
+    struct tmpfs_node* d = (struct tmpfs_node*)dir;
+    struct tmpfs_node* prev = NULL;
+    struct tmpfs_node* c = d->first_child;
+    while (c) {
+        if (strcmp(c->vfs.name, name) == 0) break;
+        prev = c;
+        c = c->next_sibling;
+    }
+    if (!c) return -ENOENT;
+    if (c->vfs.flags != FS_DIRECTORY) return -ENOTDIR;
+    if (c->first_child) return -ENOTEMPTY;
+    if (prev) prev->next_sibling = c->next_sibling;
+    else d->first_child = c->next_sibling;
+    kfree(c);
+    return 0;
+}
+
+static int tmpfs_create_impl(struct fs_node* dir, const char* name, uint32_t flags, struct fs_node** out) {
+    if (!dir || !name || !out || dir->flags != FS_DIRECTORY) return -EINVAL;
+    struct tmpfs_node* d = (struct tmpfs_node*)dir;
+    struct tmpfs_node* existing = tmpfs_child_find(d, name);
+    if (existing) {
+        *out = &existing->vfs;
+        return 0;
+    }
+    if (!(flags & 0x40U)) return -ENOENT; /* O_CREAT */
+    struct tmpfs_node* f = tmpfs_node_alloc(name, FS_FILE);
+    if (!f) return -ENOMEM;
+    f->vfs.f_ops = &tmpfs_file_ops;
+    tmpfs_child_add(d, f);
+    *out = &f->vfs;
+    return 0;
+}
+
 fs_node_t* tmpfs_create_root(void) {
     struct tmpfs_node* root = tmpfs_node_alloc("", FS_DIRECTORY);
     if (!root) return NULL;
diff --git a/user/basename.c b/user/basename.c
new file mode 100644 (file)
index 0000000..8089c29
--- /dev/null
@@ -0,0 +1,28 @@
+/* AdrOS basename utility — strip directory from filename */
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "usage: basename PATH [SUFFIX]\n");
+        return 1;
+    }
+    char* p = argv[1];
+    /* Remove trailing slashes */
+    int len = (int)strlen(p);
+    while (len > 1 && p[len - 1] == '/') p[--len] = '\0';
+    /* Find last slash */
+    char* base = p;
+    for (char* s = p; *s; s++) {
+        if (*s == '/' && *(s + 1)) base = s + 1;
+    }
+    /* Strip suffix if provided */
+    if (argc > 2) {
+        int blen = (int)strlen(base);
+        int slen = (int)strlen(argv[2]);
+        if (blen > slen && strcmp(base + blen - slen, argv[2]) == 0)
+            base[blen - slen] = '\0';
+    }
+    printf("%s\n", base);
+    return 0;
+}
diff --git a/user/clear.c b/user/clear.c
new file mode 100644 (file)
index 0000000..619feb4
--- /dev/null
@@ -0,0 +1,8 @@
+/* AdrOS clear utility — clear the terminal screen */
+#include <unistd.h>
+
+int main(void) {
+    /* ANSI escape: clear screen + move cursor to top-left */
+    write(STDOUT_FILENO, "\033[2J\033[H", 7);
+    return 0;
+}
index 42cc1c2e4e3e1a2a6b13fc9e873437b4692bcd35..47d4d5dc8a84ba3f65ae2f1df054844d90c85045 100644 (file)
@@ -79,9 +79,15 @@ int main(int argc, char** argv) {
         if (strcmp(argv[i], "-d") == 0 && i + 1 < argc) {
             delim = argv[++i][0];
             start = i + 1;
+        } else if (strncmp(argv[i], "-d", 2) == 0 && argv[i][2] != '\0') {
+            delim = argv[i][2];
+            start = i + 1;
         } else if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) {
             parse_fields(argv[++i]);
             start = i + 1;
+        } else if (strncmp(argv[i], "-f", 2) == 0 && argv[i][2] != '\0') {
+            parse_fields(argv[i] + 2);
+            start = i + 1;
         } else if (argv[i][0] != '-') {
             break;
         }
diff --git a/user/dd.c b/user/dd.c
new file mode 100644 (file)
index 0000000..eef0d8c
--- /dev/null
+++ b/user/dd.c
@@ -0,0 +1,63 @@
+/* AdrOS dd utility — convert and copy a file */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+static int parse_size(const char* s) {
+    int v = atoi(s);
+    int len = (int)strlen(s);
+    if (len > 0) {
+        char suf = s[len - 1];
+        if (suf == 'k' || suf == 'K') v *= 1024;
+        else if (suf == 'm' || suf == 'M') v *= 1024 * 1024;
+    }
+    return v;
+}
+
+int main(int argc, char** argv) {
+    const char* inf = NULL;
+    const char* outf = NULL;
+    int bs = 512;
+    int count = -1;
+
+    for (int i = 1; i < argc; i++) {
+        if (strncmp(argv[i], "if=", 3) == 0) inf = argv[i] + 3;
+        else if (strncmp(argv[i], "of=", 3) == 0) outf = argv[i] + 3;
+        else if (strncmp(argv[i], "bs=", 3) == 0) bs = parse_size(argv[i] + 3);
+        else if (strncmp(argv[i], "count=", 6) == 0) count = atoi(argv[i] + 6);
+    }
+
+    int ifd = STDIN_FILENO;
+    int ofd = STDOUT_FILENO;
+
+    if (inf) {
+        ifd = open(inf, O_RDONLY);
+        if (ifd < 0) { fprintf(stderr, "dd: cannot open '%s'\n", inf); return 1; }
+    }
+    if (outf) {
+        ofd = open(outf, O_WRONLY | O_CREAT | O_TRUNC);
+        if (ofd < 0) { fprintf(stderr, "dd: cannot open '%s'\n", outf); return 1; }
+    }
+
+    if (bs > 4096) bs = 4096;
+    char buf[4096];
+    int blocks = 0, partial = 0, total = 0;
+
+    while (count < 0 || blocks + partial < count) {
+        int n = read(ifd, buf, (size_t)bs);
+        if (n <= 0) break;
+        write(ofd, buf, (size_t)n);
+        total += n;
+        if (n == bs) blocks++;
+        else partial++;
+    }
+
+    fprintf(stderr, "%d+%d records in\n%d+%d records out\n%d bytes copied\n",
+            blocks, partial, blocks, partial, total);
+
+    if (inf) close(ifd);
+    if (outf) close(ofd);
+    return 0;
+}
diff --git a/user/df.c b/user/df.c
new file mode 100644 (file)
index 0000000..81d0a87
--- /dev/null
+++ b/user/df.c
@@ -0,0 +1,10 @@
+/* AdrOS df utility — display filesystem disk space usage */
+#include <stdio.h>
+
+int main(void) {
+    printf("Filesystem     Size  Used  Avail  Use%%  Mounted on\n");
+    printf("overlayfs         -     -      -     -  /\n");
+    printf("devfs             -     -      -     -  /dev\n");
+    printf("procfs            -     -      -     -  /proc\n");
+    return 0;
+}
diff --git a/user/dirname.c b/user/dirname.c
new file mode 100644 (file)
index 0000000..15e518d
--- /dev/null
@@ -0,0 +1,22 @@
+/* AdrOS dirname utility — strip last component from path */
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "usage: dirname PATH\n");
+        return 1;
+    }
+    char* p = argv[1];
+    int len = (int)strlen(p);
+    /* Remove trailing slashes */
+    while (len > 1 && p[len - 1] == '/') len--;
+    /* Find last slash */
+    while (len > 0 && p[len - 1] != '/') len--;
+    /* Remove trailing slashes from result */
+    while (len > 1 && p[len - 1] == '/') len--;
+    if (len == 0) { printf(".\n"); return 0; }
+    p[len] = '\0';
+    printf("%s\n", p);
+    return 0;
+}
diff --git a/user/dmesg.c b/user/dmesg.c
new file mode 100644 (file)
index 0000000..b2aacc4
--- /dev/null
@@ -0,0 +1,18 @@
+/* AdrOS dmesg utility — print kernel ring buffer from /proc/dmesg */
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+int main(void) {
+    int fd = open("/proc/dmesg", O_RDONLY);
+    if (fd < 0) {
+        fprintf(stderr, "dmesg: cannot open /proc/dmesg\n");
+        return 1;
+    }
+    char buf[512];
+    int n;
+    while ((n = read(fd, buf, sizeof(buf))) > 0)
+        write(STDOUT_FILENO, buf, (size_t)n);
+    close(fd);
+    return 0;
+}
diff --git a/user/env.c b/user/env.c
new file mode 100644 (file)
index 0000000..a7d727a
--- /dev/null
@@ -0,0 +1,21 @@
+/* AdrOS env utility — print environment or run command with modified env */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+extern char** __environ;
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        /* Print all environment variables */
+        if (__environ) {
+            for (int i = 0; __environ[i]; i++)
+                printf("%s\n", __environ[i]);
+        }
+        return 0;
+    }
+    /* env COMMAND ARGS... — run command with current environment */
+    execve(argv[1], (const char* const*)&argv[1], (const char* const*)__environ);
+    fprintf(stderr, "env: %s: not found\n", argv[1]);
+    return 127;
+}
diff --git a/user/free.c b/user/free.c
new file mode 100644 (file)
index 0000000..b07a8bc
--- /dev/null
@@ -0,0 +1,17 @@
+/* AdrOS free utility — display memory usage from /proc/meminfo */
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+int main(void) {
+    int fd = open("/proc/meminfo", O_RDONLY);
+    if (fd >= 0) {
+        char buf[512];
+        int n = read(fd, buf, sizeof(buf) - 1);
+        if (n > 0) { buf[n] = '\0'; printf("%s", buf); }
+        close(fd);
+    } else {
+        printf("free: /proc/meminfo not available\n");
+    }
+    return 0;
+}
diff --git a/user/grep.c b/user/grep.c
new file mode 100644 (file)
index 0000000..1f755ee
--- /dev/null
@@ -0,0 +1,79 @@
+/* AdrOS grep utility — search for pattern in files */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+static int match_simple(const char* text, const char* pat) {
+    /* Simple substring match (no regex) */
+    return strstr(text, pat) != NULL;
+}
+
+static int grep_fd(int fd, const char* pattern, const char* fname, int show_name, int invert, int count_only, int line_num) {
+    char buf[4096];
+    int pos = 0, n, matches = 0, lnum = 0;
+    while ((n = read(fd, buf + pos, (size_t)(sizeof(buf) - 1 - pos))) > 0) {
+        pos += n;
+        buf[pos] = '\0';
+        char* start = buf;
+        char* nl;
+        while ((nl = strchr(start, '\n')) != NULL) {
+            *nl = '\0';
+            lnum++;
+            int m = match_simple(start, pattern);
+            if (invert) m = !m;
+            if (m) {
+                matches++;
+                if (!count_only) {
+                    if (show_name) printf("%s:", fname);
+                    if (line_num) printf("%d:", lnum);
+                    printf("%s\n", start);
+                }
+            }
+            start = nl + 1;
+        }
+        int rem = (int)(buf + pos - start);
+        if (rem > 0) memmove(buf, start, (size_t)rem);
+        pos = rem;
+    }
+    if (pos > 0) {
+        buf[pos] = '\0';
+        lnum++;
+        int m = match_simple(buf, pattern);
+        if (invert) m = !m;
+        if (m) {
+            matches++;
+            if (!count_only) {
+                if (show_name) printf("%s:", fname);
+                if (line_num) printf("%d:", lnum);
+                printf("%s\n", buf);
+            }
+        }
+    }
+    if (count_only) printf("%s%s%d\n", show_name ? fname : "", show_name ? ":" : "", matches);
+    return matches > 0 ? 0 : 1;
+}
+
+int main(int argc, char** argv) {
+    int invert = 0, count_only = 0, line_num = 0;
+    int i = 1;
+    while (i < argc && argv[i][0] == '-') {
+        for (int j = 1; argv[i][j]; j++) {
+            if (argv[i][j] == 'v') invert = 1;
+            else if (argv[i][j] == 'c') count_only = 1;
+            else if (argv[i][j] == 'n') line_num = 1;
+        }
+        i++;
+    }
+    if (i >= argc) { fprintf(stderr, "usage: grep [-vcn] PATTERN [FILE...]\n"); return 2; }
+    const char* pattern = argv[i++];
+    if (i >= argc) return grep_fd(STDIN_FILENO, pattern, "(stdin)", 0, invert, count_only, line_num);
+    int rc = 1, nfiles = argc - i;
+    for (; i < argc; i++) {
+        int fd = open(argv[i], O_RDONLY);
+        if (fd < 0) { fprintf(stderr, "grep: %s: No such file or directory\n", argv[i]); continue; }
+        if (grep_fd(fd, pattern, argv[i], nfiles > 1, invert, count_only, line_num) == 0) rc = 0;
+        close(fd);
+    }
+    return rc;
+}
diff --git a/user/id.c b/user/id.c
new file mode 100644 (file)
index 0000000..31b51ab
--- /dev/null
+++ b/user/id.c
@@ -0,0 +1,9 @@
+/* AdrOS id utility — display user and group IDs */
+#include <stdio.h>
+#include <unistd.h>
+
+int main(void) {
+    printf("uid=%d gid=%d euid=%d egid=%d\n",
+           getuid(), getgid(), geteuid(), getegid());
+    return 0;
+}
diff --git a/user/kill.c b/user/kill.c
new file mode 100644 (file)
index 0000000..7cc1e95
--- /dev/null
@@ -0,0 +1,41 @@
+/* AdrOS kill utility — send signal to process */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "usage: kill [-SIGNAL] PID...\n");
+        return 1;
+    }
+
+    int sig = 15; /* SIGTERM */
+    int start = 1;
+
+    if (argv[1][0] == '-') {
+        const char* s = argv[1] + 1;
+        if (strcmp(s, "9") == 0 || strcmp(s, "KILL") == 0) sig = 9;
+        else if (strcmp(s, "15") == 0 || strcmp(s, "TERM") == 0) sig = 15;
+        else if (strcmp(s, "2") == 0 || strcmp(s, "INT") == 0) sig = 2;
+        else if (strcmp(s, "1") == 0 || strcmp(s, "HUP") == 0) sig = 1;
+        else if (strcmp(s, "0") == 0) sig = 0;
+        else sig = atoi(s);
+        start = 2;
+    }
+
+    int rc = 0;
+    for (int i = start; i < argc; i++) {
+        int pid = atoi(argv[i]);
+        if (pid <= 0) {
+            fprintf(stderr, "kill: invalid pid '%s'\n", argv[i]);
+            rc = 1;
+            continue;
+        }
+        if (kill(pid, sig) < 0) {
+            fprintf(stderr, "kill: %d: no such process\n", pid);
+            rc = 1;
+        }
+    }
+    return rc;
+}
diff --git a/user/mount.c b/user/mount.c
new file mode 100644 (file)
index 0000000..970e327
--- /dev/null
@@ -0,0 +1,23 @@
+/* AdrOS mount utility — display mounted filesystems */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+int main(int argc, char** argv) {
+    (void)argc; (void)argv;
+    /* Read /proc/mounts if available, otherwise show static info */
+    int fd = open("/proc/mounts", O_RDONLY);
+    if (fd >= 0) {
+        char buf[1024];
+        int n;
+        while ((n = read(fd, buf, sizeof(buf))) > 0)
+            write(STDOUT_FILENO, buf, (size_t)n);
+        close(fd);
+    } else {
+        printf("tmpfs on / type overlayfs (rw)\n");
+        printf("devfs on /dev type devfs (rw)\n");
+        printf("procfs on /proc type procfs (ro)\n");
+    }
+    return 0;
+}
diff --git a/user/printenv.c b/user/printenv.c
new file mode 100644 (file)
index 0000000..0e3f081
--- /dev/null
@@ -0,0 +1,27 @@
+/* AdrOS printenv utility — print environment variables */
+#include <stdio.h>
+#include <string.h>
+
+extern char** __environ;
+
+int main(int argc, char** argv) {
+    if (!__environ) return 1;
+    if (argc <= 1) {
+        for (int i = 0; __environ[i]; i++)
+            printf("%s\n", __environ[i]);
+        return 0;
+    }
+    for (int i = 1; i < argc; i++) {
+        int found = 0;
+        int nlen = (int)strlen(argv[i]);
+        for (int j = 0; __environ[j]; j++) {
+            if (strncmp(__environ[j], argv[i], (size_t)nlen) == 0 && __environ[j][nlen] == '=') {
+                printf("%s\n", __environ[j] + nlen + 1);
+                found = 1;
+                break;
+            }
+        }
+        if (!found) return 1;
+    }
+    return 0;
+}
diff --git a/user/ps.c b/user/ps.c
new file mode 100644 (file)
index 0000000..f9d8d5b
--- /dev/null
+++ b/user/ps.c
@@ -0,0 +1,42 @@
+/* AdrOS ps utility — list processes from /proc */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+
+static int is_digit(char c) { return c >= '0' && c <= '9'; }
+
+int main(void) {
+    printf("  PID CMD\n");
+    int fd = open("/proc", O_RDONLY);
+    if (fd < 0) {
+        fprintf(stderr, "ps: cannot open /proc\n");
+        return 1;
+    }
+    char buf[512];
+    int rc;
+    while ((rc = getdents(fd, buf, sizeof(buf))) > 0) {
+        int off = 0;
+        while (off < rc) {
+            struct dirent* d = (struct dirent*)(buf + off);
+            if (d->d_reclen == 0) break;
+            if (is_digit(d->d_name[0])) {
+                char path[64];
+                snprintf(path, sizeof(path), "/proc/%s/cmdline", d->d_name);
+                int cfd = open(path, O_RDONLY);
+                char cmd[64] = "?";
+                if (cfd >= 0) {
+                    int n = read(cfd, cmd, sizeof(cmd) - 1);
+                    if (n > 0) cmd[n] = '\0';
+                    else strcpy(cmd, "?");
+                    close(cfd);
+                }
+                printf("%5s %s\n", d->d_name, cmd);
+            }
+            off += d->d_reclen;
+        }
+    }
+    close(fd);
+    return 0;
+}
diff --git a/user/pwd.c b/user/pwd.c
new file mode 100644 (file)
index 0000000..baa6134
--- /dev/null
@@ -0,0 +1,14 @@
+/* AdrOS pwd utility — print working directory */
+#include <stdio.h>
+#include <unistd.h>
+
+int main(void) {
+    char buf[256];
+    if (getcwd(buf, sizeof(buf)) >= 0)
+        printf("%s\n", buf);
+    else {
+        fprintf(stderr, "pwd: error\n");
+        return 1;
+    }
+    return 0;
+}
diff --git a/user/rmdir.c b/user/rmdir.c
new file mode 100644 (file)
index 0000000..0d65e8a
--- /dev/null
@@ -0,0 +1,18 @@
+/* AdrOS rmdir utility — remove empty directories */
+#include <stdio.h>
+#include <unistd.h>
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "rmdir: missing operand\n");
+        return 1;
+    }
+    int rc = 0;
+    for (int i = 1; i < argc; i++) {
+        if (rmdir(argv[i]) < 0) {
+            fprintf(stderr, "rmdir: failed to remove '%s'\n", argv[i]);
+            rc = 1;
+        }
+    }
+    return rc;
+}
index aec3d569291f8d4ce5513fe9d3e756b521522fdd..8df4b2340e45a316ea7deb18566ae2079b5ef395 100644 (file)
--- a/user/sh.c
+++ b/user/sh.c
 #include <string.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <dirent.h>
+#include <termios.h>
+
+static struct termios orig_termios;
+
+static void tty_raw_mode(void) {
+    tcgetattr(STDIN_FILENO, &orig_termios);
+    struct termios raw = orig_termios;
+    raw.c_lflag &= ~(ICANON | ECHO | ISIG);
+    raw.c_cc[VMIN] = 1;
+    raw.c_cc[VTIME] = 0;
+    tcsetattr(STDIN_FILENO, TCSANOW, &raw);
+}
+
+static void tty_restore(void) {
+    tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
+}
 
 #define LINE_MAX   512
 #define MAX_ARGS   64
@@ -107,6 +124,136 @@ static void term_write(const char* s, int n) {
     write(STDOUT_FILENO, s, (size_t)n);
 }
 
+/* ---- Tab completion ---- */
+
+static int tab_complete(char* buf, int* p_pos, int* p_len) {
+    int pos = *p_pos;
+    int len = *p_len;
+
+    /* Find the start of the current word */
+    int wstart = pos;
+    while (wstart > 0 && buf[wstart - 1] != ' ' && buf[wstart - 1] != '\t')
+        wstart--;
+
+    char prefix[128];
+    int plen = pos - wstart;
+    if (plen <= 0 || plen >= (int)sizeof(prefix)) return 0;
+    memcpy(prefix, buf + wstart, (size_t)plen);
+    prefix[plen] = '\0';
+
+    /* Determine if this is a command (first word) or filename */
+    int is_cmd = 1;
+    for (int i = 0; i < wstart; i++) {
+        if (buf[i] != ' ' && buf[i] != '\t') { is_cmd = 0; break; }
+    }
+
+    char match[128];
+    match[0] = '\0';
+    int nmatches = 0;
+
+    /* Split prefix into directory part and name part for file completion */
+    char dirpath[128] = ".";
+    const char* namepfx = prefix;
+    char* lastsep = NULL;
+    for (char* p = prefix; *p; p++) {
+        if (*p == '/') lastsep = p;
+    }
+    if (lastsep) {
+        int dlen = (int)(lastsep - prefix);
+        if (dlen == 0) { dirpath[0] = '/'; dirpath[1] = '\0'; }
+        else { memcpy(dirpath, prefix, (size_t)dlen); dirpath[dlen] = '\0'; }
+        namepfx = lastsep + 1;
+    }
+    int nplen = (int)strlen(namepfx);
+
+    if (!is_cmd || lastsep) {
+        /* File/directory completion */
+        int fd = open(dirpath, 0);
+        if (fd >= 0) {
+            char dbuf[512];
+            int rc;
+            while ((rc = getdents(fd, dbuf, sizeof(dbuf))) > 0) {
+                int off = 0;
+                while (off < rc) {
+                    struct dirent* d = (struct dirent*)(dbuf + off);
+                    if (d->d_reclen == 0) break;
+                    if (d->d_name[0] != '.' || nplen > 0) {
+                        int nlen = (int)strlen(d->d_name);
+                        if (nlen >= nplen && memcmp(d->d_name, namepfx, (size_t)nplen) == 0) {
+                            if (nmatches == 0) strcpy(match, d->d_name);
+                            nmatches++;
+                        }
+                    }
+                    off += d->d_reclen;
+                }
+            }
+            close(fd);
+        }
+    }
+
+    if (is_cmd && !lastsep) {
+        /* Command completion: search PATH directories + builtins */
+        static const char* builtins[] = {
+            "cd", "exit", "echo", "export", "unset", "set", "pwd", "type", NULL
+        };
+        for (int i = 0; builtins[i]; i++) {
+            int blen = (int)strlen(builtins[i]);
+            if (blen >= plen && memcmp(builtins[i], prefix, (size_t)plen) == 0) {
+                if (nmatches == 0) strcpy(match, builtins[i]);
+                nmatches++;
+            }
+        }
+        const char* path_env = var_get("PATH");
+        if (!path_env) path_env = "/bin:/sbin:/usr/bin";
+        char pathcopy[512];
+        strncpy(pathcopy, path_env, sizeof(pathcopy) - 1);
+        pathcopy[sizeof(pathcopy) - 1] = '\0';
+        char* save = pathcopy;
+        char* dir;
+        while ((dir = save) != NULL) {
+            char* sep = strchr(save, ':');
+            if (sep) { *sep = '\0'; save = sep + 1; } else save = NULL;
+            int fd = open(dir, 0);
+            if (fd < 0) continue;
+            char dbuf[512];
+            int rc;
+            while ((rc = getdents(fd, dbuf, sizeof(dbuf))) > 0) {
+                int off = 0;
+                while (off < rc) {
+                    struct dirent* d = (struct dirent*)(dbuf + off);
+                    if (d->d_reclen == 0) break;
+                    int nlen = (int)strlen(d->d_name);
+                    if (nlen >= plen && memcmp(d->d_name, prefix, (size_t)plen) == 0) {
+                        if (nmatches == 0) strcpy(match, d->d_name);
+                        nmatches++;
+                    }
+                    off += d->d_reclen;
+                }
+            }
+            close(fd);
+        }
+    }
+
+    if (nmatches != 1) return 0;
+
+    /* Insert the completion suffix */
+    int mlen = (int)strlen(match);
+    int suffix_len = is_cmd && !lastsep ? mlen - plen : mlen - nplen;
+    const char* suffix = is_cmd && !lastsep ? match + plen : match + nplen;
+    if (suffix_len <= 0 || len + suffix_len >= LINE_MAX - 1) return 0;
+
+    memmove(buf + pos + suffix_len, buf + pos, (size_t)(len - pos));
+    memcpy(buf + pos, suffix, (size_t)suffix_len);
+    len += suffix_len;
+    buf[len] = '\0';
+    term_write(buf + pos, len - pos);
+    pos += suffix_len;
+    for (int i = 0; i < len - pos; i++) term_write("\b", 1);
+    *p_pos = pos;
+    *p_len = len;
+    return 1;
+}
+
 static int read_line_edit(void) {
     int pos = 0;
     int len = 0;
@@ -142,6 +289,12 @@ static int read_line_edit(void) {
             continue;
         }
 
+        /* Tab = autocomplete */
+        if (c == '\t') {
+            tab_complete(line, &pos, &len);
+            continue;
+        }
+
         /* Ctrl+D = EOF */
         if (c == 4) {
             if (len == 0) return -1;
@@ -185,6 +338,33 @@ static int read_line_edit(void) {
             if (seq[0] != '[') continue;
             if (read(STDIN_FILENO, &seq[1], 1) <= 0) continue;
 
+            if (seq[1] >= '0' && seq[1] <= '9') {
+                /* Extended sequence like \x1b[3~ (DELETE), \x1b[1~ (Home), \x1b[4~ (End) */
+                char trail;
+                if (read(STDIN_FILENO, &trail, 1) <= 0) continue;
+                if (trail == '~') {
+                    if (seq[1] == '3') {
+                        /* DELETE key — delete char at cursor */
+                        if (pos < len) {
+                            memmove(line + pos, line + pos + 1, (size_t)(len - pos - 1));
+                            len--;
+                            line[len] = '\0';
+                            term_write(line + pos, len - pos);
+                            term_write(" \b", 2);
+                            for (int i = 0; i < len - pos; i++) term_write("\b", 1);
+                        }
+                    } else if (seq[1] == '1') {
+                        /* Home */
+                        while (pos > 0) { term_write("\b", 1); pos--; }
+                    } else if (seq[1] == '4') {
+                        /* End */
+                        term_write(line + pos, len - pos);
+                        pos = len;
+                    }
+                }
+                continue;
+            }
+
             switch (seq[1]) {
             case 'A':  /* Up arrow — previous history */
                 if (hist_pos > 0 && hist_pos > hist_count - HIST_SIZE) {
@@ -222,6 +402,13 @@ static int read_line_edit(void) {
             case 'D':  /* Left arrow */
                 if (pos > 0) { term_write("\b", 1); pos--; }
                 break;
+            case 'H':  /* Home */
+                while (pos > 0) { term_write("\b", 1); pos--; }
+                break;
+            case 'F':  /* End */
+                term_write(line + pos, len - pos);
+                pos = len;
+                break;
             }
             continue;
         }
@@ -384,6 +571,19 @@ static void run_simple(char* cmd) {
     argc = nargc;
     if (argc == 0) return;
 
+    /* ---- Apply redirections for builtins too ---- */
+    int saved_stdin = -1, saved_stdout = -1;
+    if (redir_in) {
+        int fd = open(redir_in, O_RDONLY);
+        if (fd >= 0) { saved_stdin = dup(0); dup2(fd, 0); close(fd); }
+    }
+    if (redir_out) {
+        int flags = O_WRONLY | O_CREAT;
+        flags |= append ? O_APPEND : O_TRUNC;
+        int fd = open(redir_out, flags);
+        if (fd >= 0) { saved_stdout = dup(1); dup2(fd, 1); close(fd); }
+    }
+
     /* ---- Builtins ---- */
 
     if (strcmp(argv[0], "exit") == 0) {
@@ -401,7 +601,7 @@ static void run_simple(char* cmd) {
             if (getcwd(cwd, sizeof(cwd)) >= 0)
                 var_set("PWD", cwd, 1);
         }
-        return;
+        goto restore_redir;
     }
 
     if (strcmp(argv[0], "pwd") == 0) {
@@ -410,7 +610,7 @@ static void run_simple(char* cmd) {
             printf("%s\n", cwd);
         else
             fprintf(stderr, "pwd: error\n");
-        return;
+        goto restore_redir;
     }
 
     if (strcmp(argv[0], "export") == 0) {
@@ -426,18 +626,18 @@ static void run_simple(char* cmd) {
                         vars[j].exported = 1;
             }
         }
-        return;
+        goto restore_redir;
     }
 
     if (strcmp(argv[0], "unset") == 0) {
         for (int i = 1; i < argc; i++) var_unset(argv[i]);
-        return;
+        goto restore_redir;
     }
 
     if (strcmp(argv[0], "set") == 0) {
         for (int i = 0; i < nvar; i++)
             printf("%s=%s\n", vars[i].name, vars[i].value);
-        return;
+        goto restore_redir;
     }
 
     if (strcmp(argv[0], "echo") == 0) {
@@ -449,7 +649,7 @@ static void run_simple(char* cmd) {
             write(STDOUT_FILENO, argv[i], strlen(argv[i]));
         }
         if (!nflag) write(STDOUT_FILENO, "\n", 1);
-        return;
+        goto restore_redir;
     }
 
     if (strcmp(argv[0], "type") == 0) {
@@ -467,10 +667,10 @@ static void run_simple(char* cmd) {
                     printf("%s: not found\n", argv[i]);
             }
         }
-        return;
+        goto restore_redir;
     }
 
-    /* ---- External command ---- */
+    /* ---- External command — restore parent redirections before fork ---- */
     const char* path = resolve(argv[0]);
     char** envp = build_envp();
 
@@ -498,6 +698,11 @@ static void run_simple(char* cmd) {
     int st;
     waitpid(pid, &st, 0);
     last_status = st;
+    goto restore_redir;
+
+restore_redir:
+    if (saved_stdout >= 0) { dup2(saved_stdout, 1); close(saved_stdout); }
+    if (saved_stdin >= 0)  { dup2(saved_stdin, 0);  close(saved_stdin);  }
 }
 
 /* ---- Pipeline support ---- */
@@ -644,16 +849,21 @@ int main(int argc, char** argv, char** envp) {
     if (!var_get("HOME"))
         var_set("HOME", "/", 1);
 
+    tty_raw_mode();
+
     print_prompt();
     while (1) {
         int len = read_line_edit();
         if (len < 0) break;
         if (len > 0) {
             hist_add(line);
+            tty_restore();
             process_line(line);
+            tty_raw_mode();
         }
         print_prompt();
     }
 
+    tty_restore();
     return last_status;
 }
diff --git a/user/sleep.c b/user/sleep.c
new file mode 100644 (file)
index 0000000..9da1be1
--- /dev/null
@@ -0,0 +1,18 @@
+/* AdrOS sleep utility — pause for N seconds */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "usage: sleep SECONDS\n");
+        return 1;
+    }
+    int secs = atoi(argv[1]);
+    if (secs > 0) {
+        struct timespec ts = { .tv_sec = secs, .tv_nsec = 0 };
+        nanosleep(&ts, NULL);
+    }
+    return 0;
+}
diff --git a/user/stat.c b/user/stat.c
new file mode 100644 (file)
index 0000000..7083d29
--- /dev/null
@@ -0,0 +1,25 @@
+/* AdrOS stat utility — display file status */
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "usage: stat FILE...\n");
+        return 1;
+    }
+    int rc = 0;
+    for (int i = 1; i < argc; i++) {
+        struct stat st;
+        if (stat(argv[i], (void*)&st) < 0) {
+            fprintf(stderr, "stat: cannot stat '%s'\n", argv[i]);
+            rc = 1;
+            continue;
+        }
+        printf("  File: %s\n", argv[i]);
+        printf("  Size: %u\tInode: %u\n", (unsigned)st.st_size, (unsigned)st.st_ino);
+        printf("  Mode: %o\tUid: %u\tGid: %u\n", (unsigned)st.st_mode, (unsigned)st.st_uid, (unsigned)st.st_gid);
+    }
+    return rc;
+}
diff --git a/user/tee.c b/user/tee.c
new file mode 100644 (file)
index 0000000..b85a018
--- /dev/null
@@ -0,0 +1,34 @@
+/* AdrOS tee utility — read stdin, write to stdout and files */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+int main(int argc, char** argv) {
+    int aflag = 0;
+    int fds[16];
+    int nfds = 0;
+
+    for (int i = 1; i < argc && nfds < 16; i++) {
+        if (strcmp(argv[i], "-a") == 0) { aflag = 1; continue; }
+        int flags = O_WRONLY | O_CREAT;
+        flags |= aflag ? O_APPEND : O_TRUNC;
+        int fd = open(argv[i], flags);
+        if (fd < 0) {
+            fprintf(stderr, "tee: %s: cannot open\n", argv[i]);
+            continue;
+        }
+        fds[nfds++] = fd;
+    }
+
+    char buf[4096];
+    int n;
+    while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
+        write(STDOUT_FILENO, buf, (size_t)n);
+        for (int i = 0; i < nfds; i++)
+            write(fds[i], buf, (size_t)n);
+    }
+
+    for (int i = 0; i < nfds; i++) close(fds[i]);
+    return 0;
+}
diff --git a/user/tr.c b/user/tr.c
new file mode 100644 (file)
index 0000000..cf7606f
--- /dev/null
+++ b/user/tr.c
@@ -0,0 +1,44 @@
+/* AdrOS tr utility — translate or delete characters */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+int main(int argc, char** argv) {
+    int delete_mode = 0;
+    int start = 1;
+
+    if (argc > 1 && strcmp(argv[1], "-d") == 0) {
+        delete_mode = 1;
+        start = 2;
+    }
+
+    if (delete_mode) {
+        if (start >= argc) { fprintf(stderr, "usage: tr -d SET1\n"); return 1; }
+        const char* set1 = argv[start];
+        char c;
+        while (read(STDIN_FILENO, &c, 1) > 0) {
+            if (!strchr(set1, c))
+                write(STDOUT_FILENO, &c, 1);
+        }
+    } else {
+        if (start + 1 >= argc) { fprintf(stderr, "usage: tr SET1 SET2\n"); return 1; }
+        const char* set1 = argv[start];
+        const char* set2 = argv[start + 1];
+        int len1 = (int)strlen(set1);
+        int len2 = (int)strlen(set2);
+        char c;
+        while (read(STDIN_FILENO, &c, 1) > 0) {
+            int found = 0;
+            for (int i = 0; i < len1; i++) {
+                if (c == set1[i]) {
+                    char r = (i < len2) ? set2[i] : set2[len2 - 1];
+                    write(STDOUT_FILENO, &r, 1);
+                    found = 1;
+                    break;
+                }
+            }
+            if (!found) write(STDOUT_FILENO, &c, 1);
+        }
+    }
+    return 0;
+}
diff --git a/user/ulibc/include/termios.h b/user/ulibc/include/termios.h
new file mode 100644 (file)
index 0000000..4fdb006
--- /dev/null
@@ -0,0 +1,53 @@
+#ifndef ULIBC_TERMIOS_H
+#define ULIBC_TERMIOS_H
+
+#include <stdint.h>
+
+#define NCCS 11
+
+/* c_lflag bits */
+#define ISIG    0x0001
+#define ICANON  0x0002
+#define ECHO    0x0008
+
+/* c_iflag bits */
+#define ICRNL   0x0100
+#define IGNCR   0x0080
+#define INLCR   0x0040
+
+/* c_oflag bits */
+#define OPOST   0x0001
+#define ONLCR   0x0004
+
+/* c_cc indices */
+#define VINTR   0
+#define VQUIT   1
+#define VERASE  2
+#define VKILL   3
+#define VEOF    4
+#define VSUSP   7
+#define VMIN    8
+#define VTIME   9
+
+/* ioctl commands */
+#define TCGETS   0x5401
+#define TCSETS   0x5402
+#define TCSETSW  0x5403
+#define TCSETSF  0x5404
+
+struct termios {
+    uint32_t c_iflag;
+    uint32_t c_oflag;
+    uint32_t c_cflag;
+    uint32_t c_lflag;
+    uint8_t  c_cc[NCCS];
+};
+
+int tcgetattr(int fd, struct termios* t);
+int tcsetattr(int fd, int actions, const struct termios* t);
+
+#define TCSANOW   0
+#define TCSADRAIN 1
+#define TCSAFLUSH 2
+
+#endif
index 51c34e092c52d5bc62729eb4a6c0179581c6466f..7eee36f1ef1e85612317d8e5d5af376e5b9f1139 100644 (file)
@@ -1,6 +1,7 @@
 #include "unistd.h"
 #include "syscall.h"
 #include "errno.h"
+#include "termios.h"
 
 int read(int fd, void* buf, size_t count) {
     return __syscall_ret(_syscall3(SYS_READ, fd, (int)buf, (int)count));
@@ -215,6 +216,17 @@ int readlink(const char* path, char* buf, size_t bufsiz) {
     return -1;  /* TODO: implement when kernel has SYS_READLINK */
 }
 
+int tcgetattr(int fd, struct termios* t) {
+    return __syscall_ret(_syscall3(SYS_IOCTL, fd, 0x5401 /* TCGETS */, (int)t));
+}
+
+int tcsetattr(int fd, int actions, const struct termios* t) {
+    int cmd = 0x5402; /* TCSETS */
+    if (actions == 1) cmd = 0x5403; /* TCSETSW */
+    else if (actions == 2) cmd = 0x5404; /* TCSETSF */
+    return __syscall_ret(_syscall3(SYS_IOCTL, fd, cmd, (int)t));
+}
+
 void _exit(int status) {
     _syscall1(SYS_EXIT, status);
     /* If exit syscall somehow returns, loop forever.
diff --git a/user/umount.c b/user/umount.c
new file mode 100644 (file)
index 0000000..1396c94
--- /dev/null
@@ -0,0 +1,11 @@
+/* AdrOS umount utility — stub (no SYS_UMOUNT syscall yet) */
+#include <stdio.h>
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "umount: missing operand\n");
+        return 1;
+    }
+    fprintf(stderr, "umount: %s: operation not supported\n", argv[1]);
+    return 1;
+}
diff --git a/user/uname.c b/user/uname.c
new file mode 100644 (file)
index 0000000..250fe05
--- /dev/null
@@ -0,0 +1,34 @@
+/* AdrOS uname utility — print system information */
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char** argv) {
+    const char* sysname = "AdrOS";
+    const char* nodename = "adros";
+    const char* release = "0.1.0";
+    const char* version = "AdrOS x86 SMP";
+    const char* machine = "i686";
+
+    if (argc <= 1) { printf("%s\n", sysname); return 0; }
+
+    int all = 0, s = 0, n = 0, r = 0, v = 0, m = 0;
+    for (int i = 1; i < argc; i++) {
+        if (strcmp(argv[i], "-a") == 0) all = 1;
+        else if (strcmp(argv[i], "-s") == 0) s = 1;
+        else if (strcmp(argv[i], "-n") == 0) n = 1;
+        else if (strcmp(argv[i], "-r") == 0) r = 1;
+        else if (strcmp(argv[i], "-v") == 0) v = 1;
+        else if (strcmp(argv[i], "-m") == 0) m = 1;
+    }
+    if (all) { s = n = r = v = m = 1; }
+    if (!s && !n && !r && !v && !m) s = 1;
+
+    int first = 1;
+    if (s) { printf("%s%s", first ? "" : " ", sysname); first = 0; }
+    if (n) { printf("%s%s", first ? "" : " ", nodename); first = 0; }
+    if (r) { printf("%s%s", first ? "" : " ", release); first = 0; }
+    if (v) { printf("%s%s", first ? "" : " ", version); first = 0; }
+    if (m) { printf("%s%s", first ? "" : " ", machine); first = 0; }
+    printf("\n");
+    return 0;
+}