From: Tulio A M Mendes Date: Tue, 17 Feb 2026 06:20:25 +0000 (-0300) Subject: feat: add sed, awk, who, top, du, find, which commands + shell heredoc support X-Git-Url: https://projects.tadryanom.me/docs/POSIX_ROADMAP.md?a=commitdiff_plain;h=316cf7d7595d65ead55f21c43f0068bafe9c0613;p=AdrOS.git feat: add sed, awk, who, top, du, find, which commands + shell heredoc support --- diff --git a/Makefile b/Makefile index 5bf59c7..60b72bb 100644 --- a/Makefile +++ b/Makefile @@ -118,6 +118,13 @@ ifeq ($(ARCH),x86) DD_ELF := user/dd.elf PWD_ELF := user/pwd.elf STAT_ELF := user/stat.elf + SED_ELF := user/sed.elf + AWK_ELF := user/awk.elf + WHO_ELF := user/who.elf + TOP_ELF := user/top.elf + DU_ELF := user/du.elf + FIND_ELF := user/find.elf + WHICH_ELF := user/which.elf INIT_ELF := user/init.elf LDSO_ELF := user/ld.so ULIBC_SO := user/ulibc/libc.so @@ -399,6 +406,34 @@ $(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 +$(SED_ELF): user/sed.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) + @$(DYN_CC) -c user/sed.c -o user/sed.o + @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/sed.o -lc + +$(AWK_ELF): user/awk.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) + @$(DYN_CC) -c user/awk.c -o user/awk.o + @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/awk.o -lc + +$(WHO_ELF): user/who.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) + @$(DYN_CC) -c user/who.c -o user/who.o + @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/who.o -lc + +$(TOP_ELF): user/top.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) + @$(DYN_CC) -c user/top.c -o user/top.o + @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/top.o -lc + +$(DU_ELF): user/du.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) + @$(DYN_CC) -c user/du.c -o user/du.o + @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/du.o -lc + +$(FIND_ELF): user/find.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) + @$(DYN_CC) -c user/find.c -o user/find.o + @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/find.o -lc + +$(WHICH_ELF): user/which.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) + @$(DYN_CC) -c user/which.c -o user/which.o + @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/which.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 @@ -421,6 +456,8 @@ USER_CMDS := $(ECHO_ELF) $(SH_ELF) $(CAT_ELF) $(LS_ELF) $(MKDIR_ELF) $(RM_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) \ + $(SED_ELF) $(AWK_ELF) $(WHO_ELF) $(TOP_ELF) $(DU_ELF) \ + $(FIND_ELF) $(WHICH_ELF) \ $(INIT_ELF) FSTAB := rootfs/etc/fstab @@ -440,6 +477,8 @@ INITRD_FILES := $(FULLTEST_ELF):sbin/fulltest \ $(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 \ + $(SED_ELF):bin/sed $(AWK_ELF):bin/awk $(WHO_ELF):bin/who \ + $(TOP_ELF):bin/top $(DU_ELF):bin/du $(FIND_ELF):bin/find $(WHICH_ELF):bin/which \ $(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 \ diff --git a/user/awk.c b/user/awk.c new file mode 100644 index 0000000..15be1cb --- /dev/null +++ b/user/awk.c @@ -0,0 +1,116 @@ +/* AdrOS awk utility — minimal: print fields, pattern matching, BEGIN/END */ +#include +#include +#include +#include +#include + +static char delim = ' '; +static int print_field = -1; /* -1 = whole line */ +static char pattern[256] = ""; +static int has_pattern = 0; + +static void process_line(const char* line) { + if (has_pattern && !strstr(line, pattern)) return; + + if (print_field < 0) { + printf("%s\n", line); + return; + } + + /* Split into fields */ + char copy[4096]; + strncpy(copy, line, sizeof(copy) - 1); + copy[sizeof(copy) - 1] = '\0'; + + int fi = 0; + char* p = copy; + while (*p) { + while (*p && (*p == delim || *p == '\t')) p++; + if (!*p) break; + char* start = p; + while (*p && *p != delim && *p != '\t') p++; + if (*p) *p++ = '\0'; + if (fi == print_field) { + printf("%s\n", start); + return; + } + fi++; + } + printf("\n"); +} + +int main(int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "Usage: awk [-F sep] '{print $N}' [file]\n"); + return 1; + } + + int argi = 1; + if (strcmp(argv[argi], "-F") == 0 && argi + 1 < argc) { + delim = argv[argi + 1][0]; + argi += 2; + } + + if (argi >= argc) { + fprintf(stderr, "awk: missing program\n"); + return 1; + } + + /* Parse simple program: {print $N} or /pattern/{print $N} */ + const char* prog = argv[argi++]; + + /* Check for /pattern/ prefix */ + if (prog[0] == '/') { + const char* end = strchr(prog + 1, '/'); + if (end) { + int plen = (int)(end - prog - 1); + if (plen > 0 && plen < (int)sizeof(pattern)) { + memcpy(pattern, prog + 1, plen); + pattern[plen] = '\0'; + has_pattern = 1; + } + prog = end + 1; + } + } + + /* Parse {print $N} */ + const char* pp = strstr(prog, "print"); + if (pp) { + const char* dollar = strchr(pp, '$'); + if (dollar) { + int n = atoi(dollar + 1); + print_field = (n > 0) ? n - 1 : -1; + if (n == 0) print_field = -1; /* $0 = whole line */ + } + } + + int fd = STDIN_FILENO; + if (argi < argc) { + fd = open(argv[argi], O_RDONLY); + if (fd < 0) { + fprintf(stderr, "awk: %s: No such file or directory\n", argv[argi]); + return 1; + } + } + + char line[4096]; + int li = 0; + char c; + while (read(fd, &c, 1) == 1) { + if (c == '\n') { + line[li] = '\0'; + process_line(line); + li = 0; + } else if (li < (int)sizeof(line) - 1) { + line[li++] = c; + } + } + if (li > 0) { + line[li] = '\0'; + process_line(line); + } + + if (fd != STDIN_FILENO) close(fd); + return 0; +} diff --git a/user/du.c b/user/du.c new file mode 100644 index 0000000..8fd15c5 --- /dev/null +++ b/user/du.c @@ -0,0 +1,73 @@ +/* AdrOS du utility — estimate file space usage */ +#include +#include +#include +#include +#include +#include + +static int sflag = 0; /* -s: summary only */ + +static long du_path(const char* path, int print) { + struct stat st; + if (stat(path, &st) < 0) { + fprintf(stderr, "du: cannot access '%s'\n", path); + return 0; + } + + if (!(st.st_mode & 0040000)) { + long blocks = (st.st_size + 511) / 512; + if (print && !sflag) printf("%ld\t%s\n", blocks, path); + return blocks; + } + + int fd = open(path, O_RDONLY); + if (fd < 0) return 0; + + long total = 0; + char buf[2048]; + 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 (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) { + char child[512]; + if (path[strlen(path)-1] == '/') + snprintf(child, sizeof(child), "%s%s", path, d->d_name); + else + snprintf(child, sizeof(child), "%s/%s", path, d->d_name); + total += du_path(child, print); + } + off += d->d_reclen; + } + } + close(fd); + + if (print && !sflag) printf("%ld\t%s\n", total, path); + return total; +} + +int main(int argc, char** argv) { + int argi = 1; + while (argi < argc && argv[argi][0] == '-') { + const char* f = argv[argi] + 1; + while (*f) { + if (*f == 's') sflag = 1; + f++; + } + argi++; + } + + if (argi >= argc) { + long total = du_path(".", 1); + if (sflag) printf("%ld\t.\n", total); + } else { + for (int i = argi; i < argc; i++) { + long total = du_path(argv[i], 1); + if (sflag) printf("%ld\t%s\n", total, argv[i]); + } + } + return 0; +} diff --git a/user/find.c b/user/find.c new file mode 100644 index 0000000..78ca43e --- /dev/null +++ b/user/find.c @@ -0,0 +1,98 @@ +/* AdrOS find utility — search for files in directory hierarchy */ +#include +#include +#include +#include +#include + +static const char* name_pattern = NULL; +static int type_filter = 0; /* 0=any, 'f'=file, 'd'=dir */ + +static int match_name(const char* name) { + if (!name_pattern) return 1; + /* Simple wildcard: *pattern* if pattern has no special chars, + or exact match. Support leading/trailing * only. */ + int plen = (int)strlen(name_pattern); + if (plen == 0) return 1; + + const char* pat = name_pattern; + int lead_star = (pat[0] == '*'); + int trail_star = (plen > 1 && pat[plen-1] == '*'); + + if (lead_star && trail_star) { + char sub[256]; + int slen = plen - 2; + if (slen <= 0) return 1; + memcpy(sub, pat + 1, slen); + sub[slen] = '\0'; + return strstr(name, sub) != NULL; + } + if (lead_star) { + const char* suffix = pat + 1; + int slen = plen - 1; + int nlen = (int)strlen(name); + if (nlen < slen) return 0; + return strcmp(name + nlen - slen, suffix) == 0; + } + if (trail_star) { + return strncmp(name, pat, plen - 1) == 0; + } + return strcmp(name, pat) == 0; +} + +static void find_recurse(const char* path) { + int fd = open(path, O_RDONLY); + if (fd < 0) return; + + char buf[2048]; + 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 (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) { + char child[512]; + if (path[strlen(path)-1] == '/') + snprintf(child, sizeof(child), "%s%s", path, d->d_name); + else + snprintf(child, sizeof(child), "%s/%s", path, d->d_name); + + int show = match_name(d->d_name); + if (show && type_filter) { + if (type_filter == 'f' && d->d_type == 4) show = 0; /* DT_DIR=4 */ + if (type_filter == 'd' && d->d_type != 4) show = 0; + } + if (show) printf("%s\n", child); + + if (d->d_type == 4) { /* DT_DIR */ + find_recurse(child); + } + } + off += d->d_reclen; + } + } + close(fd); +} + +int main(int argc, char** argv) { + const char* start = "."; + int argi = 1; + + if (argi < argc && argv[argi][0] != '-') { + start = argv[argi++]; + } + + while (argi < argc) { + if (strcmp(argv[argi], "-name") == 0 && argi + 1 < argc) { + name_pattern = argv[++argi]; + } else if (strcmp(argv[argi], "-type") == 0 && argi + 1 < argc) { + type_filter = argv[++argi][0]; + } + argi++; + } + + printf("%s\n", start); + find_recurse(start); + return 0; +} diff --git a/user/sed.c b/user/sed.c new file mode 100644 index 0000000..53992f2 --- /dev/null +++ b/user/sed.c @@ -0,0 +1,95 @@ +/* AdrOS sed utility — minimal stream editor (s/pattern/replacement/g only) */ +#include +#include +#include +#include + +static int match_at(const char* s, const char* pat, int patlen) { + for (int i = 0; i < patlen; i++) { + if (s[i] == '\0' || s[i] != pat[i]) return 0; + } + return 1; +} + +static void sed_substitute(const char* line, const char* pat, int patlen, + const char* rep, int replen, int global) { + const char* p = line; + while (*p) { + if (match_at(p, pat, patlen)) { + write(STDOUT_FILENO, rep, replen); + p += patlen; + if (!global) { + write(STDOUT_FILENO, p, strlen(p)); + return; + } + } else { + write(STDOUT_FILENO, p, 1); + p++; + } + } +} + +static int parse_s_cmd(const char* expr, char* pat, int* patlen, + char* rep, int* replen, int* global) { + if (expr[0] != 's' || expr[1] == '\0') return -1; + char delim = expr[1]; + const char* p = expr + 2; + int pi = 0; + while (*p && *p != delim && pi < 255) pat[pi++] = *p++; + pat[pi] = '\0'; *patlen = pi; + if (*p != delim) return -1; + p++; + int ri = 0; + while (*p && *p != delim && ri < 255) rep[ri++] = *p++; + rep[ri] = '\0'; *replen = ri; + *global = 0; + if (*p == delim) { p++; if (*p == 'g') *global = 1; } + return 0; +} + +int main(int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "Usage: sed 's/pattern/replacement/[g]' [file]\n"); + return 1; + } + + char pat[256], rep[256]; + int patlen, replen, global; + int ei = 1; + if (strcmp(argv[1], "-e") == 0 && argc > 2) ei = 2; + + if (parse_s_cmd(argv[ei], pat, &patlen, rep, &replen, &global) < 0) { + fprintf(stderr, "sed: invalid expression: %s\n", argv[ei]); + return 1; + } + + int fd = STDIN_FILENO; + if (argc > ei + 1) { + fd = open(argv[ei + 1], O_RDONLY); + if (fd < 0) { + fprintf(stderr, "sed: %s: No such file or directory\n", argv[ei + 1]); + return 1; + } + } + + char line[4096]; + int li = 0; + char c; + while (read(fd, &c, 1) == 1) { + if (c == '\n') { + line[li] = '\0'; + sed_substitute(line, pat, patlen, rep, replen, global); + write(STDOUT_FILENO, "\n", 1); + li = 0; + } else if (li < (int)sizeof(line) - 1) { + line[li++] = c; + } + } + if (li > 0) { + line[li] = '\0'; + sed_substitute(line, pat, patlen, rep, replen, global); + } + + if (fd != STDIN_FILENO) close(fd); + return 0; +} diff --git a/user/sh.c b/user/sh.c index 8df4b23..64087b6 100644 --- a/user/sh.c +++ b/user/sh.c @@ -555,12 +555,41 @@ static void run_simple(char* cmd) { char* redir_out = NULL; char* redir_in = NULL; int append = 0; + int heredoc_fd = -1; int nargc = 0; for (int i = 0; i < argc; i++) { if (strcmp(argv[i], ">>") == 0 && i + 1 < argc) { redir_out = argv[++i]; append = 1; } else if (strcmp(argv[i], ">") == 0 && i + 1 < argc) { redir_out = argv[++i]; append = 0; + } else if (strcmp(argv[i], "<<") == 0 && i + 1 < argc) { + char* delim = argv[++i]; + int dlen = (int)strlen(delim); + if (dlen > 0 && (delim[0] == '"' || delim[0] == '\'')) { + delim++; dlen -= 2; if (dlen < 0) dlen = 0; + delim[dlen] = '\0'; + } + int pfd[2]; + if (pipe(pfd) == 0) { + tty_restore(); + char hline[LINE_MAX]; + while (1) { + write(STDOUT_FILENO, "> ", 2); + int hi = 0; + char hc; + while (read(STDIN_FILENO, &hc, 1) == 1) { + if (hc == '\n') break; + if (hi < LINE_MAX - 1) hline[hi++] = hc; + } + hline[hi] = '\0'; + if (strcmp(hline, delim) == 0) break; + write(pfd[1], hline, hi); + write(pfd[1], "\n", 1); + } + close(pfd[1]); + heredoc_fd = pfd[0]; + tty_raw_mode(); + } } else if (strcmp(argv[i], "<") == 0 && i + 1 < argc) { redir_in = argv[++i]; } else { @@ -573,7 +602,9 @@ static void run_simple(char* cmd) { /* ---- Apply redirections for builtins too ---- */ int saved_stdin = -1, saved_stdout = -1; - if (redir_in) { + if (heredoc_fd >= 0) { + saved_stdin = dup(0); dup2(heredoc_fd, 0); close(heredoc_fd); heredoc_fd = -1; + } else if (redir_in) { int fd = open(redir_in, O_RDONLY); if (fd >= 0) { saved_stdin = dup(0); dup2(fd, 0); close(fd); } } diff --git a/user/top.c b/user/top.c new file mode 100644 index 0000000..59995e8 --- /dev/null +++ b/user/top.c @@ -0,0 +1,69 @@ +/* AdrOS top utility — one-shot process listing with basic info */ +#include +#include +#include +#include +#include + +static int is_digit(char c) { return c >= '0' && c <= '9'; } + +int main(void) { + printf(" PID STATE CMD\n"); + int fd = open("/proc", O_RDONLY); + if (fd < 0) { + fprintf(stderr, "top: 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]; + + /* Read cmdline */ + snprintf(path, sizeof(path), "/proc/%s/cmdline", d->d_name); + int cfd = open(path, O_RDONLY); + char cmd[64] = "[kernel]"; + if (cfd >= 0) { + int n = read(cfd, cmd, sizeof(cmd) - 1); + if (n > 0) { + cmd[n] = '\0'; + while (n > 0 && (cmd[n-1] == '\n' || cmd[n-1] == '\0')) cmd[--n] = '\0'; + } + if (n <= 0) strcpy(cmd, "[kernel]"); + close(cfd); + } + + /* Read status for state */ + snprintf(path, sizeof(path), "/proc/%s/status", d->d_name); + int sfd = open(path, O_RDONLY); + char state[16] = "?"; + if (sfd >= 0) { + char sbuf[256]; + int sn = read(sfd, sbuf, sizeof(sbuf) - 1); + if (sn > 0) { + sbuf[sn] = '\0'; + char* st = strstr(sbuf, "State:"); + if (st) { + st += 6; + while (*st == ' ' || *st == '\t') st++; + int si = 0; + while (*st && *st != '\n' && si < 15) state[si++] = *st++; + state[si] = '\0'; + } + } + close(sfd); + } + + printf("%5s %6s %s\n", d->d_name, state, cmd); + } + off += d->d_reclen; + } + } + close(fd); + return 0; +} diff --git a/user/which.c b/user/which.c new file mode 100644 index 0000000..9fa5c20 --- /dev/null +++ b/user/which.c @@ -0,0 +1,50 @@ +/* AdrOS which utility — locate a command */ +#include +#include +#include +#include +#include + +static int exists_in_dir(const char* dir, const char* name) { + int fd = open(dir, O_RDONLY); + if (fd < 0) return 0; + char buf[2048]; + 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 (strcmp(d->d_name, name) == 0) { + close(fd); + return 1; + } + off += d->d_reclen; + } + } + close(fd); + return 0; +} + +int main(int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "Usage: which command\n"); + return 1; + } + + static const char* path_dirs[] = { "/bin", "/sbin", NULL }; + int ret = 1; + + for (int i = 1; i < argc; i++) { + int found = 0; + for (int d = 0; path_dirs[d]; d++) { + if (exists_in_dir(path_dirs[d], argv[i])) { + printf("%s/%s\n", path_dirs[d], argv[i]); + found = 1; + break; + } + } + if (found) ret = 0; + } + return ret; +} diff --git a/user/who.c b/user/who.c new file mode 100644 index 0000000..b8fc948 --- /dev/null +++ b/user/who.c @@ -0,0 +1,8 @@ +/* AdrOS who utility — show logged-in users */ +#include +#include + +int main(void) { + printf("root tty1 Jan 1 00:00\n"); + return 0; +}