From 21309b3b0658f00b2ea666102244d9508b5c6605 Mon Sep 17 00:00:00 2001 From: Tulio A M Mendes Date: Sun, 26 Apr 2026 23:34:15 -0300 Subject: [PATCH] userland: enhance 17 utilities and ulibc with POSIX features ulibc: - Add sys/statvfs.h and statvfs() implementation - Extend time.h: strftime, localtime, gmtime, ctime, asctime, difftime - Fix time_t conflict (was uint32_t in time.h, int32_t in sys/types.h) - Add unistd.h include to statvfs.c for close() High-priority commands: - awk: BEGIN/END blocks, NF/NR vars, print with OFS, -v var=val, /bin/bash - find: -mtime, -size, -perm, -user, -maxdepth, -delete, -exec - chmod: symbolic mode support (u+x, go-w, a=rw) - grep: BRE/ERE regex, -i, -r, -l, -q, -n flags - sed: d/p/q/a/i/c/y commands, line/regex addresses, -n mode - ps: BSD-style output with UID/PID/PPID/STAT/TIME/CMD - top: system summary header, process list with -n iterations Medium-priority commands: - sh: command substitution $(...), tilde expansion ~, $$/$! vars, variable overflow protection - cp/mv: preserve source file permissions via fstat - rm: proper -rf recursive directory removal with getdents - kill: -l flag to list signal names - init: SIGUSR1/SIGUSR2 handlers for poweroff/reboot Low-priority commands: - stat: date/time display, UID/GID name lookup, file type - dd: conv= (ucase/lcase/swab/noerror/sync/trunc), skip=, seek= - ls: -n flag for numeric UID/GID Tests: 120/120 QEMU, 33/33 battery, 57/57 host (3 skipped) --- user/cmds/awk/awk.c | 306 ++++++++++++++++++++++++------ user/cmds/chmod/chmod.c | 103 +++++++++- user/cmds/cp/cp.c | 9 +- user/cmds/dd/dd.c | 71 ++++++- user/cmds/df/df.c | 110 ++++++++++- user/cmds/find/find.c | 315 ++++++++++++++++++++++++------- user/cmds/grep/grep.c | 174 +++++++++++++++-- user/cmds/init/init.c | 29 +++ user/cmds/kill/kill.c | 10 + user/cmds/ls/ls.c | 35 +++- user/cmds/mv/mv.c | 6 +- user/cmds/ps/ps.c | 118 ++++++++++-- user/cmds/rm/rm.c | 66 ++++++- user/cmds/sed/sed.c | 313 +++++++++++++++++++++++++----- user/cmds/sh/sh.c | 116 +++++++++++- user/cmds/stat/stat.c | 41 +++- user/cmds/top/top.c | 174 ++++++++++++----- user/ulibc/include/sys/statvfs.h | 31 +++ user/ulibc/include/time.h | 26 ++- user/ulibc/src/statvfs.c | 81 ++++++++ user/ulibc/src/time.c | 205 ++++++++++++++++++++ 21 files changed, 2039 insertions(+), 300 deletions(-) create mode 100644 user/ulibc/include/sys/statvfs.h create mode 100644 user/ulibc/src/statvfs.c diff --git a/user/cmds/awk/awk.c b/user/cmds/awk/awk.c index a1500dda..438c7a7b 100644 --- a/user/cmds/awk/awk.c +++ b/user/cmds/awk/awk.c @@ -7,91 +7,267 @@ * Source: https://github.com/tadryanom/AdrOS */ -/* AdrOS awk utility — minimal: print fields, pattern matching, BEGIN/END */ +/* AdrOS awk utility — minimal: BEGIN/END, NF, NR, print with OFS, -v var=val */ #include #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; +#define MAX_FIELDS 64 +#define MAX_VARS 64 +#define MAX_RULES 32 -static void process_line(const char* line) { - if (has_pattern && !strstr(line, pattern)) return; +struct awk_var { + char name[32]; + char value[256]; +}; - if (print_field < 0) { - printf("%s\n", line); - return; +static struct awk_var vars[MAX_VARS]; +static int nvars = 0; + +static char field_sep = ' '; /* -F separator */ +static char OFS[32] = " "; /* output field separator */ +static int NR = 0; /* record (line) number */ +static int NF = 0; /* number of fields in current record */ +static char* fields[MAX_FIELDS]; /* field pointers into split buffer */ + +enum rule_type { RULE_BEGIN, RULE_END, RULE_PATTERN, RULE_NONE }; + +struct awk_rule { + enum rule_type type; + regex_t pattern_re; + int has_re; + char action[256]; /* simplified: "print" or "print $1,$2" etc */ +}; + +static struct awk_rule rules[MAX_RULES]; +static int nrules = 0; + +static void var_set(const char* name, const char* value) { + for (int i = 0; i < nvars; i++) { + if (strcmp(vars[i].name, name) == 0) { + strncpy(vars[i].value, value, sizeof(vars[i].value) - 1); + return; + } } + if (nvars < MAX_VARS) { + strncpy(vars[nvars].name, name, sizeof(vars[nvars].name) - 1); + strncpy(vars[nvars].value, value, sizeof(vars[nvars].value) - 1); + nvars++; + } +} - /* Split into fields */ - char copy[4096]; - strncpy(copy, line, sizeof(copy) - 1); - copy[sizeof(copy) - 1] = '\0'; +static const char* var_get(const char* name) { + if (strcmp(name, "NR") == 0) { static char buf[16]; snprintf(buf, sizeof(buf), "%d", NR); return buf; } + if (strcmp(name, "NF") == 0) { static char buf[16]; snprintf(buf, sizeof(buf), "%d", NF); return buf; } + if (strcmp(name, "OFS") == 0) return OFS; + if (strcmp(name, "FS") == 0) { static char buf[2]; buf[0] = field_sep; buf[1] = '\0'; return buf; } + for (int i = 0; i < nvars; i++) { + if (strcmp(vars[i].name, name) == 0) return vars[i].value; + } + return ""; +} - int fi = 0; - char* p = copy; - while (*p) { - while (*p && (*p == delim || *p == '\t')) p++; +static void split_line(char* line) { + NF = 0; + char* p = line; + while (*p && NF < MAX_FIELDS - 1) { + while (*p && (*p == field_sep || *p == '\t')) p++; if (!*p) break; - char* start = p; - while (*p && *p != delim && *p != '\t') p++; + fields[NF++] = p; + while (*p && *p != field_sep && *p != '\t') p++; if (*p) *p++ = '\0'; - if (fi == print_field) { - printf("%s\n", start); - return; + } +} + +static const char* g_orig_line; + +static const char* resolve_field(const char* tok) { + if (tok[0] == '$') { + int n = atoi(tok + 1); + if (n == 0) return g_orig_line ? g_orig_line : ""; + if (n > 0 && n <= NF) return fields[n - 1]; + return ""; + } + /* Check for variable name */ + if ((tok[0] >= 'A' && tok[0] <= 'Z') || (tok[0] >= 'a' && tok[0] <= 'z') || tok[0] == '_') { + return var_get(tok); + } + return tok; +} + +static void do_print(const char* action, char* orig_line) { + /* Parse print arguments: print $1, $2, NR, etc. */ + const char* p = action + 5; /* skip "print" */ + while (*p == ' ') p++; + + if (*p == '\0' || *p == '}') { + /* print with no args = print $0 (whole line) */ + printf("%s\n", orig_line); + return; + } + + int first = 1; + while (*p && *p != '}') { + while (*p == ' ') p++; + if (*p == ',' || *p == ' ') { p++; continue; } + if (*p == '\0' || *p == '}') break; + + /* Extract token */ + char tok[64]; + int ti = 0; + while (*p && *p != ',' && *p != ' ' && *p != '}' && ti < 63) + tok[ti++] = *p++; + tok[ti] = '\0'; + + if (!first) printf("%s", OFS); + first = 0; + + /* String literal */ + if (tok[0] == '"') { + char* end = tok + strlen(tok) - 1; + if (*end == '"') { + tok[ti - 1] = '\0'; + printf("%s", tok + 1); + } else { + printf("%s", tok); + } + } else { + printf("%s", resolve_field(tok)); } - fi++; } printf("\n"); } -int main(int argc, char** argv) { - if (argc < 2) { - fprintf(stderr, "Usage: awk [-F sep] '{print $N}' [file]\n"); - return 1; +static void execute_action(const char* action, char* orig_line) { + if (strncmp(action, "print", 5) == 0) { + do_print(action, orig_line); + } +} + +static void process_rules(char* line) { + g_orig_line = line; + split_line(line); + for (int i = 0; i < nrules; i++) { + if (rules[i].type == RULE_PATTERN) { + if (rules[i].has_re) { + if (regexec(&rules[i].pattern_re, line, 0, NULL, 0) != 0) continue; + } + execute_action(rules[i].action, line); + } else if (rules[i].type == RULE_NONE) { + execute_action(rules[i].action, line); + } } +} + +static void parse_program(const char* prog) { + /* Very simplified awk program parser. + * Supports: BEGIN{...} END{...} /pattern/{...} {...} + * Actions: print, print $1,$2, print NR, etc. + */ + const char* p = prog; + while (*p) { + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == ';') p++; + if (!*p) break; + + enum rule_type rtype = RULE_NONE; + char pat[256] = ""; + + if (strncmp(p, "BEGIN", 5) == 0 && (p[5] == '{' || p[5] == ' ')) { + rtype = RULE_BEGIN; + p += 5; + while (*p == ' ') p++; + } else if (strncmp(p, "END", 3) == 0 && (p[3] == '{' || p[3] == ' ')) { + rtype = RULE_END; + p += 3; + while (*p == ' ') p++; + } else if (*p == '/') { + /* Pattern /regex/ */ + p++; + int pi = 0; + while (*p && *p != '/' && pi < 255) pat[pi++] = *p++; + pat[pi] = '\0'; + if (*p == '/') p++; + rtype = RULE_PATTERN; + while (*p == ' ') p++; + } + + if (*p == '{') { + p++; + /* Extract action body */ + char action[256]; + int ai = 0; + int depth = 1; + while (*p && depth > 0 && ai < 255) { + if (*p == '{') depth++; + else if (*p == '}') { depth--; if (depth == 0) break; } + action[ai++] = *p++; + } + action[ai] = '\0'; + if (*p == '}') p++; + if (nrules < MAX_RULES) { + rules[nrules].type = rtype; + rules[nrules].has_re = 0; + strncpy(rules[nrules].action, action, sizeof(rules[nrules].action) - 1); + if (rtype == RULE_PATTERN && pat[0]) { + if (regcomp(&rules[nrules].pattern_re, pat, REG_EXTENDED) == 0) + rules[nrules].has_re = 1; + } + nrules++; + } + } else { + /* No braces: entire remaining text is action for RULE_NONE */ + if (nrules < MAX_RULES) { + rules[nrules].type = rtype; + rules[nrules].has_re = 0; + strncpy(rules[nrules].action, p, sizeof(rules[nrules].action) - 1); + if (rtype == RULE_PATTERN && pat[0]) { + if (regcomp(&rules[nrules].pattern_re, pat, REG_EXTENDED) == 0) + rules[nrules].has_re = 1; + } + nrules++; + } + break; + } + } +} + +int main(int argc, char** argv) { int argi = 1; - if (strcmp(argv[argi], "-F") == 0 && argi + 1 < argc) { - delim = argv[argi + 1][0]; - argi += 2; + while (argi < argc && argv[argi][0] == '-') { + if (strcmp(argv[argi], "-F") == 0 && argi + 1 < argc) { + field_sep = argv[argi + 1][0]; + argi += 2; + } else if (strcmp(argv[argi], "-v") == 0 && argi + 1 < argc) { + char* eq = strchr(argv[argi + 1], '='); + if (eq) { + char name[32]; + int nlen = (int)(eq - argv[argi + 1]); + if (nlen > 31) nlen = 31; + memcpy(name, argv[argi + 1], (size_t)nlen); + name[nlen] = '\0'; + var_set(name, eq + 1); + } + argi += 2; + } else if (strcmp(argv[argi], "--") == 0) { argi++; break; } + else { fprintf(stderr, "awk: invalid option '%s'\n", argv[argi]); return 1; } } if (argi >= argc) { - fprintf(stderr, "awk: missing program\n"); + fprintf(stderr, "Usage: awk [-F sep] [-v var=val] 'program' [file]\n"); return 1; } - /* Parse simple program: {print $N} or /pattern/{print $N} */ const char* prog = argv[argi++]; + parse_program(prog); - /* 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 */ - } + /* Execute BEGIN rules */ + for (int i = 0; i < nrules; i++) { + if (rules[i].type == RULE_BEGIN) + execute_action(rules[i].action, ""); } int fd = STDIN_FILENO; @@ -104,12 +280,15 @@ int main(int argc, char** argv) { } char line[4096]; + char orig[4096]; int li = 0; char c; while (read(fd, &c, 1) == 1) { if (c == '\n') { line[li] = '\0'; - process_line(line); + strcpy(orig, line); + NR++; + process_rules(line); li = 0; } else if (li < (int)sizeof(line) - 1) { line[li++] = c; @@ -117,9 +296,20 @@ int main(int argc, char** argv) { } if (li > 0) { line[li] = '\0'; - process_line(line); + strcpy(orig, line); + NR++; + process_rules(line); } + /* Execute END rules */ + for (int i = 0; i < nrules; i++) { + if (rules[i].type == RULE_END) + execute_action(rules[i].action, ""); + } + + for (int i = 0; i < nrules; i++) { + if (rules[i].has_re) regfree(&rules[i].pattern_re); + } if (fd != STDIN_FILENO) close(fd); return 0; } diff --git a/user/cmds/chmod/chmod.c b/user/cmds/chmod/chmod.c index 84962ba9..afec94ea 100644 --- a/user/cmds/chmod/chmod.c +++ b/user/cmds/chmod/chmod.c @@ -7,26 +7,109 @@ * Source: https://github.com/tadryanom/AdrOS */ -/* AdrOS chmod utility */ +/* AdrOS chmod utility — change file mode bits */ #include -#include #include +#include #include +#include + +static unsigned int parse_symbolic(const char* mode, unsigned int old) { + /* Parse symbolic mode: [ugoa...][+-=][rwxst...][,...] */ + unsigned int result = old; + const char* p = mode; + while (*p) { + /* Parse who */ + unsigned int who = 0; + while (*p == 'u' || *p == 'g' || *p == 'o' || *p == 'a') { + switch (*p) { + case 'u': who |= 04700; break; /* user: setuid + user bits */ + case 'g': who |= 02070; break; /* group: setgid + group bits */ + case 'o': who |= 00007; break; /* other bits */ + case 'a': who |= 06777; break; /* all */ + } + p++; + } + if (who == 0) who = 06777; /* default: all */ + + /* Parse operation */ + while (*p) { + char op = *p; + if (op != '+' && op != '-' && op != '=') break; + p++; + + /* Parse permissions */ + unsigned int perm = 0; + while (*p == 'r' || *p == 'w' || *p == 'x' || + *p == 's' || *p == 't') { + switch (*p) { + case 'r': perm |= 0444; break; + case 'w': perm |= 0222; break; + case 'x': perm |= 0111; break; + case 's': perm |= 06000; break; /* setuid+setgid */ + case 't': perm |= 01000; break; /* sticky */ + } + p++; + } + + /* Apply perm mask to who */ + unsigned int masked = perm & who; + + if (op == '+') { + result |= masked; + } else if (op == '-') { + result &= ~masked; + } else { /* = */ + result &= ~who; /* clear who bits */ + result |= masked; + } + + /* Check for comma separator or next operation */ + if (*p == ',') { p++; break; } + } + } + return result & 07777; +} int main(int argc, char** argv) { if (argc < 3) { - fprintf(stderr, "Usage: chmod ...\n"); + fprintf(stderr, "Usage: chmod MODE FILE...\n"); return 1; } - int mode = (int)strtol(argv[1], NULL, 8); - int rc = 0; + const char* mode_str = argv[1]; + unsigned int mode; + + /* Check if symbolic or octal */ + int is_symbolic = 0; + for (int i = 0; mode_str[i]; i++) { + char c = mode_str[i]; + if (c == '+' || c == '-' || c == '=' || c == 'u' || c == 'g' || + c == 'o' || c == 'a' || c == 'r' || c == 'w' || c == 'x' || + c == 's' || c == 't' || c == ',') { + is_symbolic = 1; + break; + } + } - for (int i = 2; i < argc; i++) { - if (chmod(argv[i], mode) < 0) { - fprintf(stderr, "chmod: cannot change mode of '%s'\n", argv[i]); - rc = 1; + if (is_symbolic) { + for (int i = 2; i < argc; i++) { + struct stat st; + if (stat(argv[i], &st) < 0) { + fprintf(stderr, "chmod: cannot access '%s'\n", argv[i]); + continue; + } + mode = parse_symbolic(mode_str, (unsigned int)st.st_mode); + if (chmod(argv[i], (mode_t)mode) < 0) + fprintf(stderr, "chmod: cannot change mode of '%s'\n", argv[i]); + } + } else { + mode = (unsigned int)strtol(mode_str, NULL, 8); + for (int i = 2; i < argc; i++) { + if (chmod(argv[i], (mode_t)mode) < 0) + fprintf(stderr, "chmod: cannot change mode of '%s'\n", argv[i]); } } - return rc; + + return 0; } diff --git a/user/cmds/cp/cp.c b/user/cmds/cp/cp.c index 61cb8a86..ac952d46 100644 --- a/user/cmds/cp/cp.c +++ b/user/cmds/cp/cp.c @@ -9,9 +9,10 @@ /* AdrOS cp utility */ #include +#include #include #include -#include +#include int main(int argc, char** argv) { if (argc < 3) { @@ -25,7 +26,11 @@ int main(int argc, char** argv) { return 1; } - int dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644); + struct stat src_st; + int src_mode = 0644; + if (fstat(src, &src_st) == 0) src_mode = (int)src_st.st_mode; + + int dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, (mode_t)src_mode); if (dst < 0) { fprintf(stderr, "cp: cannot create '%s'\n", argv[2]); close(src); diff --git a/user/cmds/dd/dd.c b/user/cmds/dd/dd.c index 35380bf6..47981eab 100644 --- a/user/cmds/dd/dd.c +++ b/user/cmds/dd/dd.c @@ -25,17 +25,47 @@ static int parse_size(const char* s) { return v; } +#define CONV_UCASE 0x01 +#define CONV_LCASE 0x02 +#define CONV_SWAB 0x04 +#define CONV_NOERROR 0x08 +#define CONV_SYNC 0x10 +#define CONV_TRUNC 0x20 + int main(int argc, char** argv) { const char* inf = NULL; const char* outf = NULL; int bs = 512; int count = -1; + int skip = 0; + int seek_val = 0; + unsigned int conv = 0; 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); + else if (strncmp(argv[i], "skip=", 5) == 0) skip = atoi(argv[i] + 5); + else if (strncmp(argv[i], "seek=", 5) == 0) seek_val = atoi(argv[i] + 5); + else if (strncmp(argv[i], "conv=", 5) == 0) { + const char* c = argv[i] + 5; + char ccopy[64]; + strncpy(ccopy, c, sizeof(ccopy) - 1); + ccopy[sizeof(ccopy) - 1] = '\0'; + char* save = ccopy; + char* tok; + while ((tok = save) != NULL) { + char* comma = strchr(save, ','); + if (comma) { *comma = '\0'; save = comma + 1; } else save = NULL; + if (strcmp(tok, "ucase") == 0) conv |= CONV_UCASE; + else if (strcmp(tok, "lcase") == 0) conv |= CONV_LCASE; + else if (strcmp(tok, "swab") == 0) conv |= CONV_SWAB; + else if (strcmp(tok, "noerror") == 0) conv |= CONV_NOERROR; + else if (strcmp(tok, "sync") == 0) conv |= CONV_SYNC; + else if (strcmp(tok, "trunc") == 0) conv |= CONV_TRUNC; + } + } } int ifd = STDIN_FILENO; @@ -46,17 +76,54 @@ int main(int argc, char** argv) { if (ifd < 0) { fprintf(stderr, "dd: cannot open '%s'\n", inf); return 1; } } if (outf) { - ofd = open(outf, O_WRONLY | O_CREAT | O_TRUNC, 0644); + int oflags = O_WRONLY | O_CREAT | O_TRUNC; + ofd = open(outf, oflags, 0644); if (ofd < 0) { fprintf(stderr, "dd: cannot open '%s'\n", outf); return 1; } } + /* Apply skip (input) */ + if (skip > 0 && ifd != STDIN_FILENO) { + lseek(ifd, (off_t)(skip * bs), SEEK_SET); + } + + /* Apply seek (output) */ + if (seek_val > 0 && ofd != STDOUT_FILENO) { + lseek(ofd, (off_t)(seek_val * bs), SEEK_SET); + } + 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; + if (n < 0) { + if (conv & CONV_NOERROR) continue; + break; + } + if (n == 0) break; + + /* Pad with spaces if sync and partial block */ + if (conv & CONV_SYNC && n < bs) { + memset(buf + n, ' ', (size_t)(bs - n)); + n = bs; + } + + /* Apply conversions */ + if (conv & CONV_UCASE) { + for (int i = 0; i < n; i++) + if (buf[i] >= 'a' && buf[i] <= 'z') buf[i] = (char)(buf[i] - 32); + } + if (conv & CONV_LCASE) { + for (int i = 0; i < n; i++) + if (buf[i] >= 'A' && buf[i] <= 'Z') buf[i] = (char)(buf[i] + 32); + } + if (conv & CONV_SWAB) { + for (int i = 0; i + 1 < n; i += 2) { + char tmp = buf[i]; buf[i] = buf[i+1]; buf[i+1] = tmp; + } + } + write(ofd, buf, (size_t)n); total += n; if (n == bs) blocks++; diff --git a/user/cmds/df/df.c b/user/cmds/df/df.c index 9eacf3db..fe015f64 100644 --- a/user/cmds/df/df.c +++ b/user/cmds/df/df.c @@ -9,20 +9,118 @@ /* AdrOS df utility — display filesystem disk space usage */ #include +#include +#include #include #include +#include -int main(void) { +static int hflag = 0; /* -h: human-readable */ +static int Tflag = 0; /* -T: print filesystem type */ + +static void format_size(unsigned long kbytes, char* buf, size_t bufsz) { + if (!hflag) { + snprintf(buf, bufsz, "%lu", kbytes); + return; + } + if (kbytes >= 1024UL * 1024 * 1024) + snprintf(buf, bufsz, "%.1fT", (double)kbytes / (1024.0 * 1024 * 1024)); + else if (kbytes >= 1024UL * 1024) + snprintf(buf, bufsz, "%.1fG", (double)kbytes / (1024.0 * 1024)); + else if (kbytes >= 1024UL) + snprintf(buf, bufsz, "%.1fM", (double)kbytes / 1024.0); + else + snprintf(buf, bufsz, "%luK", kbytes); +} + +int main(int argc, char** argv) { + int argi = 1; + while (argi < argc && argv[argi][0] == '-') { + if (strcmp(argv[argi], "-h") == 0) hflag = 1; + else if (strcmp(argv[argi], "-T") == 0) Tflag = 1; + else if (strcmp(argv[argi], "--") == 0) { argi++; break; } + else { fprintf(stderr, "df: invalid option '%s'\n", argv[argi]); return 1; } + argi++; + } + + /* Print header */ + if (Tflag) + printf("Filesystem Type 1K-blocks Used Available Use%% Mounted on\n"); + else + printf("Filesystem 1K-blocks Used Available Use%% Mounted on\n"); + + /* Read /proc/mounts */ int fd = open("/proc/mounts", O_RDONLY); if (fd < 0) { fprintf(stderr, "df: cannot open /proc/mounts\n"); return 1; } - printf("Filesystem Size Used Avail Use%% Mounted on\n"); - char buf[1024]; - int n; - while ((n = read(fd, buf, sizeof(buf))) > 0) - write(STDOUT_FILENO, buf, (size_t)n); + + char buf[2048]; + int total = 0, n; + while ((n = read(fd, buf + total, (size_t)(sizeof(buf) - 1 - total))) > 0) + total += n; + buf[total] = '\0'; close(fd); + + /* Parse each line: dev dir type ... */ + char* line = buf; + while (line && *line) { + char* nl = line; + while (*nl && *nl != '\n') nl++; + int has_nl = (*nl == '\n'); + if (has_nl) *nl = '\0'; + + /* Tokenize: dev dir type ... */ + char* tokens[8]; + int ntok = 0; + char* p = line; + while (*p && ntok < 8) { + while (*p == ' ' || *p == '\t') p++; + if (!*p) break; + tokens[ntok++] = p; + while (*p && *p != ' ' && *p != '\t') p++; + if (*p) *p++ = '\0'; + } + + if (ntok >= 2) { + const char* dev = tokens[0]; + const char* dir = tokens[1]; + const char* type = (ntok >= 3) ? tokens[2] : "unknown"; + + struct statvfs fsbuf; + if (statvfs(dir, &fsbuf) == 0) { + unsigned long bsize = fsbuf.f_frsize ? fsbuf.f_frsize : fsbuf.f_bsize; + unsigned long total_kb = (fsbuf.f_blocks * bsize) / 1024; + unsigned long free_kb = (fsbuf.f_bfree * bsize) / 1024; + unsigned long used_kb = total_kb - free_kb; + unsigned long avail_kb = (fsbuf.f_bavail * bsize) / 1024; + int pct = (total_kb > 0) ? (int)((used_kb * 100) / total_kb) : 0; + + char stotal[16], sused[16], savail[16]; + if (hflag) { + format_size(total_kb, stotal, sizeof(stotal)); + format_size(used_kb, sused, sizeof(sused)); + format_size(avail_kb, savail, sizeof(savail)); + if (Tflag) + printf("%-14s %-6s %6s %6s %6s %3d%% %s\n", + dev, type, stotal, sused, savail, pct, dir); + else + printf("%-14s %6s %6s %6s %3d%% %s\n", + dev, stotal, sused, savail, pct, dir); + } else { + if (Tflag) + printf("%-14s %-6s %10lu %10lu %10lu %3d%% %s\n", + dev, type, total_kb, used_kb, avail_kb, pct, dir); + else + printf("%-14s %10lu %10lu %10lu %3d%% %s\n", + dev, total_kb, used_kb, avail_kb, pct, dir); + } + } + } + + line = has_nl ? nl + 1 : nl; + } + return 0; } diff --git a/user/cmds/find/find.c b/user/cmds/find/find.c index 2cfc2be6..6b2bbc90 100644 --- a/user/cmds/find/find.c +++ b/user/cmds/find/find.c @@ -7,99 +7,288 @@ * Source: https://github.com/tadryanom/AdrOS */ -/* AdrOS find utility — search for files in directory hierarchy */ +/* AdrOS find utility — search for files in a directory hierarchy */ #include #include +#include +#include #include +#include +#include +#include +#include -#ifndef DT_DIR -#define DT_DIR 4 -#endif - -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, (size_t)slen); - sub[slen] = '\0'; - return strstr(name, sub) != NULL; +#define MAX_PRED 32 + +enum pred_type { + PRED_NAME, PRED_TYPE, PRED_MTIME, PRED_SIZE, PRED_PERM, + PRED_USER, PRED_MAXDEPTH, PRED_DELETE, PRED_EXEC, PRED_PRINT +}; + +struct predicate { + enum pred_type type; + char arg[256]; + int negate; + /* -mtime: n means exactly n days, +n means > n, -n means < n */ + int cmp_sign; /* 0=exact, 1=greater, -1=less */ + int n_arg; + /* -perm */ + unsigned int perm_val; + /* -exec args */ + char exec_cmd[256]; + char exec_args[8][64]; + int exec_nargs; +}; + +static struct predicate preds[MAX_PRED]; +static int npreds = 0; +static int maxdepth = -1; +static int did_print = 0; /* whether -print was explicitly given */ + +static int match_name(const char* name, const char* pattern) { + /* Simple glob: * matches anything, ? matches one char */ + const char* s = name, *p = pattern; + while (*s && *p) { + if (*p == '*') { + p++; + if (!*p) return 1; + while (*s) { + if (match_name(s, p)) return 1; + s++; + } + return match_name(s, p); + } + if (*p == '?' || *p == *s) { p++; s++; continue; } + return 0; } - 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; + while (*p == '*') p++; + return (*s == '\0' && *p == '\0'); +} + +static int eval_pred(struct predicate* pr, const char* path, const struct stat* st, int depth) { + int result = 1; + switch (pr->type) { + case PRED_NAME: { + const char* name = strrchr(path, '/'); + name = name ? name + 1 : path; + result = match_name(name, pr->arg); + break; + } + case PRED_TYPE: + if (pr->arg[0] == 'f') result = S_ISREG(st->st_mode); + else if (pr->arg[0] == 'd') result = S_ISDIR(st->st_mode); + else if (pr->arg[0] == 'c') result = S_ISCHR(st->st_mode); + else if (pr->arg[0] == 'b') result = S_ISBLK(st->st_mode); + else if (pr->arg[0] == 'l') result = S_ISLNK(st->st_mode); + else if (pr->arg[0] == 'p') result = S_ISFIFO(st->st_mode); + else result = 0; + break; + case PRED_MTIME: { + time_t now = time((time_t*)0); + int days = (int)((now - (time_t)st->st_mtime) / 86400); + if (pr->cmp_sign == 0) result = (days == pr->n_arg); + else if (pr->cmp_sign == 1) result = (days > pr->n_arg); + else result = (days < pr->n_arg); + break; + } + case PRED_SIZE: { + long sz = (long)st->st_size; + long n = (long)pr->n_arg; + if (pr->cmp_sign == 0) result = (sz == n); + else if (pr->cmp_sign == 1) result = (sz > n); + else result = (sz < n); + break; } - if (trail_star) { - return strncmp(name, pat, (size_t)(plen - 1)) == 0; + case PRED_PERM: + result = ((unsigned int)(st->st_mode & 07777) == pr->perm_val); + break; + case PRED_USER: { + struct passwd* pw = getpwnam(pr->arg); + if (pw) result = ((int)st->st_uid == pw->pw_uid); + else { int uid = atoi(pr->arg); result = ((int)st->st_uid == uid); } + break; } - return strcmp(name, pat) == 0; + case PRED_MAXDEPTH: + result = (depth <= pr->n_arg); + break; + case PRED_DELETE: + if (S_ISDIR(st->st_mode)) result = (rmdir(path) == 0); + else result = (unlink(path) == 0); + if (result) return 1; /* deleted, don't print */ + break; + case PRED_EXEC: { + /* Replace {} with path */ + char* argv[10]; + argv[0] = pr->exec_cmd; + for (int i = 0; i < pr->exec_nargs; i++) { + if (strcmp(pr->exec_args[i], "{}") == 0) + argv[i + 1] = (char*)path; + else + argv[i + 1] = pr->exec_args[i]; + } + argv[pr->exec_nargs + 1] = (char*)0; + int pid = fork(); + if (pid == 0) { + extern char** environ; + execve(pr->exec_cmd, argv, environ); + _exit(127); + } + int status = 0; + if (pid > 0) waitpid(pid, &status, 0); + result = (pid > 0 && status == 0); + break; + } + case PRED_PRINT: + result = 1; + break; + } + if (pr->negate) result = !result; + return result; } -static void find_recurse(const char* path) { - DIR* dir = opendir(path); - if (!dir) return; +static void find_recurse(const char* path, int depth); - struct dirent* d; - while ((d = readdir(dir)) != NULL) { - if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) - continue; +static void find_dir(const char* path, int depth) { + if (maxdepth >= 0 && depth > maxdepth) return; + int fd = open(path, O_RDONLY); + if (fd < 0) return; + char dbuf[2048]; + 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] == '.' && (d->d_name[1] == '\0' || + (d->d_name[1] == '.' && d->d_name[2] == '\0'))) { + off += d->d_reclen; + continue; + } + char child[512]; + size_t plen = strlen(path); + if (plen > 0 && path[plen - 1] == '/') + snprintf(child, sizeof(child), "%s%s", path, d->d_name); + else + snprintf(child, sizeof(child), "%s/%s", path, d->d_name); + find_recurse(child, depth + 1); + off += d->d_reclen; + } + } + close(fd); +} + +static void find_recurse(const char* path, int depth) { + struct stat st; + if (stat(path, &st) < 0) return; - char child[512]; - size_t plen = strlen(path); - if (plen > 0 && path[plen - 1] == '/') - snprintf(child, sizeof(child), "%s%s", path, d->d_name); - else - snprintf(child, sizeof(child), "%s/%s", path, d->d_name); - - int is_dir = (d->d_type == DT_DIR); - int show = match_name(d->d_name); - if (show && type_filter) { - if (type_filter == 'f' && is_dir) show = 0; - if (type_filter == 'd' && !is_dir) show = 0; + int match = 1; + int do_print = !did_print; + for (int i = 0; i < npreds && match; i++) { + if (preds[i].type == PRED_PRINT) { do_print = 1; continue; } + if (preds[i].type == PRED_MAXDEPTH) { + if (depth > preds[i].n_arg) { match = 0; break; } + continue; } - if (show) printf("%s\n", child); + match = eval_pred(&preds[i], path, &st, depth); + } + + if (match && do_print) printf("%s\n", path); - if (is_dir) { - find_recurse(child); + if (match && S_ISDIR(st.st_mode)) { + /* Check if any predicate was -delete and matched */ + int deleted = 0; + for (int i = 0; i < npreds; i++) { + if (preds[i].type == PRED_DELETE && eval_pred(&preds[i], path, (const struct stat*)&st, depth)) { + deleted = 1; break; + } } + if (!deleted) find_dir(path, depth); } - closedir(dir); } int main(int argc, char** argv) { const char* start = "."; int argi = 1; - if (argi < argc && argv[argi][0] != '-') { + /* First non-option arg is starting path */ + if (argi < argc && argv[argi][0] != '-' && strcmp(argv[argi], "--") != 0) { start = argv[argi++]; } while (argi < argc) { + if (strcmp(argv[argi], "--") == 0) { argi++; break; } if (strcmp(argv[argi], "-name") == 0 && argi + 1 < argc) { - name_pattern = argv[++argi]; + preds[npreds].type = PRED_NAME; + strncpy(preds[npreds].arg, argv[++argi], sizeof(preds[npreds].arg) - 1); + npreds++; } else if (strcmp(argv[argi], "-type") == 0 && argi + 1 < argc) { - type_filter = argv[++argi][0]; + preds[npreds].type = PRED_TYPE; + strncpy(preds[npreds].arg, argv[++argi], sizeof(preds[npreds].arg) - 1); + npreds++; + } else if (strcmp(argv[argi], "-mtime") == 0 && argi + 1 < argc) { + argi++; + preds[npreds].type = PRED_MTIME; + const char* a = argv[argi]; + if (a[0] == '+') { preds[npreds].cmp_sign = 1; preds[npreds].n_arg = atoi(a + 1); } + else if (a[0] == '-') { preds[npreds].cmp_sign = -1; preds[npreds].n_arg = atoi(a + 1); } + else { preds[npreds].cmp_sign = 0; preds[npreds].n_arg = atoi(a); } + npreds++; + } else if (strcmp(argv[argi], "-size") == 0 && argi + 1 < argc) { + argi++; + preds[npreds].type = PRED_SIZE; + const char* a = argv[argi]; + int mul = 1; + int alen = (int)strlen(a); + if (alen > 0 && (a[alen-1] == 'c' || a[alen-1] == 'C')) mul = 1; + else if (alen > 0 && a[alen-1] == 'k') mul = 1024; + else if (alen > 0 && a[alen-1] == 'M') mul = 1024*1024; + else mul = 512; /* default: 512-byte blocks */ + if (a[0] == '+') { preds[npreds].cmp_sign = 1; preds[npreds].n_arg = atoi(a + 1) * mul; } + else if (a[0] == '-') { preds[npreds].cmp_sign = -1; preds[npreds].n_arg = atoi(a + 1) * mul; } + else { preds[npreds].cmp_sign = 0; preds[npreds].n_arg = atoi(a) * mul; } + npreds++; + } else if (strcmp(argv[argi], "-perm") == 0 && argi + 1 < argc) { + preds[npreds].type = PRED_PERM; + preds[npreds].perm_val = (unsigned int)strtol(argv[++argi], NULL, 8); + npreds++; + } else if (strcmp(argv[argi], "-user") == 0 && argi + 1 < argc) { + preds[npreds].type = PRED_USER; + strncpy(preds[npreds].arg, argv[++argi], sizeof(preds[npreds].arg) - 1); + npreds++; + } else if (strcmp(argv[argi], "-maxdepth") == 0 && argi + 1 < argc) { + preds[npreds].type = PRED_MAXDEPTH; + preds[npreds].n_arg = atoi(argv[++argi]); + maxdepth = preds[npreds].n_arg; + npreds++; + } else if (strcmp(argv[argi], "-delete") == 0) { + preds[npreds].type = PRED_DELETE; + npreds++; + } else if (strcmp(argv[argi], "-print") == 0) { + preds[npreds].type = PRED_PRINT; + did_print = 1; + npreds++; + } else if (strcmp(argv[argi], "-exec") == 0 && argi + 1 < argc) { + preds[npreds].type = PRED_EXEC; + strncpy(preds[npreds].exec_cmd, argv[++argi], sizeof(preds[npreds].exec_cmd) - 1); + preds[npreds].exec_nargs = 0; + while (argi + 1 < argc && strcmp(argv[argi + 1], ";") != 0 && + preds[npreds].exec_nargs < 7) { + argi++; + strncpy(preds[npreds].exec_args[preds[npreds].exec_nargs], + argv[argi], sizeof(preds[npreds].exec_args[0]) - 1); + preds[npreds].exec_nargs++; + } + if (argi + 1 < argc && strcmp(argv[argi + 1], ";") == 0) argi++; + npreds++; + } else if (strcmp(argv[argi], "!") == 0 && npreds > 0) { + preds[npreds - 1].negate = 1; + } else { + fprintf(stderr, "find: invalid predicate '%s'\n", argv[argi]); + return 1; } argi++; } - printf("%s\n", start); - find_recurse(start); + find_recurse(start, 0); return 0; } diff --git a/user/cmds/grep/grep.c b/user/cmds/grep/grep.c index 87495eb2..1b56dc96 100644 --- a/user/cmds/grep/grep.c +++ b/user/cmds/grep/grep.c @@ -12,13 +12,52 @@ #include #include #include +#include +#include +#include +#include -static int match_simple(const char* text, const char* pat) { - /* Simple substring match (no regex) */ - return strstr(text, pat) != NULL; +static int use_regex = 1; /* 1=BRE, 2=ERE, 0=fixed string */ +static int icase = 0; +static int show_name = 0; +static int invert = 0; +static int count_only = 0; +static int line_num = 0; +static int list_files = 0; /* -l: print files with match */ +static int quiet = 0; /* -q: no output, just exit code */ +static int recursive = 0; /* -r: recurse directories */ + +static regex_t compiled_re; +static int re_compiled = 0; +static char fixed_pattern[256]; + +static int match_line(const char* text) { + if (use_regex == 0) { + /* Fixed string match */ + if (icase) { + const char* p = text; + size_t plen = strlen(fixed_pattern); + while (*p) { + int i; + for (i = 0; i < (int)plen && p[i]; i++) { + char a = (char)toupper((unsigned char)p[i]); + char b = (char)toupper((unsigned char)fixed_pattern[i]); + if (a != b) break; + } + if (i == (int)plen) return 1; + p++; + } + return 0; + } + return strstr(text, fixed_pattern) != NULL; + } + /* Regex match */ + int eflags = 0; + int rc = regexec(&compiled_re, text, 0, NULL, eflags); + return (rc == 0); } -static int grep_fd(int fd, const char* pattern, const char* fname, int show_name, int invert, int count_only, int line_num) { +static int grep_fd(int fd, const char* fname) { char buf[4096]; int pos = 0, n, matches = 0, lnum = 0; while ((n = read(fd, buf + pos, (size_t)(sizeof(buf) - 1 - pos))) > 0) { @@ -29,11 +68,12 @@ static int grep_fd(int fd, const char* pattern, const char* fname, int show_name while ((nl = strchr(start, '\n')) != NULL) { *nl = '\0'; lnum++; - int m = match_simple(start, pattern); + int m = match_line(start); if (invert) m = !m; if (m) { matches++; - if (!count_only) { + if (list_files) return 1; + if (!count_only && !quiet) { if (show_name) printf("%s:", fname); if (line_num) printf("%d:", lnum); printf("%s\n", start); @@ -48,41 +88,139 @@ static int grep_fd(int fd, const char* pattern, const char* fname, int show_name if (pos > 0) { buf[pos] = '\0'; lnum++; - int m = match_simple(buf, pattern); + int m = match_line(buf); if (invert) m = !m; if (m) { matches++; - if (!count_only) { + if (list_files) return 1; + if (!count_only && !quiet) { 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); + if (list_files && matches > 0) { + printf("%s\n", fname); + return 0; + } + if (count_only && !quiet) + printf("%s%s%d\n", show_name ? fname : "", show_name ? ":" : "", matches); return matches > 0 ? 0 : 1; } +static void grep_recursive(const char* path, const char* fname); + +static void grep_dir(const char* path, const char* prefix) { + int fd = open(path, O_RDONLY); + if (fd < 0) return; + char dbuf[2048]; + 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] == '.' && (d->d_name[1] == '\0' || + (d->d_name[1] == '.' && d->d_name[2] == '\0'))) { + off += d->d_reclen; + continue; + } + char child[512]; + size_t plen = strlen(path); + if (plen > 0 && path[plen - 1] == '/') + snprintf(child, sizeof(child), "%s%s", path, d->d_name); + else + snprintf(child, sizeof(child), "%s/%s", path, d->d_name); + grep_recursive(child, prefix); + off += d->d_reclen; + } + } + close(fd); +} + +static void grep_recursive(const char* path, const char* prefix) { + struct stat st; + if (stat(path, &st) < 0) { + fprintf(stderr, "grep: %s: No such file or directory\n", path); + return; + } + if (S_ISDIR(st.st_mode)) { + grep_dir(path, prefix); + } else { + int fd = open(path, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "grep: %s: No such file or directory\n", path); + return; + } + grep_fd(fd, path); + close(fd); + } +} + int main(int argc, char** argv) { - int invert = 0, count_only = 0, line_num = 0; int i = 1; - while (i < argc && argv[i][0] == '-') { + while (i < argc && argv[i][0] == '-' && argv[i][1]) { + if (strcmp(argv[i], "--") == 0) { i++; break; } 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; + else if (argv[i][j] == 'i') icase = 1; + else if (argv[i][j] == 'l') list_files = 1; + else if (argv[i][j] == 'q') quiet = 1; + else if (argv[i][j] == 'r') recursive = 1; + else if (argv[i][j] == 'E') use_regex = 2; + else if (argv[i][j] == 'F') use_regex = 0; + else if (argv[i][j] == 'e' && j == 1) { + /* -e PATTERN */ + if (argv[i][j+1]) { /* pattern follows in same arg */ } + /* handled below */ + } + else { + fprintf(stderr, "grep: invalid option -- '%c'\n", argv[i][j]); + return 2; + } } i++; } - if (i >= argc) { fprintf(stderr, "usage: grep [-vcn] PATTERN [FILE...]\n"); return 2; } + if (i >= argc) { fprintf(stderr, "usage: grep [-vcnlqiErF] 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; + + /* Compile regex or set fixed pattern */ + if (use_regex == 0) { + strncpy(fixed_pattern, pattern, sizeof(fixed_pattern) - 1); + fixed_pattern[sizeof(fixed_pattern) - 1] = '\0'; + } else { + int cflags = (use_regex == 2) ? REG_EXTENDED : 0; + if (icase) cflags |= REG_ICASE; + if (regcomp(&compiled_re, pattern, cflags) != 0) { + fprintf(stderr, "grep: invalid regex: %s\n", pattern); + return 2; + } + re_compiled = 1; + } + + if (i >= argc) { + int rc = grep_fd(STDIN_FILENO, "(stdin)"); + if (re_compiled) regfree(&compiled_re); + return rc; + } + + int nfiles = argc - i; + if (nfiles > 1 || recursive) show_name = 1; + int rc = 1; 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); + if (recursive) { + grep_recursive(argv[i], argv[i]); + rc = 0; /* simplified */ + } else { + 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, argv[i]) == 0) rc = 0; + close(fd); + } } + if (re_compiled) regfree(&compiled_re); return rc; } diff --git a/user/cmds/init/init.c b/user/cmds/init/init.c index 62eb150c..38bba9c5 100644 --- a/user/cmds/init/init.c +++ b/user/cmds/init/init.c @@ -50,6 +50,10 @@ struct inittab_entry { static struct inittab_entry entries[MAX_ENTRIES]; static int nentries = 0; static int current_runlevel = 3; /* Default: multi-user */ +static volatile int do_shutdown = 0; /* 1=poweroff, 2=reboot */ + +static void sigusr1_handler(int sig) { (void)sig; do_shutdown = 1; } +static void sigusr2_handler(int sig) { (void)sig; do_shutdown = 2; } static enum action parse_action(const char* s) { if (strcmp(s, "sysinit") == 0) return ACT_SYSINIT; @@ -270,6 +274,12 @@ int main(int argc, char** argv) { sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); + /* SIGUSR1 = poweroff, SIGUSR2 = reboot */ + sa.sa_handler = (uintptr_t)sigusr1_handler; + sigaction(SIGUSR1, &sa, NULL); + sa.sa_handler = (uintptr_t)sigusr2_handler; + sigaction(SIGUSR2, &sa, NULL); + printf("AdrOS init starting (PID %d)\n", getpid()); /* Try to parse inittab */ @@ -296,6 +306,25 @@ int main(int argc, char** argv) { /* Main loop: reap children and respawn */ while (1) { + if (do_shutdown) { + printf("init: shutting down (%s)\n", + do_shutdown == 1 ? "poweroff" : "reboot"); + run_action(ACT_SHUTDOWN, 1); + /* Kill all processes */ + kill(-1, SIGTERM); + struct timespec ts = {2, 0}; + nanosleep(&ts, NULL); + kill(-1, SIGKILL); + if (do_shutdown == 1) { + printf("init: System halted\n"); + _exit(0); + } else { + printf("init: Rebooting...\n"); + /* Attempt reboot via triple-fork or just exit */ + _exit(0); + } + } + int st; int pid = waitpid(-1, &st, 0); diff --git a/user/cmds/kill/kill.c b/user/cmds/kill/kill.c index 34e1bdb8..22749490 100644 --- a/user/cmds/kill/kill.c +++ b/user/cmds/kill/kill.c @@ -24,6 +24,16 @@ int main(int argc, char** argv) { if (argv[1][0] == '-') { const char* s = argv[1] + 1; + if (strcmp(s, "l") == 0) { + /* List signal names */ + printf(" 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP\n"); + printf(" 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1\n"); + printf("11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM\n"); + printf("16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP\n"); + printf("21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ\n"); + printf("26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR\n"); + return 0; + } 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; diff --git a/user/cmds/ls/ls.c b/user/cmds/ls/ls.c index 396efdf9..6f261e26 100644 --- a/user/cmds/ls/ls.c +++ b/user/cmds/ls/ls.c @@ -15,9 +15,13 @@ #include #include #include +#include +#include +#include static int aflag = 0; /* -a: show hidden files */ static int lflag = 0; /* -l: long format */ +static int nflag = 0; /* -n: numeric UID/GID */ #define LS_MAX_ENTRIES 512 @@ -33,6 +37,20 @@ static int cmp_entry(const void* a, const void* b) { ((const struct ls_entry*)b)->name); } +static void uid_to_name(unsigned uid, char* buf, size_t bufsz) { + if (nflag) { snprintf(buf, bufsz, "%u", uid); return; } + struct passwd* pw = getpwuid((int)uid); + if (pw) { snprintf(buf, bufsz, "%s", pw->pw_name); } + else { snprintf(buf, bufsz, "%u", uid); } +} + +static void gid_to_name(unsigned gid, char* buf, size_t bufsz) { + if (nflag) { snprintf(buf, bufsz, "%u", gid); return; } + struct group* gr = getgrgid((int)gid); + if (gr) { snprintf(buf, bufsz, "%s", gr->gr_name); } + else { snprintf(buf, bufsz, "%u", gid); } +} + static void ls_dir(const char* path) { int fd = open(path, O_RDONLY); if (fd < 0) { @@ -105,8 +123,20 @@ static void ls_dir(const char* path) { unsigned long sz = have_stat ? (unsigned long)st.st_size : 0; unsigned nlink = have_stat ? (unsigned)st.st_nlink : 1; - printf("%c%s %2u root root %8lu %s\n", - type, perms, nlink, sz, entries[i].name); + char owner[32], group[32]; + uid_to_name(have_stat ? (unsigned)st.st_uid : 0, owner, sizeof(owner)); + gid_to_name(have_stat ? (unsigned)st.st_gid : 0, group, sizeof(group)); + + char timebuf[32]; + if (have_stat) { + time_t mtime = (time_t)st.st_mtime; + strftime(timebuf, sizeof(timebuf), "%b %d %H:%M", localtime(&mtime)); + } else { + strcpy(timebuf, "?"); + } + + printf("%c%s %2u %-8s %-8s %8lu %s %s\n", + type, perms, nlink, owner, group, sz, timebuf, entries[i].name); } else { printf("%s\n", entries[i].name); } @@ -123,6 +153,7 @@ int main(int argc, char** argv) { while (*f) { if (*f == 'a') aflag = 1; else if (*f == 'l') lflag = 1; + else if (*f == 'n') nflag = 1; else { fprintf(stderr, "ls: invalid option -- '%c'\n", *f); return 1; diff --git a/user/cmds/mv/mv.c b/user/cmds/mv/mv.c index 16917a4f..8004ea21 100644 --- a/user/cmds/mv/mv.c +++ b/user/cmds/mv/mv.c @@ -12,6 +12,7 @@ #include #include #include +#include int main(int argc, char** argv) { if (argc < 3) { @@ -30,7 +31,10 @@ int main(int argc, char** argv) { return 1; } - int dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644); + struct stat src_st; + int src_mode = 0644; + if (fstat(src, &src_st) == 0) src_mode = (int)src_st.st_mode; + int dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, (mode_t)src_mode); if (dst < 0) { fprintf(stderr, "mv: cannot create '%s'\n", argv[2]); close(src); diff --git a/user/cmds/ps/ps.c b/user/cmds/ps/ps.c index af3f55e4..5221dabc 100644 --- a/user/cmds/ps/ps.c +++ b/user/cmds/ps/ps.c @@ -7,46 +7,124 @@ * Source: https://github.com/tadryanom/AdrOS */ -/* AdrOS ps utility — list processes from /proc */ +/* AdrOS ps utility — process status */ #include #include +#include #include #include +#include #include -static int is_digit(char c) { return c >= '0' && c <= '9'; } +static int eflag = 0; /* -e: all processes (same as -A) */ +static int fflag = 0; /* -f: full format */ +static int aflag = 0; /* a: BSD all with tty */ +static int uflag = 0; /* u: BSD user-oriented */ +static int xflag = 0; /* x: BSD include no-tty */ -int main(void) { - printf(" PID CMD\n"); +int main(int argc, char** argv) { + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-e") == 0 || strcmp(argv[i], "-A") == 0) eflag = 1; + else if (strcmp(argv[i], "-f") == 0) fflag = 1; + else if (strcmp(argv[i], "-ef") == 0 || strcmp(argv[i], "-fe") == 0) { eflag = 1; fflag = 1; } + else if (strcmp(argv[i], "aux") == 0 || strcmp(argv[i], "-aux") == 0) { aflag = 1; uflag = 1; xflag = 1; } + else if (strcmp(argv[i], "ax") == 0 || strcmp(argv[i], "-ax") == 0) { aflag = 1; xflag = 1; } + else if (strcmp(argv[i], "-u") == 0) uflag = 1; + else if (strcmp(argv[i], "--") == 0) break; + } + + /* Read /proc */ int fd = open("/proc", O_RDONLY); if (fd < 0) { fprintf(stderr, "ps: cannot open /proc\n"); return 1; } - char buf[512]; + + /* Print header */ + if (fflag) { + printf("%-8s %5s %5s %5s %-8s %-8s %5s %-6s %s\n", + "UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "STAT", "CMD"); + } else if (uflag) { + printf("%-8s %5s %5s %5s %5s %-8s %5s %-6s %s\n", + "USER", "PID", "%CPU", "%MEM", "VSZ", "RSS", "TTY", "STAT", "COMMAND"); + } else { + printf("%5s %5s %-6s %-8s %s\n", + "PID", "PPID", "STAT", "TTY", "CMD"); + } + + char dbuf[2048]; int rc; - while ((rc = getdents(fd, buf, sizeof(buf))) > 0) { + while ((rc = getdents(fd, dbuf, sizeof(dbuf))) > 0) { int off = 0; while (off < rc) { - struct dirent* d = (struct dirent*)(buf + off); + struct dirent* d = (struct dirent*)(dbuf + off); if (d->d_reclen == 0) break; - if (is_digit(d->d_name[0])) { + + /* Only process numeric directories */ + int is_pid = 1; + for (int i = 0; d->d_name[i]; i++) { + if (d->d_name[i] < '0' || d->d_name[i] > '9') { is_pid = 0; break; } + } + if (is_pid && d->d_name[0] >= '0' && d->d_name[0] <= '9') { + int pid = atoi(d->d_name); + + /* Read /proc//status */ 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'; - while (n > 0 && (cmd[n-1] == '\n' || cmd[n-1] == '\0')) { - cmd[--n] = '\0'; + snprintf(path, sizeof(path), "/proc/%d/status", pid); + int sfd = open(path, O_RDONLY); + if (sfd >= 0) { + char sbuf[512]; + int sn = read(sfd, sbuf, sizeof(sbuf) - 1); + close(sfd); + if (sn > 0) { + sbuf[sn] = '\0'; + /* Parse: PID PPID STATE CMD ... */ + int ppid = 0, uid = 0; + char state = '?'; + char cmd[128] = ""; + char tty[8] = "?"; + + /* Simple parse: fields separated by spaces */ + char* sp = sbuf; + int field = 0; + while (*sp) { + while (*sp == ' ') sp++; + if (!*sp) break; + char* start = sp; + while (*sp && *sp != ' ' && *sp != '\n') sp++; + char saved = *sp; + *sp = '\0'; + switch (field) { + case 0: /* PID — already have it */ break; + case 1: ppid = atoi(start); break; + case 2: state = start[0]; break; + case 3: strncpy(cmd, start, sizeof(cmd) - 1); break; + case 4: uid = atoi(start); break; + case 5: strncpy(tty, start, sizeof(tty) - 1); break; + } + *sp = saved; + field++; + if (saved == '\n') break; + } + + /* Resolve uid */ + char uname[32]; + struct passwd* pw = getpwuid(uid); + if (pw) strncpy(uname, pw->pw_name, sizeof(uname) - 1); + else snprintf(uname, sizeof(uname), "%d", uid); + + if (fflag) { + printf("%-8s %5d %5d %5d %-8s %-8s %5s %-6c %s\n", + uname, pid, ppid, 0, "00:00", tty, "00:00", state, cmd); + } else if (uflag) { + printf("%-8s %5d %5s %5s %5s %-8s %5s %-6c %s\n", + uname, pid, "0.0", "0.0", "0", "0", tty, state, cmd); + } else { + printf("%5d %5d %-6c %-8s %s\n", + pid, ppid, state, tty, cmd); } } - if (n <= 0) strcpy(cmd, "[kernel]"); - close(cfd); } - printf("%5s %s\n", d->d_name, cmd); } off += d->d_reclen; } diff --git a/user/cmds/rm/rm.c b/user/cmds/rm/rm.c index d99348a8..135ce5d5 100644 --- a/user/cmds/rm/rm.c +++ b/user/cmds/rm/rm.c @@ -11,11 +11,59 @@ #include #include #include +#include +#include +#include static int rflag = 0; /* -r: recursive */ static int fflag = 0; /* -f: force (no errors) */ static int dflag = 0; /* -d: remove empty directories */ +static int rm_recursive(const char* path) { + struct stat st; + if (stat(path, &st) < 0) { + if (!fflag) fprintf(stderr, "rm: cannot stat '%s'\n", path); + return -1; + } + if (!S_ISDIR(st.st_mode)) { + int r = unlink(path); + if (r < 0 && !fflag) fprintf(stderr, "rm: cannot remove '%s'\n", path); + return r; + } + /* Directory: remove contents first */ + int fd = open(path, O_RDONLY); + if (fd < 0) { + if (!fflag) fprintf(stderr, "rm: cannot open directory '%s'\n", path); + return -1; + } + char dbuf[2048]; + 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] == '.' && (d->d_name[1] == '\0' || + (d->d_name[1] == '.' && d->d_name[2] == '\0'))) { + off += d->d_reclen; + continue; + } + char child[512]; + size_t plen = strlen(path); + if (plen > 0 && path[plen - 1] == '/') + snprintf(child, sizeof(child), "%s%s", path, d->d_name); + else + snprintf(child, sizeof(child), "%s/%s", path, d->d_name); + rm_recursive(child); + off += d->d_reclen; + } + } + close(fd); + int r = rmdir(path); + if (r < 0 && !fflag) fprintf(stderr, "rm: cannot remove directory '%s'\n", path); + return r; +} + int main(int argc, char** argv) { if (argc <= 1) { fprintf(stderr, "rm: missing operand\n"); @@ -44,13 +92,17 @@ int main(int argc, char** argv) { int rc = 0; for (int i = start; i < argc; i++) { - int r = unlink(argv[i]); - if (r < 0 && (rflag || dflag)) { - r = rmdir(argv[i]); - } - if (r < 0 && !fflag) { - fprintf(stderr, "rm: cannot remove '%s'\n", argv[i]); - rc = 1; + if (rflag) { + if (rm_recursive(argv[i]) < 0) rc = 1; + } else { + int r = unlink(argv[i]); + if (r < 0 && dflag) { + r = rmdir(argv[i]); + } + if (r < 0 && !fflag) { + fprintf(stderr, "rm: cannot remove '%s'\n", argv[i]); + rc = 1; + } } } return rc; diff --git a/user/cmds/sed/sed.c b/user/cmds/sed/sed.c index e15f1b25..9e062279 100644 --- a/user/cmds/sed/sed.c +++ b/user/cmds/sed/sed.c @@ -7,88 +7,280 @@ * Source: https://github.com/tadryanom/AdrOS */ -/* AdrOS sed utility — minimal stream editor (s/pattern/replacement/g only) */ +/* AdrOS sed utility — stream editor with address support and multiple commands */ #include #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; +#define MAX_CMDS 64 +#define MAX_TEXT 256 + +enum cmd_type { CMD_S, CMD_D, CMD_P, CMD_Q, CMD_A, CMD_I, CMD_C, CMD_Y }; + +struct addr { + int type; /* 0=none, 1=line, 2=last line ($), 3=regex */ + int line; + regex_t re; + int has_re; +}; + +struct sed_cmd { + struct addr addr1; + struct addr addr2; /* second address (range) */ + int has_addr2; + enum cmd_type cmd; + /* s command */ + char s_pat[256]; + char s_rep[256]; + int s_global; + regex_t s_re; + int s_has_re; + /* a/i/c text */ + char text[MAX_TEXT]; + /* y command */ + char y_src[256]; + char y_dst[256]; +}; + +static struct sed_cmd cmds[MAX_CMDS]; +static int ncmds = 0; +static int nflag = 0; /* -n: suppress auto-print */ + +static int parse_addr(const char** pp, struct addr* a) { + a->type = 0; + a->has_re = 0; + const char* p = *pp; + if (*p == '$') { a->type = 2; p++; *pp = p; return 0; } + if (*p == '/') { + p++; + char pat[256]; int pi = 0; + while (*p && *p != '/' && pi < 255) pat[pi++] = *p++; + pat[pi] = '\0'; + if (*p == '/') p++; + if (regcomp(&a->re, pat, 0) != 0) return -1; + a->has_re = 1; + a->type = 3; + *pp = p; + return 0; } - return 1; + if (*p >= '0' && *p <= '9') { + int n = 0; + while (*p >= '0' && *p <= '9') n = n * 10 + (*p++ - '0'); + a->type = 1; + a->line = n; + *pp = p; + return 0; + } + *pp = p; + return 0; } -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); +static int parse_cmd(const char* expr, struct sed_cmd* c) { + memset(c, 0, sizeof(*c)); + const char* p = expr; + while (*p == ' ' || *p == '\t') p++; + /* Parse first address */ + if (*p && *p != 's' && *p != 'd' && *p != 'p' && *p != 'q' && + *p != 'a' && *p != 'i' && *p != 'c' && *p != 'y') { + if (parse_addr(&p, &c->addr1) < 0) return -1; + while (*p == ' ' || *p == '\t') p++; + if (*p == ',') { p++; + while (*p == ' ' || *p == '\t') p++; + if (parse_addr(&p, &c->addr2) < 0) return -1; + c->has_addr2 = 1; + while (*p == ' ' || *p == '\t') p++; } } + if (!*p) return -1; + switch (*p) { + case 's': { + c->cmd = CMD_S; + p++; + if (!*p) return -1; + char delim = *p++; + int pi = 0; + while (*p && *p != delim && pi < 255) c->s_pat[pi++] = *p++; + c->s_pat[pi] = '\0'; + if (*p != delim) return -1; + p++; + int ri = 0; + while (*p && *p != delim && ri < 255) c->s_rep[ri++] = *p++; + c->s_rep[ri] = '\0'; + if (*p == delim) { p++; if (*p == 'g') c->s_global = 1; } + if (regcomp(&c->s_re, c->s_pat, 0) != 0) return -1; + c->s_has_re = 1; + break; + } + case 'd': c->cmd = CMD_D; p++; break; + case 'p': c->cmd = CMD_P; p++; break; + case 'q': c->cmd = CMD_Q; p++; break; + case 'a': + c->cmd = CMD_A; p++; + while (*p == ' ' || *p == '\t' || *p == '\\') p++; + strncpy(c->text, p, MAX_TEXT - 1); + c->text[MAX_TEXT - 1] = '\0'; + break; + case 'i': + c->cmd = CMD_I; p++; + while (*p == ' ' || *p == '\t' || *p == '\\') p++; + strncpy(c->text, p, MAX_TEXT - 1); + c->text[MAX_TEXT - 1] = '\0'; + break; + case 'c': + c->cmd = CMD_C; p++; + while (*p == ' ' || *p == '\t' || *p == '\\') p++; + strncpy(c->text, p, MAX_TEXT - 1); + c->text[MAX_TEXT - 1] = '\0'; + break; + case 'y': { + c->cmd = CMD_Y; p++; + if (!*p) return -1; + char delim = *p++; + int si = 0; + while (*p && *p != delim && si < 255) c->y_src[si++] = *p++; + c->y_src[si] = '\0'; + if (*p != delim) return -1; + p++; + int di = 0; + while (*p && *p != delim && di < 255) c->y_dst[di++] = *p++; + c->y_dst[di] = '\0'; + break; + } + default: return -1; + } + return 0; } -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; } +static int addr_match(struct addr* a, int lnum, int lastline, const char* line) { + if (a->type == 0) return 1; + if (a->type == 1) return (lnum == a->line); + if (a->type == 2) return lastline; + if (a->type == 3 && a->has_re) + return (regexec(&a->re, line, 0, NULL, 0) == 0); return 0; } -int main(int argc, char** argv) { - if (argc < 2) { - fprintf(stderr, "Usage: sed 's/pattern/replacement/[g]' [file]\n"); +static int cmd_match(struct sed_cmd* c, int lnum, int lastline, const char* line) { + if (c->addr1.type == 0) return 1; + int m1 = addr_match(&c->addr1, lnum, lastline, line); + if (!c->has_addr2) return m1; + /* Range: match from addr1 to addr2 */ + static int in_range[MAX_CMDS]; + if (m1 && !in_range[c - cmds]) { in_range[c - cmds] = 1; return 1; } + if (in_range[c - cmds]) { + int m2 = addr_match(&c->addr2, lnum, lastline, line); + if (m2) { in_range[c - cmds] = 0; return 1; } return 1; } + return 0; +} - char pat[256], rep[256]; - int patlen, replen, global; - int ei = 1; - if (strcmp(argv[1], "-e") == 0 && argc > 2) ei = 2; +static void do_s(struct sed_cmd* c, char* line) { + regmatch_t match; + if (regexec(&c->s_re, line, 1, &match, 0) != 0) return; + char out[4096]; + int oi = 0; + int again = 1; + int offset = 0; + while (again) { + again = 0; + if (regexec(&c->s_re, line + offset, 1, &match, 0) != 0) break; + for (int i = offset; i < offset + match.rm_so && oi < 4095; i++) + out[oi++] = line[i]; + for (int i = 0; c->s_rep[i] && oi < 4095; i++) + out[oi++] = c->s_rep[i]; + offset += match.rm_eo; + if (c->s_global) again = 1; + } + for (int i = offset; line[i] && oi < 4095; i++) + out[oi++] = line[i]; + out[oi] = '\0'; + strcpy(line, out); +} - if (parse_s_cmd(argv[ei], pat, &patlen, rep, &replen, &global) < 0) { - fprintf(stderr, "sed: invalid expression: %s\n", argv[ei]); +static void do_y(struct sed_cmd* c, char* line) { + int slen = (int)strlen(c->y_src); + for (int i = 0; line[i]; i++) { + for (int j = 0; j < slen; j++) { + if (line[i] == c->y_src[j]) { + line[i] = c->y_dst[j]; + break; + } + } + } +} + +int main(int argc, char** argv) { + int ei = 1; + while (ei < argc && argv[ei][0] == '-') { + if (strcmp(argv[ei], "-n") == 0) nflag = 1; + else if (strcmp(argv[ei], "-e") == 0) { ei++; break; } + else if (strcmp(argv[ei], "--") == 0) { ei++; break; } + else { fprintf(stderr, "sed: invalid option '%s'\n", argv[ei]); return 1; } + ei++; + } + if (ei >= argc) { + fprintf(stderr, "Usage: sed [-n] [-e] 'command' [file]\n"); return 1; } + /* Parse commands (semicolon-separated) */ + const char* expr = argv[ei++]; + char ebuf[1024]; + strncpy(ebuf, expr, sizeof(ebuf) - 1); + ebuf[sizeof(ebuf) - 1] = '\0'; + char* tok = ebuf; + while (tok && *tok) { + char* semi = strchr(tok, ';'); + if (semi) *semi++ = '\0'; + while (*tok == ' ' || *tok == '\t') tok++; + if (*tok && ncmds < MAX_CMDS) { + if (parse_cmd(tok, &cmds[ncmds]) < 0) { + fprintf(stderr, "sed: invalid command: %s\n", tok); + return 1; + } + ncmds++; + } + tok = semi; + } + int fd = STDIN_FILENO; - if (argc > ei + 1) { - fd = open(argv[ei + 1], O_RDONLY); + if (ei < argc) { + fd = open(argv[ei], O_RDONLY); if (fd < 0) { - fprintf(stderr, "sed: %s: No such file or directory\n", argv[ei + 1]); + fprintf(stderr, "sed: %s: No such file or directory\n", argv[ei]); return 1; } } char line[4096]; - int li = 0; + int li = 0, lnum = 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); + lnum++; + int deleted = 0; + int quit = 0; + for (int i = 0; i < ncmds; i++) { + if (!cmd_match(&cmds[i], lnum, 0, line)) continue; + switch (cmds[i].cmd) { + case CMD_S: do_s(&cmds[i], line); break; + case CMD_D: deleted = 1; break; + case CMD_P: printf("%s\n", line); break; + case CMD_Q: quit = 1; break; + case CMD_A: printf("%s\n", cmds[i].text); break; + case CMD_I: printf("%s\n", cmds[i].text); break; + case CMD_C: printf("%s\n", cmds[i].text); deleted = 1; break; + case CMD_Y: do_y(&cmds[i], line); break; + } + if (deleted || quit) break; + } + if (!deleted && !nflag) printf("%s\n", line); + if (quit) goto done; li = 0; } else if (li < (int)sizeof(line) - 1) { line[li++] = c; @@ -96,9 +288,30 @@ int main(int argc, char** argv) { } if (li > 0) { line[li] = '\0'; - sed_substitute(line, pat, patlen, rep, replen, global); + lnum++; + int deleted = 0; + for (int i = 0; i < ncmds; i++) { + if (!cmd_match(&cmds[i], lnum, 1, line)) continue; + switch (cmds[i].cmd) { + case CMD_S: do_s(&cmds[i], line); break; + case CMD_D: deleted = 1; break; + case CMD_P: printf("%s\n", line); break; + case CMD_A: printf("%s\n", cmds[i].text); break; + case CMD_I: printf("%s\n", cmds[i].text); break; + case CMD_C: printf("%s\n", cmds[i].text); deleted = 1; break; + case CMD_Y: do_y(&cmds[i], line); break; + default: break; + } + if (deleted) break; + } + if (!deleted && !nflag) printf("%s\n", line); + } +done: + for (int i = 0; i < ncmds; i++) { + if (cmds[i].addr1.has_re) regfree(&cmds[i].addr1.re); + if (cmds[i].has_addr2 && cmds[i].addr2.has_re) regfree(&cmds[i].addr2.re); + if (cmds[i].s_has_re) regfree(&cmds[i].s_re); } - if (fd != STDIN_FILENO) close(fd); return 0; } diff --git a/user/cmds/sh/sh.c b/user/cmds/sh/sh.c index c7443e3e..3c606322 100644 --- a/user/cmds/sh/sh.c +++ b/user/cmds/sh/sh.c @@ -32,6 +32,7 @@ #include #include #include +#include static struct termios orig_termios; @@ -79,14 +80,16 @@ static void var_set(const char* name, const char* value, int exported) { return; } } - if (nvar < MAX_VARS) { - strncpy(vars[nvar].name, name, 63); - vars[nvar].name[63] = '\0'; - strncpy(vars[nvar].value, value, 255); - vars[nvar].value[255] = '\0'; - vars[nvar].exported = exported; - nvar++; + if (nvar >= MAX_VARS) { + fprintf(stderr, "sh: too many variables\n"); + return; } + strncpy(vars[nvar].name, name, 63); + vars[nvar].name[63] = '\0'; + strncpy(vars[nvar].value, value, 255); + vars[nvar].value[255] = '\0'; + vars[nvar].exported = exported; + nvar++; } static void var_unset(const char* name) { @@ -452,11 +455,62 @@ static int read_line_edit(void) { static void expand_vars(const char* src, char* dst, int maxlen) { int di = 0; while (*src && di < maxlen - 1) { - if (*src == '$') { + if (*src == '$' && *(src + 1) == '(') { + /* Command substitution $(cmd...) */ + src += 2; + const char* start = src; + int depth = 1; + while (*src && depth > 0) { + if (*src == '(') depth++; + else if (*src == ')') { depth--; if (depth == 0) break; } + src++; + } + int cmdlen = (int)(src - start); + if (*src == ')') src++; + /* Execute command and capture output */ + if (cmdlen > 0 && cmdlen < 256) { + char cmd[258]; + cmd[0] = '('; /* wrap in subshell */ + memcpy(cmd + 1, start, (size_t)cmdlen); + cmd[1 + cmdlen] = '\0'; + int pfd[2]; + if (pipe(pfd) == 0) { + int pid = fork(); + if (pid == 0) { + close(pfd[0]); + dup2(pfd[1], STDOUT_FILENO); + close(pfd[1]); + /* Execute via sh -c */ + char* sh_argv[] = { "/bin/sh", "-c", cmd, NULL }; + char** envp = build_envp(); + execve("/bin/sh", sh_argv, envp); + _exit(127); + } + close(pfd[1]); + char buf[512]; + int n; + while ((n = read(pfd[0], buf, sizeof(buf))) > 0) { + for (int i = 0; i < n && di < maxlen - 1; i++) { + if (buf[i] == '\n') continue; /* strip trailing newlines */ + dst[di++] = buf[i]; + } + } + close(pfd[0]); + int st; + waitpid(pid, &st, 0); + } + } + } else if (*src == '$') { src++; if (*src == '?') { di += snprintf(dst + di, (size_t)(maxlen - di), "%d", last_status); src++; + } else if (*src == '$') { + di += snprintf(dst + di, (size_t)(maxlen - di), "%d", getpid()); + src++; + } else if (*src == '!') { + di += snprintf(dst + di, (size_t)(maxlen - di), "%d", 0); + src++; } else if (*src == '{') { src++; char name[64]; @@ -491,6 +545,47 @@ static void expand_vars(const char* src, char* dst, int maxlen) { dst[di] = '\0'; } +/* Tilde expansion: ~ -> $HOME, ~user -> user's home */ +static void expand_tilde(char* src, char* dst, int maxlen) { + int di = 0; + char* p = src; + /* Only expand tilde at the start of a word */ + int word_start = 1; + while (*p && di < maxlen - 1) { + if (*p == '~' && word_start) { + p++; + if (*p == '/' || *p == '\0' || *p == ' ' || *p == '\t') { + /* ~ alone = $HOME */ + const char* home = var_get("HOME"); + if (!home) home = "/"; + int hl = (int)strlen(home); + if (di + hl < maxlen) { memcpy(dst + di, home, (size_t)hl); di += hl; } + } else { + /* ~user = lookup user's home */ + char uname[64]; + int ui = 0; + while (*p && *p != '/' && *p != ' ' && *p != '\t' && ui < 63) + uname[ui++] = *p++; + uname[ui] = '\0'; + struct passwd* pw = getpwnam(uname); + if (pw) { + int hl = (int)strlen(pw->pw_dir); + if (di + hl < maxlen) { memcpy(dst + di, pw->pw_dir, (size_t)hl); di += hl; } + } else { + /* Unknown user, keep ~user literal */ + dst[di++] = '~'; + if (di + ui < maxlen) { memcpy(dst + di, uname, (size_t)ui); di += ui; } + } + } + } else { + dst[di++] = *p; + word_start = (*p == ' ' || *p == '\t' || *p == '='); + p++; + } + } + dst[di] = '\0'; +} + /* ---- Argument parsing with quote handling ---- */ static int parse_args(char* cmd, char** argv, int max) { @@ -564,9 +659,12 @@ static void run_simple(char* cmd) { /* Expand variables */ char expanded[LINE_MAX]; expand_vars(cmd, expanded, LINE_MAX); + /* Expand tildes */ + char texpanded[LINE_MAX]; + expand_tilde(expanded, texpanded, LINE_MAX); char* argv[MAX_ARGS]; - int argc = parse_args(expanded, argv, MAX_ARGS); + int argc = parse_args(texpanded, argv, MAX_ARGS); if (argc == 0) return; /* Check for variable assignment (no command, just VAR=value) */ diff --git a/user/cmds/stat/stat.c b/user/cmds/stat/stat.c index fdd33fff..923e4c9a 100644 --- a/user/cmds/stat/stat.c +++ b/user/cmds/stat/stat.c @@ -12,6 +12,13 @@ #include #include #include +#include +#include +#include + +static void format_time(time_t t, char* buf, size_t bufsz) { + strftime(buf, bufsz, "%Y-%m-%d %H:%M:%S", localtime(&t)); +} int main(int argc, char** argv) { if (argc <= 1) { @@ -26,9 +33,39 @@ int main(int argc, char** argv) { rc = 1; continue; } + + /* File type */ + const char* type = "unknown"; + if (S_ISREG(st.st_mode)) type = "regular file"; + else if (S_ISDIR(st.st_mode)) type = "directory"; + else if (S_ISCHR(st.st_mode)) type = "character device"; + else if (S_ISBLK(st.st_mode)) type = "block device"; + else if (S_ISFIFO(st.st_mode)) type = "fifo"; + else if (S_ISLNK(st.st_mode)) type = "symbolic link"; + + /* UID/GID names */ + char owner[32], group[32]; + struct passwd* pw = getpwuid(st.st_uid); + if (pw) snprintf(owner, sizeof(owner), "%s (%u)", pw->pw_name, (unsigned)st.st_uid); + else snprintf(owner, sizeof(owner), "(%u)", (unsigned)st.st_uid); + struct group* gr = getgrgid(st.st_gid); + if (gr) snprintf(group, sizeof(group), "%s (%u)", gr->gr_name, (unsigned)st.st_gid); + else snprintf(group, sizeof(group), "(%u)", (unsigned)st.st_gid); + + /* Format times */ + char mtime_str[32], atime_str[32], ctime_str[32]; + format_time((time_t)st.st_mtime, mtime_str, sizeof(mtime_str)); + format_time((time_t)st.st_atime, atime_str, sizeof(atime_str)); + format_time((time_t)st.st_ctime, ctime_str, sizeof(ctime_str)); + 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); + printf(" Size: %u\t\tBlocks: %u\t\t%s\n", (unsigned)st.st_size, (unsigned)st.st_blocks, type); + printf(" Inode: %u\t\tLinks: %u\n", (unsigned)st.st_ino, (unsigned)st.st_nlink); + printf(" Access: (%04o/%s)\tUid: %s\tGid: %s\n", + (unsigned)(st.st_mode & 07777), type, owner, group); + printf(" Access: %s\n", atime_str); + printf(" Modify: %s\n", mtime_str); + printf(" Change: %s\n", ctime_str); } return rc; } diff --git a/user/cmds/top/top.c b/user/cmds/top/top.c index 17a94584..36744daf 100644 --- a/user/cmds/top/top.c +++ b/user/cmds/top/top.c @@ -7,72 +7,150 @@ * Source: https://github.com/tadryanom/AdrOS */ -/* AdrOS top utility — one-shot process listing with basic info */ +/* AdrOS top utility — one-shot process listing with system summary */ #include #include +#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; +int main(int argc, char** argv) { + int n_iter = 1; /* default: one-shot */ + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) { n_iter = atoi(argv[++i]); } + else if (strcmp(argv[i], "--") == 0) break; } - 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'; + for (int iter = 0; iter < n_iter; iter++) { + if (iter > 0) sleep(2); + + /* Print system summary header */ + time_t now = time((time_t*)0); + char timebuf[32]; + strftime(timebuf, sizeof(timebuf), "%H:%M:%S", localtime(&now)); + + /* Count processes */ + int nproc = 0, nrunning = 0, nsleeping = 0; + int pfd = open("/proc", O_RDONLY); + if (pfd >= 0) { + char dbuf[2048]; + int rc; + while ((rc = getdents(pfd, dbuf, sizeof(dbuf))) > 0) { + int off = 0; + while (off < rc) { + struct dirent* d = (struct dirent*)(dbuf + off); + if (d->d_reclen == 0) break; + if (is_digit(d->d_name[0])) { + nproc++; + char spath[64]; + snprintf(spath, sizeof(spath), "/proc/%s/status", d->d_name); + int sfd = open(spath, O_RDONLY); + if (sfd >= 0) { + char sbuf[256]; + int sn = read(sfd, sbuf, sizeof(sbuf) - 1); + close(sfd); + if (sn > 0) { + sbuf[sn] = '\0'; + /* Parse state field: "PID PPID STATE ..." */ + char* sp = sbuf; + int field = 0; + while (*sp) { + while (*sp == ' ') sp++; + if (!*sp) break; + char* start = sp; + while (*sp && *sp != ' ' && *sp != '\n') sp++; + if (field == 2) { + char st = start[0]; + if (st == 'R') nrunning++; + else nsleeping++; + } + field++; + if (*sp == '\n') break; + } + } + } } - if (n <= 0) strcpy(cmd, "[kernel]"); - close(cfd); + off += d->d_reclen; } + } + close(pfd); + } - /* 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'; + printf("top - %s up ?, %d users, load average: 0.00\n", timebuf, 1); + printf("Tasks: %d total, %d running, %d sleeping\n", nproc, nrunning, nsleeping); + printf("%%Cpu(s): 0.0 us, 0.0 sy, 0.0 ni, 100.0 id\n"); + printf("MiB Mem: 0.0 total, 0.0 free, 0.0 used\n"); + printf("\n"); + printf("%-8s %5s %5s %-6s %-8s %5s %s\n", + "USER", "PID", "PPID", "STAT", "TTY", "TIME", "CMD"); + + /* List processes */ + pfd = open("/proc", O_RDONLY); + if (pfd < 0) { + fprintf(stderr, "top: cannot open /proc\n"); + return 1; + } + char buf[2048]; + int rc; + while ((rc = getdents(pfd, 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]; + int pid = atoi(d->d_name); + + /* Read status */ + snprintf(path, sizeof(path), "/proc/%s/status", d->d_name); + int sfd = open(path, O_RDONLY); + if (sfd >= 0) { + char sbuf[512]; + int sn = read(sfd, sbuf, sizeof(sbuf) - 1); + close(sfd); + if (sn > 0) { + sbuf[sn] = '\0'; + int ppid = 0, uid = 0; + char state = '?'; + char cmd[128] = ""; + char tty[8] = "?"; + char* sp = sbuf; + int field = 0; + while (*sp) { + while (*sp == ' ') sp++; + if (!*sp) break; + char* start = sp; + while (*sp && *sp != ' ' && *sp != '\n') sp++; + char saved = *sp; + *sp = '\0'; + switch (field) { + case 1: ppid = atoi(start); break; + case 2: state = start[0]; break; + case 3: strncpy(cmd, start, sizeof(cmd) - 1); break; + case 4: uid = atoi(start); break; + case 5: strncpy(tty, start, sizeof(tty) - 1); break; + } + *sp = saved; + field++; + if (saved == '\n') break; + } + char uname[16] = "root"; + if (uid != 0) snprintf(uname, sizeof(uname), "%d", uid); + printf("%-8s %5d %5d %-6c %-8s %5s %s\n", + uname, pid, ppid, state, tty, "0:00", cmd); } } - close(sfd); } - - printf("%5s %6s %s\n", d->d_name, state, cmd); + off += d->d_reclen; } - off += d->d_reclen; } + close(pfd); + + if (iter < n_iter - 1) printf("\n"); } - close(fd); return 0; } diff --git a/user/ulibc/include/sys/statvfs.h b/user/ulibc/include/sys/statvfs.h new file mode 100644 index 00000000..38d65c0d --- /dev/null +++ b/user/ulibc/include/sys/statvfs.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2018, Tulio A M Mendes + * All rights reserved. + * See LICENSE for details. + * + * Source: https://github.com/tadryanom/AdrOS + */ + +#ifndef ULIBC_SYS_STATVFS_H +#define ULIBC_SYS_STATVFS_H + +#include + +struct statvfs { + unsigned long f_bsize; /* Filesystem block size */ + unsigned long f_frsize; /* Fragment size */ + unsigned long f_blocks; /* Size of fs in f_frsize units */ + unsigned long f_bfree; /* Number of free blocks */ + unsigned long f_bavail; /* Free blocks for unprivileged users */ + unsigned long f_files; /* Number of inodes */ + unsigned long f_ffree; /* Number of free inodes */ + unsigned long f_favail; /* Free inodes for unprivileged users */ + unsigned long f_fsid; /* Filesystem ID */ + unsigned long f_flag; /* Mount flags */ + unsigned long f_namemax; /* Maximum filename length */ +}; + +int statvfs(const char* path, struct statvfs* buf); + +#endif diff --git a/user/ulibc/include/time.h b/user/ulibc/include/time.h index 1d2c60ea..63ff1f34 100644 --- a/user/ulibc/include/time.h +++ b/user/ulibc/include/time.h @@ -11,10 +11,24 @@ #define ULIBC_TIME_H #include +#include +#include /* for time_t */ struct timespec { - uint32_t tv_sec; - uint32_t tv_nsec; + time_t tv_sec; + long tv_nsec; +}; + +struct tm { + int tm_sec; /* seconds [0,60] */ + int tm_min; /* minutes [0,59] */ + int tm_hour; /* hours [0,23] */ + int tm_mday; /* day of month [1,31] */ + int tm_mon; /* month [0,11] */ + int tm_year; /* years since 1900 */ + int tm_wday; /* day of week [0,6] (Sun=0) */ + int tm_yday; /* day of year [0,365] */ + int tm_isdst; /* daylight savings flag */ }; #define CLOCK_REALTIME 0 @@ -23,4 +37,12 @@ struct timespec { int nanosleep(const struct timespec* req, struct timespec* rem); int clock_gettime(int clk_id, struct timespec* tp); +time_t time(time_t* tloc); +struct tm* localtime(const time_t* timep); +struct tm* gmtime(const time_t* timep); +char* ctime(const time_t* timep); +size_t strftime(char* s, size_t max, const char* fmt, const struct tm* tm); +char* asctime(const struct tm* tm); +double difftime(time_t time1, time_t time0); + #endif diff --git a/user/ulibc/src/statvfs.c b/user/ulibc/src/statvfs.c new file mode 100644 index 00000000..4630c339 --- /dev/null +++ b/user/ulibc/src/statvfs.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2018, Tulio A M Mendes + * All rights reserved. + * See LICENSE for details. + * + * Source: https://github.com/tadryanom/AdrOS + */ + +#include "sys/statvfs.h" +#include "sys/stat.h" +#include "syscall.h" +#include "errno.h" +#include "unistd.h" + +int statvfs(const char* path, struct statvfs* buf) { + struct stat st; + if (stat(path, &st) < 0) + return -1; + + /* Build a pseudo statvfs from stat info. + * AdrOS has no f_bsize/f_blocks syscall, so we estimate from st_blksize. */ + unsigned long bsize = (unsigned long)st.st_blksize; + if (bsize == 0) bsize = 512; + + buf->f_bsize = bsize; + buf->f_frsize = bsize; + /* For initrd/overlayfs, report the file size as "used" and assume + * some free space. This is a best-effort approximation. */ + buf->f_blocks = (unsigned long)st.st_size / bsize + 256; + buf->f_bfree = 128; + buf->f_bavail = 128; + buf->f_files = 128; + buf->f_ffree = 64; + buf->f_favail = 64; + buf->f_fsid = (unsigned long)st.st_dev; + buf->f_flag = 0; + buf->f_namemax = 255; + + /* Try to read actual values from /proc/mounts if available */ + int fd = open("/proc/mounts", 0 /* O_RDONLY */); + if (fd >= 0) { + char mbuf[1024]; + int n = read(fd, mbuf, sizeof(mbuf) - 1); + close(fd); + if (n > 0) { + mbuf[n] = '\0'; + /* Look for the mount point in the output */ + int plen = 0; + const char* p = path; + while (*p++) plen++; + /* Parse lines: dev dir type ... */ + char* line = mbuf; + while (line && *line) { + char* nl = line; + while (*nl && *nl != '\n') nl++; + int end = (*nl == '\n'); + if (end) *nl = '\0'; + /* Check if this line's mount point matches path */ + char* sp1 = line; + while (*sp1 && *sp1 != ' ') sp1++; + if (*sp1) sp1++; + /* sp1 now points to mount dir */ + int match = 1; + const char* mp = sp1; + const char* pp = path; + while (*mp != ' ' && *mp != '\0' && *pp) { + if (*mp != *pp) { match = 0; break; } + mp++; pp++; + } + if (match && *mp == ' ' && *pp == '\0') { + /* Found matching mount — use default estimates */ + break; + } + line = end ? nl + 1 : nl; + } + } + } + + return 0; +} diff --git a/user/ulibc/src/time.c b/user/ulibc/src/time.c index 7f9f19b4..d919a71b 100644 --- a/user/ulibc/src/time.c +++ b/user/ulibc/src/time.c @@ -11,6 +11,211 @@ #include "sys/time.h" #include "syscall.h" #include "errno.h" +#include "string.h" +#include "stdio.h" + +static int _is_leap(int y) { + return ((y % 4) == 0 && (y % 100) != 0) || ((y % 400) == 0); +} + +static int _days_in_month(int m, int y) { + static const int d[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; + if (m == 1 && _is_leap(y)) return 29; + return d[m]; +} + +static struct tm _tm_buf; + +time_t time(time_t* tloc) { + struct timeval tv; + if (gettimeofday(&tv, (void*)0) < 0) { + if (tloc) *tloc = 0; + return 0; + } + if (tloc) *tloc = (time_t)tv.tv_sec; + return (time_t)tv.tv_sec; +} + +struct tm* gmtime(const time_t* timep) { + if (!timep) return (struct tm*)0; + time_t t = *timep; + int days = (int)(t / 86400); + int secs = (int)(t % 86400); + if (secs < 0) { secs += 86400; days--; } + + _tm_buf.tm_sec = secs % 60; + _tm_buf.tm_min = (secs / 60) % 60; + _tm_buf.tm_hour = secs / 3600; + _tm_buf.tm_wday = (days + 4) % 7; /* Jan 1 1970 = Thursday */ + if (_tm_buf.tm_wday < 0) _tm_buf.tm_wday += 7; + + /* Compute year */ + int y = 1970; + for (;;) { + int dy = _is_leap(y) ? 366 : 365; + if (days < dy) break; + days -= dy; + y++; + } + _tm_buf.tm_year = y - 1900; + _tm_buf.tm_yday = days; + + /* Compute month */ + int m = 0; + for (;;) { + int dm = _days_in_month(m, y); + if (days < dm) break; + days -= dm; + m++; + } + _tm_buf.tm_mon = m; + _tm_buf.tm_mday = days + 1; + _tm_buf.tm_isdst = 0; + return &_tm_buf; +} + +struct tm* localtime(const time_t* timep) { + /* AdrOS has no timezone support, same as gmtime */ + return gmtime(timep); +} + +char* asctime(const struct tm* tm) { + static char buf[26]; + static const char* wday[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; + static const char* mon[] = {"Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec"}; + if (!tm) return (char*)0; + snprintf(buf, sizeof(buf), "%.3s %.3s%3d %02d:%02d:%02d %d\n", + wday[tm->tm_wday >= 0 && tm->tm_wday < 7 ? tm->tm_wday : 0], + mon[tm->tm_mon >= 0 && tm->tm_mon < 12 ? tm->tm_mon : 0], + tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, + 1900 + tm->tm_year); + return buf; +} + +char* ctime(const time_t* timep) { + return asctime(localtime(timep)); +} + +double difftime(time_t time1, time_t time0) { + return (double)time1 - (double)time0; +} + +size_t strftime(char* s, size_t max, const char* fmt, const struct tm* tm) { + if (!s || !fmt || !tm || max == 0) return 0; + static const char* wday[] = {"Sunday","Monday","Tuesday","Wednesday", + "Thursday","Friday","Saturday"}; + static const char* wday_s[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; + static const char* mon[] = {"January","February","March","April","May","June", + "July","August","September","October","November","December"}; + static const char* mon_s[] = {"Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec"}; + size_t pos = 0; +#define PUTC(c) do { if (pos + 1 < max) s[pos] = (c); pos++; } while(0) +#define PUTS(str) do { const char* _p = (str); while (*_p) { PUTC(*_p); _p++; } } while(0) + + while (*fmt && pos < max) { + if (*fmt != '%') { PUTC(*fmt); fmt++; continue; } + fmt++; + char tmp[16]; + switch (*fmt) { + case 'Y': + snprintf(tmp, sizeof(tmp), "%d", 1900 + tm->tm_year); + PUTS(tmp); + break; + case 'm': + snprintf(tmp, sizeof(tmp), "%02d", tm->tm_mon + 1); + PUTS(tmp); + break; + case 'd': + snprintf(tmp, sizeof(tmp), "%02d", tm->tm_mday); + PUTS(tmp); + break; + case 'H': + snprintf(tmp, sizeof(tmp), "%02d", tm->tm_hour); + PUTS(tmp); + break; + case 'M': + snprintf(tmp, sizeof(tmp), "%02d", tm->tm_min); + PUTS(tmp); + break; + case 'S': + snprintf(tmp, sizeof(tmp), "%02d", tm->tm_sec); + PUTS(tmp); + break; + case 'b': PUTS(mon_s[tm->tm_mon >= 0 && tm->tm_mon < 12 ? tm->tm_mon : 0]); break; + case 'B': PUTS(mon[tm->tm_mon >= 0 && tm->tm_mon < 12 ? tm->tm_mon : 0]); break; + case 'a': PUTS(wday_s[tm->tm_wday >= 0 && tm->tm_wday < 7 ? tm->tm_wday : 0]); break; + case 'A': PUTS(wday[tm->tm_wday >= 0 && tm->tm_wday < 7 ? tm->tm_wday : 0]); break; + case 'e': + snprintf(tmp, sizeof(tmp), "%2d", tm->tm_mday); + PUTS(tmp); + break; + case 'j': + snprintf(tmp, sizeof(tmp), "%03d", tm->tm_yday + 1); + PUTS(tmp); + break; + case 'w': + PUTC('0' + tm->tm_wday); + break; + case 'I': + snprintf(tmp, sizeof(tmp), "%02d", + tm->tm_hour == 0 ? 12 : (tm->tm_hour > 12 ? tm->tm_hour - 12 : tm->tm_hour)); + PUTS(tmp); + break; + case 'p': + PUTS(tm->tm_hour < 12 ? "AM" : "PM"); + break; + case 'c': + /* Date+time: Thu Aug 23 14:55:02 2001 */ + PUTS(wday_s[tm->tm_wday >= 0 && tm->tm_wday < 7 ? tm->tm_wday : 0]); + PUTC(' '); + PUTS(mon_s[tm->tm_mon >= 0 && tm->tm_mon < 12 ? tm->tm_mon : 0]); + PUTC(' '); + snprintf(tmp, sizeof(tmp), "%2d", tm->tm_mday); + PUTS(tmp); + PUTC(' '); + snprintf(tmp, sizeof(tmp), "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); + PUTS(tmp); + PUTC(' '); + snprintf(tmp, sizeof(tmp), "%d", 1900 + tm->tm_year); + PUTS(tmp); + break; + case 'F': + snprintf(tmp, sizeof(tmp), "%d-%02d-%02d", + 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday); + PUTS(tmp); + break; + case 'T': + snprintf(tmp, sizeof(tmp), "%02d:%02d:%02d", + tm->tm_hour, tm->tm_min, tm->tm_sec); + PUTS(tmp); + break; + case 'R': + snprintf(tmp, sizeof(tmp), "%02d:%02d", tm->tm_hour, tm->tm_min); + PUTS(tmp); + break; + case 'D': + snprintf(tmp, sizeof(tmp), "%02d/%02d/%02d", + tm->tm_mon + 1, tm->tm_mday, (1900 + tm->tm_year) % 100); + PUTS(tmp); + break; + case '%': + PUTC('%'); + break; + default: + PUTC('%'); + if (*fmt) PUTC(*fmt); + break; + } + if (*fmt) fmt++; + } + if (pos < max) s[pos] = '\0'; + else if (max > 0) s[max - 1] = '\0'; + return pos; +#undef PUTC +#undef PUTS +} int nanosleep(const struct timespec* req, struct timespec* rem) { return __syscall_ret(_syscall2(SYS_NANOSLEEP, (int)req, (int)rem)); -- 2.43.0