1DOLLY 32MODULE core-tools34REQUIRES HEADER libc5REQUIRES HEADER runtime6REQUIRES TOOL   cc7REQUIRES TOOL   rm89# Small Dolly-owned commands live directly in the module. The root's module10# hash authenticates their source; each TOOL export names the compiled result.11FILE /tmp/core-tools/foreground.c12    #include <errno.h>13    #include <stdio.h>14    #include <string.h>15    #include <dolly/runtime.h>16    17    int main(int argc, char **argv) {18      int first = 1;19      int interactive = 0;20      if (argc == 2 && strcmp(argv[1], "--help") == 0) {21        fputs("usage: foreground [-i] /absolute/program [ARG ...]\n", stdout);22        return 0;23      }24      if (first < argc && strcmp(argv[first], "-i") == 0) {25        interactive = 1;26        first++;27      }28      if (first == argc || argv[first][0] != '/') {29        fputs("usage: foreground [-i] /absolute/program [ARG ...]\n", stderr);30        return 2;31      }32      const int pid = dolly_spawn_foreground(argv[first], argc - first,33                                             argv + first, interactive);34      if (pid < 0) {35        fprintf(stderr, "foreground: %s: %s\n", argv[first], strerror(-pid));36        return pid == -ENOENT ? 127 : 126;37      }38      int status;39      const int waited = dolly_wait(pid, &status);40      if (waited < 0) {41        fprintf(stderr, "foreground: wait: %s\n", strerror(-waited));42        return 126;43      }44      return status;45    }46FILE /tmp/core-tools/help.c47    #include <stdio.h>48    #include <stdlib.h>49    #include <string.h>50    #include <unistd.h>51    52    int main(int argc, char **argv) {53      if (argc > 2 || (argc == 2 && strcmp(argv[1], "--help") != 0)) {54        fprintf(stderr, "help: unsupported argument: %s\n", argv[1]);55        return 2;56      }57      const char *path = getenv("PATH");58      fputs("Dolly Slop: minimal agent-tool compatibility inside Wasm\n", stdout);59      fputs("stateful builtins: : . source eval exec exit return cd export unset set shift read getopts local break continue type\n", stdout);60      printf("PATH=%s\n", path == NULL ? "" : path);61      fputs("commands are files on PATH; inspect this image with ls /bin /usr/bin\n", stdout);62      fputs("exec supports permanent redirections, not replacing the shell\n", stdout);63      fputs("operators: ; newline backslash-newline && || ! | < > >> 2> 2>> 2>&1\n", stdout);64      fputs("conditionals: if COMMANDS; then COMMANDS; [elif ...; then ...;] [else ...;] fi\n", stdout);65      fputs("loops: for NAME [in WORD ...]; do COMMANDS; done; while|until COMMANDS; do COMMANDS; done; break|continue [N]\n", stdout);66      fputs("selection: case WORD in PATTERN[|PATTERN]...) COMMANDS ;; ... esac\n", stdout);67      fputs("functions/groups: NAME () { COMMANDS; }; return [STATUS]; { COMMANDS; }; (COMMANDS)\n", stdout);68      fputs("expansion: $VAR ${VAR} ${VAR:-WORD} ${VAR:=WORD} ${VAR:+WORD} ${VAR:?WORD} ${#VAR} ${VAR#PATTERN} ${VAR##PATTERN} ${VAR%PATTERN} ${VAR%%PATTERN} $? $$ $# $0..9 $@ $* $(command) `command` $((integer expression)) and globs; set [--] ARG...; shift [N]\n", stdout);69      fputs("options: set -e/+e -x/+x -o/+o pipefail; set -o lists finite options\n", stdout);70      fputs("make recipes run serially through /bin/slop -c\n", stdout);71      if (access("/usr/bin/tsc", F_OK) == 0)72        fputs("TypeScript: tsc FILE.ts --target ES2023 --module ES2022\n", stdout);73      if (access("/usr/bin/bonnie", F_OK) == 0) {74        fputs("Python packages: bonnie install PACKAGE; bonnie list|freeze|show|check\n", stdout);75      }76      return 0;77    }78FILE /tmp/core-tools/pwd.c79    #include <errno.h>80    #include <stdio.h>81    #include <string.h>82    #include <unistd.h>83    84    int main(int argc, char **argv) {85      if (argc == 2 && strcmp(argv[1], "--help") == 0) {86        fputs("usage: pwd [-L|-P]\n", stdout);87        return 0;88      }89      if (argc > 2 ||90          (argc == 2 && strcmp(argv[1], "-L") != 0 && strcmp(argv[1], "-P") != 0)) {91        fprintf(stderr, "pwd: unsupported option: %s\n", argc > 1 ? argv[1] : "");92        return 2;93      }94      char cwd[1024];95      if (getcwd(cwd, sizeof(cwd)) == NULL) {96        fprintf(stderr, "pwd: %s\n", strerror(errno));97        return 1;98      }99      fputs(cwd, stdout);100      fputc('\n', stdout);101      return 0;102    }103FILE /tmp/core-tools/cd.c104    #include <errno.h>105    #include <stdio.h>106    #include <stdlib.h>107    #include <string.h>108    #include <unistd.h>109    110    int main(int argc, char **argv) {111      int first_path = 1;112      if (first_path < argc && strcmp(argv[first_path], "--help") == 0) {113        fputs("usage: cd [--] [DIRECTORY]\n", stdout);114        return 0;115      }116      if (first_path < argc && strcmp(argv[first_path], "--") == 0) first_path++;117      if (argc - first_path > 1) {118        fputs("cd: expected at most one path\n", stderr);119        return 2;120      }121      const char *path = first_path < argc ? argv[first_path] : getenv("HOME");122      if (path == NULL || path[0] == '\0') path = "/workspace";123      if (chdir(path) != 0) {124        fprintf(stderr, "cd: %s: %s\n", path, strerror(errno));125        return 1;126      }127      return 0;128    }129FILE /tmp/core-tools/cat.c130    #include <errno.h>131    #include <stdio.h>132    #include <string.h>133    134    static int copy_stream(FILE *input, const char *name, int number_lines,135                           unsigned long *line_number) {136      unsigned char buffer[4096];137      int line_start = 1;138      size_t count;139      while ((count = fread(buffer, 1, sizeof(buffer), input)) != 0) {140        if (!number_lines) {141          if (fwrite(buffer, 1, count, stdout) != count) {142            fprintf(stderr, "cat: %s: write failed\n", name);143            return 1;144          }145          continue;146        }147        for (size_t index = 0; index < count; index++) {148          if (line_start) {149            if (fprintf(stdout, "%6lu\t", (*line_number)++) < 0) return 1;150            line_start = 0;151          }152          if (fputc(buffer[index], stdout) == EOF) return 1;153          if (buffer[index] == '\n') line_start = 1;154        }155      }156      if (ferror(input)) {157        fprintf(stderr, "cat: %s: %s\n", name, strerror(errno));158        return 1;159      }160      return 0;161    }162    163    int main(int argc, char **argv) {164      int number_lines = 0;165      int first_file = 1;166      for (; first_file < argc; first_file++) {167        if (strcmp(argv[first_file], "--") == 0) {168          first_file++;169          break;170        }171        if (strcmp(argv[first_file], "--help") == 0) {172          fputs("usage: cat [-n] [--] [FILE ...]\n", stdout);173          fputs("with no FILE, or when FILE is -, read standard input\n", stdout);174          return 0;175        }176        if (strcmp(argv[first_file], "-n") == 0) {177          number_lines = 1;178          continue;179        }180        if (argv[first_file][0] == '-' && strcmp(argv[first_file], "-") != 0) {181          fprintf(stderr, "cat: unsupported option: %s\n", argv[first_file]);182          return 2;183        }184        break;185      }186    187      int status = 0;188      unsigned long line_number = 1;189      if (first_file == argc) {190        status = copy_stream(stdin, "standard input", number_lines, &line_number);191      }192      for (int index = first_file; index < argc; index++) {193        FILE *input = stdin;194        if (strcmp(argv[index], "-") != 0) {195          input = fopen(argv[index], "rb");196          if (input == NULL) {197            fprintf(stderr, "cat: %s: %s\n", argv[index], strerror(errno));198            status = 1;199            continue;200          }201        }202        if (copy_stream(input, argv[index], number_lines, &line_number) != 0) {203          status = 1;204        }205        if (input != stdin && fclose(input) != 0) {206          fprintf(stderr, "cat: %s: %s\n", argv[index], strerror(errno));207          status = 1;208        }209      }210      if (fflush(stdout) != 0) status = 1;211      return status;212    }213FILE /tmp/core-tools/echo.c214    #include <stdio.h>215    #include <string.h>216    217    int main(int argc, char **argv) {218      int newline = 1;219      int first = 1;220      if (first < argc && strcmp(argv[first], "--help") == 0) {221        fputs("usage: echo [-n] [--] [ARG ...]\n", stdout);222        return 0;223      }224      if (first < argc && strcmp(argv[first], "-n") == 0) {225        newline = 0;226        first++;227      }228      if (first < argc && strcmp(argv[first], "--") == 0) first++;229    230      for (int index = first; index < argc; index++) {231        if (index != first) fputc(' ', stdout);232        fputs(argv[index], stdout);233      }234      if (newline) fputc('\n', stdout);235      fflush(stdout);236      return ferror(stdout) ? 1 : 0;237    }238FILE /tmp/core-tools/touch.c239    #include <errno.h>240    #include <stdio.h>241    #include <string.h>242    #include <utime.h>243    244    int main(int argc, char **argv) {245      int no_create = 0;246      int first_file = 1;247      for (; first_file < argc; first_file++) {248        if (strcmp(argv[first_file], "--") == 0) {249          first_file++;250          break;251        }252        if (strcmp(argv[first_file], "--help") == 0) {253          fputs("usage: touch [-c] [--] FILE ...\n", stdout);254          return 0;255        }256        if (strcmp(argv[first_file], "-c") == 0 ||257            strcmp(argv[first_file], "--no-create") == 0) {258          no_create = 1;259        } else if (argv[first_file][0] == '-') {260          fprintf(stderr, "touch: unsupported option: %s\n", argv[first_file]);261          return 2;262        } else {263          break;264        }265      }266      if (first_file == argc) {267        fputs("touch: missing file operand\n", stderr);268        return 2;269      }270    271      int status = 0;272      for (int index = first_file; index < argc; index++) {273        if (utime(argv[index], NULL) == 0) {274          continue;275        }276        if (errno != ENOENT) {277          fprintf(stderr, "touch: %s: %s\n", argv[index], strerror(errno));278          status = 1;279          continue;280        }281        if (no_create) continue;282        FILE *file = fopen(argv[index], "ab");283        if (file == NULL) {284          fprintf(stderr, "touch: %s: %s\n", argv[index], strerror(errno));285          status = 1;286        } else if (fclose(file) != 0) {287          fprintf(stderr, "touch: %s: %s\n", argv[index], strerror(errno));288          status = 1;289        }290      }291      return status;292    }293FILE /tmp/core-tools/clear.c294    #include <stdio.h>295    #include <string.h>296    297    int main(int argc, char **argv) {298      if (argc == 2 && strcmp(argv[1], "--help") == 0) {299        fputs("usage: clear\n", stdout);300        return 0;301      }302      if (argc != 1) {303        fprintf(stderr, "clear: unsupported option: %s\n", argv[1]);304        return 2;305      }306      fputs("\033[2J\033[H", stdout);307      fflush(stdout);308      return 0;309    }310FILE /tmp/core-tools/ls.c311    #define _POSIX_C_SOURCE 200809L312    313    #include <dirent.h>314    #include <errno.h>315    #include <stdint.h>316    #include <stdio.h>317    #include <stdlib.h>318    #include <string.h>319    #include <sys/stat.h>320    #include <time.h>321    322    typedef struct { char **items; size_t length; size_t capacity; } name_list;323    typedef struct {324      int show_all, almost_all, long_format, human, directory;325      int classify, recursive, reverse;326    } options;327    328    static void free_names(name_list *names) {329      for (size_t index = 0; index < names->length; index++) free(names->items[index]);330      free(names->items);331    }332    333    static int add_name(name_list *names, const char *name) {334      if (names->length == names->capacity) {335        size_t capacity = names->capacity == 0 ? 32 : names->capacity * 2;336        if (capacity < names->capacity || capacity > SIZE_MAX / sizeof(*names->items)) return -1;337        char **items = realloc(names->items, capacity * sizeof(*items));338        if (items == NULL) return -1;339        names->items = items;340        names->capacity = capacity;341      }342      char *copy = strdup(name);343      if (copy == NULL) return -1;344      names->items[names->length++] = copy;345      return 0;346    }347    348    static int compare_names(const void *left, const void *right) {349      return strcmp(*(const char *const *)left, *(const char *const *)right);350    }351    352    static char *join_path(const char *directory, const char *name) {353      const size_t directory_length = strlen(directory), name_length = strlen(name);354      const int slash = directory_length != 0 && directory[directory_length - 1] != '/';355      if (directory_length > SIZE_MAX - name_length - (size_t)slash - 1) return NULL;356      char *path = malloc(directory_length + (size_t)slash + name_length + 1);357      if (path == NULL) return NULL;358      memcpy(path, directory, directory_length);359      if (slash) path[directory_length] = '/';360      memcpy(path + directory_length + (size_t)slash, name, name_length + 1);361      return path;362    }363    364    static void format_size(char output[32], off_t size, int human) {365      if (!human || size < 1024) { snprintf(output, 32, "%lld", (long long)size); return; }366      static const char units[] = "KMGTPE";367      double value = (double)size;368      size_t unit = 0;369      do { value /= 1024.0; unit++; } while (value >= 1024.0 && unit < sizeof(units) - 1);370      if (value < 10.0) snprintf(output, 32, "%.1f%c", value, units[unit - 1]);371      else snprintf(output, 32, "%.0f%c", value, units[unit - 1]);372    }373    374    static int print_entry(const char *path, const char *name, const options *option) {375      struct stat metadata;376      if (lstat(path, &metadata) != 0) {377        fprintf(stderr, "ls: %s: %s\n", path, strerror(errno));378        return 1;379      }380      const char suffix = option->classify381          ? S_ISDIR(metadata.st_mode) ? '/' : S_ISLNK(metadata.st_mode) ? '@' : '\0'382          : '\0';383      if (!option->long_format) {384        fputs(name, stdout);385        if (suffix != '\0') fputc(suffix, stdout);386        fputc('\n', stdout);387        return 0;388      }389      char size[32], timestamp[32] = "?";390      format_size(size, metadata.st_size, option->human);391      struct tm *broken = localtime(&metadata.st_mtime);392      if (broken != NULL) strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M", broken);393      const char kind = S_ISDIR(metadata.st_mode) ? 'd' : S_ISLNK(metadata.st_mode) ? 'l' : '-';394      printf("%c %10s %s %s", kind, size, timestamp, name);395      if (suffix != '\0') fputc(suffix, stdout);396      fputc('\n', stdout);397      return 0;398    }399    400    static int hidden(const char *name, const options *option) {401      if (name[0] != '.' || option->show_all) return 0;402      if (option->almost_all && strcmp(name, ".") != 0 && strcmp(name, "..") != 0) return 0;403      return 1;404    }405    406    static int list_path(const char *path, const options *option, int print_heading) {407      struct stat metadata;408      if (lstat(path, &metadata) != 0) {409        fprintf(stderr, "ls: %s: %s\n", path, strerror(errno));410        return 1;411      }412      if (!S_ISDIR(metadata.st_mode) || option->directory) return print_entry(path, path, option);413      DIR *directory = opendir(path);414      if (directory == NULL) { fprintf(stderr, "ls: %s: %s\n", path, strerror(errno)); return 1; }415      name_list names = {0};416      int status = 0;417      struct dirent *entry;418      while ((entry = readdir(directory)) != NULL) {419        if (hidden(entry->d_name, option)) continue;420        if (add_name(&names, entry->d_name) != 0) { fputs("ls: out of memory\n", stderr); status = 1; break; }421      }422      if (closedir(directory) != 0) status = 1;423      if (names.length > 1) {424        qsort(names.items, names.length, sizeof(*names.items), compare_names);425      }426      if (print_heading) printf("%s:\n", path);427      for (size_t offset = 0; offset < names.length; offset++) {428        const size_t index = option->reverse ? names.length - offset - 1 : offset;429        char *entry_path = join_path(path, names.items[index]);430        if (entry_path == NULL || print_entry(entry_path, names.items[index], option) != 0) status = 1;431        free(entry_path);432      }433      if (option->recursive) {434        for (size_t offset = 0; offset < names.length; offset++) {435          const size_t index = option->reverse ? names.length - offset - 1 : offset;436          if (strcmp(names.items[index], ".") == 0 || strcmp(names.items[index], "..") == 0) continue;437          char *entry_path = join_path(path, names.items[index]);438          struct stat child;439          if (entry_path != NULL && lstat(entry_path, &child) == 0 && S_ISDIR(child.st_mode) && !S_ISLNK(child.st_mode)) {440            fputc('\n', stdout);441            if (list_path(entry_path, option, 1) != 0) status = 1;442          }443          free(entry_path);444        }445      }446      free_names(&names);447      return status;448    }449    450    static int short_option(options *option, char value) {451      switch (value) {452        case '1': return 0;453        case 'a': option->show_all = 1; option->almost_all = 0; return 0;454        case 'A': option->almost_all = 1; option->show_all = 0; return 0;455        case 'd': option->directory = 1; return 0;456        case 'F': case 'p': option->classify = 1; return 0;457        case 'h': option->human = 1; return 0;458        case 'l': option->long_format = 1; return 0;459        case 'R': option->recursive = 1; return 0;460        case 'r': option->reverse = 1; return 0;461        default: return -1;462      }463    }464    465    int main(int argc, char **argv) {466      options option = {0};467      int first_path = 1;468      for (; first_path < argc; first_path++) {469        const char *argument = argv[first_path];470        if (strcmp(argument, "--") == 0) { first_path++; break; }471        if (strcmp(argument, "--help") == 0) {472          fputs("usage: ls [-1aAdFhlpRr] [--color[=WHEN]] [--] [PATH ...]\n", stdout);473          return 0;474        }475        if (strncmp(argument, "--color", 7) == 0 || strcmp(argument, "--group-directories-first") == 0) continue;476        if (argument[0] != '-' || argument[1] == '\0') break;477        for (size_t index = 1; argument[index] != '\0'; index++) {478          if (short_option(&option, argument[index]) != 0) {479            fprintf(stderr, "ls: unsupported option: -%c\n", argument[index]);480            return 2;481          }482        }483      }484      if (first_path == argc) return list_path(".", &option, 0);485      const int multiple = argc - first_path > 1;486      int status = 0;487      for (int index = first_path; index < argc; index++) {488        if (index != first_path) fputc('\n', stdout);489        if (list_path(argv[index], &option, multiple) != 0) status = 1;490      }491      return status;492    }493FILE /tmp/core-tools/stat.c494    #define _POSIX_C_SOURCE 200809L495    496    #include <errno.h>497    #include <stdio.h>498    #include <string.h>499    #include <sys/stat.h>500    #include <time.h>501    502    static const char *kind(const struct stat *metadata) {503      if (S_ISDIR(metadata->st_mode)) return "directory";504      if (S_ISLNK(metadata->st_mode)) return "symbolic link";505      if (S_ISREG(metadata->st_mode)) return "regular file";506      if (S_ISCHR(metadata->st_mode)) return "character device";507      if (S_ISFIFO(metadata->st_mode)) return "fifo";508      return "other";509    }510    511    static void formatted(const char *format, const char *path, const struct stat *metadata) {512      for (const char *cursor = format; *cursor != '\0'; cursor++) {513        if (*cursor != '%' || cursor[1] == '\0') { fputc(*cursor, stdout); continue; }514        cursor++;515        switch (*cursor) {516          case '%': fputc('%', stdout); break;517          case 'n': fputs(path, stdout); break;518          case 's': printf("%lld", (long long)metadata->st_size); break;519          case 'F': fputs(kind(metadata), stdout); break;520          case 'Y': printf("%lld", (long long)metadata->st_mtime); break;521          case 'a': fputc('0', stdout); break;522          case 'A': fputc(S_ISDIR(metadata->st_mode) ? 'd' : '-', stdout); break;523          default: fputc('%', stdout); fputc(*cursor, stdout); break;524        }525      }526      fputc('\n', stdout);527    }528    529    int main(int argc, char **argv) {530      const char *format = NULL;531      int first = 1;532      if (first < argc && strcmp(argv[first], "-c") == 0) {533        if (++first == argc) { fputs("stat: -c requires a format\n", stderr); return 2; }534        format = argv[first++];535      } else if (first < argc && strncmp(argv[first], "--format=", 9) == 0) {536        format = argv[first++] + 9;537      } else if (first < argc && strcmp(argv[first], "--help") == 0) {538        fputs("usage: stat [-c FORMAT] FILE ...\nformats: %n name, %s size, %F type, %Y mtime\n", stdout);539        return 0;540      }541      if (first == argc) { fputs("stat: missing file operand\n", stderr); return 2; }542      int status = 0;543      for (; first < argc; first++) {544        struct stat metadata;545        if (lstat(argv[first], &metadata) != 0) {546          fprintf(stderr, "stat: %s: %s\n", argv[first], strerror(errno));547          status = 1;548          continue;549        }550        if (format != NULL) { formatted(format, argv[first], &metadata); continue; }551        char timestamp[32] = "?";552        struct tm *broken = localtime(&metadata.st_mtime);553        if (broken != NULL) strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", broken);554        printf("  File: %s\n  Size: %lld\tType: %s\nModify: %s\n",555               argv[first], (long long)metadata.st_size, kind(&metadata), timestamp);556      }557      return status;558    }559FILE /tmp/core-tools/file.c560    #define _POSIX_C_SOURCE 200809L561    562    #include <ctype.h>563    #include <errno.h>564    #include <stdio.h>565    #include <string.h>566    #include <sys/stat.h>567    568    static const char *classify(const char *path, int mime) {569      static char result[96];570      struct stat metadata;571      if (lstat(path, &metadata) != 0) {572        snprintf(result, sizeof(result), "cannot open: %s", strerror(errno));573        return result;574      }575      if (S_ISDIR(metadata.st_mode)) return mime ? "inode/directory" : "directory";576      FILE *stream = fopen(path, "rb");577      if (stream == NULL) {578        snprintf(result, sizeof(result), "cannot open: %s", strerror(errno));579        return result;580      }581      unsigned char bytes[512];582      const size_t length = fread(bytes, 1, sizeof(bytes), stream);583      fclose(stream);584      static const unsigned char wasm_magic[] = {0, 'a', 's', 'm', 1, 0, 0, 0};585      if (length >= sizeof(wasm_magic) && memcmp(bytes, wasm_magic, sizeof(wasm_magic)) == 0)586        return mime ? "application/wasm" : "WebAssembly binary module";587      if (length >= 8 && memcmp(bytes, "!<arch>\n", 8) == 0)588        return mime ? "application/x-archive" : "current ar archive";589      if (length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b)590        return mime ? "application/gzip" : "gzip compressed data";591      if (length == 0) return mime ? "application/x-empty" : "empty";592      int text = 1;593      for (size_t index = 0; index < length; index++) {594        if (bytes[index] == 0 || (!isprint(bytes[index]) && !isspace(bytes[index]))) { text = 0; break; }595      }596      return text ? (mime ? "text/plain" : "ASCII text")597                  : (mime ? "application/octet-stream" : "data");598    }599    600    int main(int argc, char **argv) {601      int brief = 0, mime = 0, first = 1;602      for (; first < argc; first++) {603        if (strcmp(argv[first], "--") == 0) { first++; break; }604        if (strcmp(argv[first], "-b") == 0 || strcmp(argv[first], "--brief") == 0) brief = 1;605        else if (strcmp(argv[first], "--mime-type") == 0) mime = 1;606        else if (strcmp(argv[first], "--help") == 0) { fputs("usage: file [-b] [--mime-type] FILE ...\n", stdout); return 0; }607        else if (argv[first][0] == '-') { fprintf(stderr, "file: unsupported option: %s\n", argv[first]); return 2; }608        else break;609      }610      if (first == argc) { fputs("file: missing file operand\n", stderr); return 2; }611      for (; first < argc; first++) {612        if (!brief) printf("%s: ", argv[first]);613        puts(classify(argv[first], mime));614      }615      return 0;616    }617FILE /tmp/core-tools/test.c618    #define _POSIX_C_SOURCE 200809L619    620    #include <errno.h>621    #include <stdio.h>622    #include <stdlib.h>623    #include <string.h>624    #include <sys/stat.h>625    626    #ifndef DOLLY_BRACKET627    #define DOLLY_BRACKET 0628    #endif629    630    static int unary(const char *operation, const char *value, int *known) {631      struct stat metadata;632      *known = 1;633      if (strcmp(operation, "-n") == 0) return value[0] != '\0';634      if (strcmp(operation, "-z") == 0) return value[0] == '\0';635      if (strcmp(operation, "-L") == 0 || strcmp(operation, "-h") == 0)636        return lstat(value, &metadata) == 0 && S_ISLNK(metadata.st_mode);637      if (strcmp(operation, "-e") == 0 || strcmp(operation, "-r") == 0 ||638          strcmp(operation, "-w") == 0) return stat(value, &metadata) == 0;639      if (strcmp(operation, "-f") == 0) return stat(value, &metadata) == 0 && S_ISREG(metadata.st_mode);640      // Dolly has no execute permission bits. PATH resolution likewise accepts a641      // regular file and leaves Wasm-format/ABI validation to the loader.642      if (strcmp(operation, "-x") == 0) return stat(value, &metadata) == 0 && S_ISREG(metadata.st_mode);643      if (strcmp(operation, "-d") == 0) return stat(value, &metadata) == 0 && S_ISDIR(metadata.st_mode);644      if (strcmp(operation, "-s") == 0) return stat(value, &metadata) == 0 && metadata.st_size > 0;645      *known = 0;646      return 0;647    }648    649    static int integer(const char *left, const char *operation, const char *right, int *known) {650      char *left_end, *right_end;651      errno = 0;652      const long long a = strtoll(left, &left_end, 10), b = strtoll(right, &right_end, 10);653      if (errno != 0 || left_end == left || right_end == right ||654          *left_end != '\0' || *right_end != '\0') return -1;655      *known = 1;656      if (strcmp(operation, "-eq") == 0) return a == b;657      if (strcmp(operation, "-ne") == 0) return a != b;658      if (strcmp(operation, "-gt") == 0) return a > b;659      if (strcmp(operation, "-ge") == 0) return a >= b;660      if (strcmp(operation, "-lt") == 0) return a < b;661      if (strcmp(operation, "-le") == 0) return a <= b;662      *known = 0;663      return 0;664    }665    666    enum { EXPRESSION_DEPTH_LIMIT = 64 };667    668    static int top_level_operator(int argc, char **argv, const char *operation,669                                  int *error) {670      int parentheses = 0;671      for (int index = 0; index < argc; ++index) {672        if (strcmp(argv[index], "(") == 0) {673          parentheses++;674        } else if (strcmp(argv[index], ")") == 0) {675          if (parentheses == 0) {676            *error = 1;677            return -1;678          }679          parentheses--;680        } else if (parentheses == 0 && strcmp(argv[index], operation) == 0) {681          return index;682        }683      }684      if (parentheses != 0) *error = 1;685      return -1;686    }687    688    static int outer_parentheses(int argc, char **argv, int *error) {689      if (argc < 2 || strcmp(argv[0], "(") != 0) return 0;690      int depth = 0;691      for (int index = 0; index < argc; ++index) {692        if (strcmp(argv[index], "(") == 0) depth++;693        else if (strcmp(argv[index], ")") == 0) {694          if (--depth < 0) {695            *error = 1;696            return 0;697          }698          if (depth == 0) return index == argc - 1;699        }700      }701      *error = 1;702      return 0;703    }704    705    static int evaluate(int argc, char **argv, int *error, unsigned depth) {706      *error = 0;707      if (argc == 0) return 0;708      if (argc == 1) return argv[0][0] != '\0';709      if (depth == EXPRESSION_DEPTH_LIMIT) {710        *error = 1;711        return 0;712      }713      if (outer_parentheses(argc, argv, error)) {714        if (argc == 2) {715          *error = 1;716          return 0;717        }718        return evaluate(argc - 2, argv + 1, error, depth + 1);719      }720      if (*error) return 0;721    722      int operation = top_level_operator(argc, argv, "-o", error);723      if (*error) return 0;724      if (operation >= 0) {725        if (operation == 0 || operation + 1 == argc) {726          *error = 1;727          return 0;728        }729        int left_error = 0;730        int right_error = 0;731        const int left = evaluate(operation, argv, &left_error, depth + 1);732        const int right = evaluate(argc - operation - 1, argv + operation + 1,733                                   &right_error, depth + 1);734        *error = left_error || right_error;735        return left || right;736      }737    738      operation = top_level_operator(argc, argv, "-a", error);739      if (*error) return 0;740      if (operation >= 0) {741        if (operation == 0 || operation + 1 == argc) {742          *error = 1;743          return 0;744        }745        int left_error = 0;746        int right_error = 0;747        const int left = evaluate(operation, argv, &left_error, depth + 1);748        const int right = evaluate(argc - operation - 1, argv + operation + 1,749                                   &right_error, depth + 1);750        *error = left_error || right_error;751        return left && right;752      }753    754      if (strcmp(argv[0], "!") == 0) {755        return !evaluate(argc - 1, argv + 1, error, depth + 1);756      }757      if (argc == 2) {758        int known;759        const int result = unary(argv[0], argv[1], &known);760        if (known) return result;761      }762      if (argc == 3) {763        if (strcmp(argv[1], "=") == 0 || strcmp(argv[1], "==") == 0) return strcmp(argv[0], argv[2]) == 0;764        if (strcmp(argv[1], "!=") == 0) return strcmp(argv[0], argv[2]) != 0;765        int known = 0;766        const int result = integer(argv[0], argv[1], argv[2], &known);767        if (result < 0) { *error = 1; return 0; }768        if (known) return result;769      }770      *error = 1;771      return 0;772    }773    774    int main(int argc, char **argv) {775      argc--; argv++;776      if (DOLLY_BRACKET) {777        if (argc == 0 || strcmp(argv[argc - 1], "]") != 0) { fputs("[: missing ]\n", stderr); return 2; }778        argc--;779      }780      int error = 0;781      const int result = evaluate(argc, argv, &error, 0);782      if (error) { fputs(DOLLY_BRACKET ? "[: unsupported expression\n" : "test: unsupported expression\n", stderr); return 2; }783      return result ? 0 : 1;784    }785FILE /tmp/core-tools/bracket.c786    #define DOLLY_BRACKET 1787    #include "test.c"788FILE /tmp/core-tools/mv.c789    #define _POSIX_C_SOURCE 200809L790    791    #include <errno.h>792    #include <stdint.h>793    #include <stdio.h>794    #include <stdlib.h>795    #include <string.h>796    #include <sys/stat.h>797    798    static char *base_name_copy(const char *path) {799      size_t length = strlen(path);800      while (length > 1 && path[length - 1] == '/') length--;801      size_t start = length;802      while (start > 0 && path[start - 1] != '/') start--;803      return strndup(path + start, length - start);804    }805    806    static int move_one(const char *source, const char *destination,807                        int destination_is_directory) {808      char *target = NULL;809      if (destination_is_directory) {810        char *name = base_name_copy(source);811        if (name == NULL) {812          fputs("mv: out of memory\n", stderr);813          return 1;814        }815        const size_t destination_length = strlen(destination);816        const size_t name_length = strlen(name);817        const int slash = destination_length != 0 &&818                          destination[destination_length - 1] != '/';819        if (destination_length > SIZE_MAX - name_length - (size_t)slash - 1) {820          fprintf(stderr, "mv: %s/%s: path is too long\n", destination, name);821          free(name);822          return 1;823        }824        target = malloc(destination_length + (size_t)slash + name_length + 1);825        if (target == NULL) {826          fputs("mv: out of memory\n", stderr);827          free(name);828          return 1;829        }830        memcpy(target, destination, destination_length);831        if (slash) target[destination_length] = '/';832        memcpy(target + destination_length + (size_t)slash, name, name_length + 1);833        free(name);834      }835      const char *resolved = target == NULL ? destination : target;836      if (rename(source, resolved) != 0) {837        fprintf(stderr, "mv: %s -> %s: %s\n", source, resolved, strerror(errno));838        free(target);839        return 1;840      }841      free(target);842      return 0;843    }844    845    int main(int argc, char **argv) {846      int first = 1;847      while (first < argc && argv[first][0] == '-') {848        if (strcmp(argv[first], "--") == 0) {849          first++;850          break;851        }852        if (strcmp(argv[first], "--help") == 0) {853          fputs("usage: mv [-f] [--] SOURCE... DESTINATION\n", stdout);854          return 0;855        }856        if (strcmp(argv[first], "-f") != 0) {857          fprintf(stderr, "mv: unsupported option %s\n", argv[first]);858          return 2;859        }860        first++;861      }862      if (argc - first < 2) {863        fputs("usage: mv [-f] SOURCE... DESTINATION\n", stderr);864        return 2;865      }866      const char *destination = argv[argc - 1];867      struct stat metadata;868      const int destination_is_directory =869          stat(destination, &metadata) == 0 && S_ISDIR(metadata.st_mode);870      if (argc - first > 2 && !destination_is_directory) {871        fprintf(stderr, "mv: %s is not a directory\n", destination);872        return 1;873      }874      int status = 0;875      for (int index = first; index < argc - 1; index++) {876        if (move_one(argv[index], destination, destination_is_directory) != 0) {877          status = 1;878        }879      }880      return status;881    }882FILE /tmp/core-tools/cp.c883    #define _POSIX_C_SOURCE 200809L884    #define _XOPEN_SOURCE 700885    886    #include <dirent.h>887    #include <errno.h>888    #include <fcntl.h>889    #include <limits.h>890    #include <stdint.h>891    #include <stdio.h>892    #include <stdlib.h>893    #include <string.h>894    #include <sys/stat.h>895    #include <unistd.h>896    897    static int recursive;898    static int verbose;899    900    static char *base_name_copy(const char *path) {901      size_t length = strlen(path);902      while (length > 1 && path[length - 1] == '/') length--;903      size_t start = length;904      while (start > 0 && path[start - 1] != '/') start--;905      return strndup(path + start, length - start);906    }907    908    static char *join_path(const char *directory, const char *name) {909      const size_t directory_length = strlen(directory);910      const size_t name_length = strlen(name);911      const int slash = directory_length != 0 && directory[directory_length - 1] != '/';912      if (directory_length > SIZE_MAX - name_length - (size_t)slash - 1) {913        errno = ENAMETOOLONG;914        return NULL;915      }916      char *joined = malloc(directory_length + (size_t)slash + name_length + 1);917      if (joined == NULL) return NULL;918      memcpy(joined, directory, directory_length);919      if (slash) joined[directory_length] = '/';920      memcpy(joined + directory_length + (size_t)slash, name, name_length + 1);921      return joined;922    }923    924    static int copy_path(const char *source, const char *destination);925    926    static int copy_regular(const char *source, const char *destination) {927      int input = open(source, O_RDONLY);928      if (input < 0) {929        fprintf(stderr, "cp: %s: %s\n", source, strerror(errno));930        return 1;931      }932      struct stat source_metadata;933      struct stat destination_metadata;934      if (fstat(input, &source_metadata) != 0) {935        fprintf(stderr, "cp: %s: %s\n", source, strerror(errno));936        close(input);937        return 1;938      }939      if (stat(destination, &destination_metadata) == 0 &&940          source_metadata.st_dev == destination_metadata.st_dev &&941          source_metadata.st_ino == destination_metadata.st_ino) {942        fprintf(stderr, "cp: %s and %s are the same file\n", source, destination);943        close(input);944        return 1;945      }946      int output = open(destination, O_WRONLY | O_CREAT | O_TRUNC, 0666);947      if (output < 0) {948        fprintf(stderr, "cp: %s: %s\n", destination, strerror(errno));949        close(input);950        return 1;951      }952    953      int status = 0;954      unsigned char buffer[16384];955      for (;;) {956        ssize_t count = read(input, buffer, sizeof(buffer));957        if (count < 0 && errno == EINTR) continue;958        if (count < 0) {959          fprintf(stderr, "cp: %s: %s\n", source, strerror(errno));960          status = 1;961          break;962        }963        if (count == 0) break;964        size_t offset = 0;965        while (offset < (size_t)count) {966          ssize_t written = write(output, buffer + offset, (size_t)count - offset);967          if (written < 0 && errno == EINTR) continue;968          if (written <= 0) {969            fprintf(stderr, "cp: %s: %s\n", destination,970                    written == 0 ? "short write" : strerror(errno));971            status = 1;972            break;973          }974          offset += (size_t)written;975        }976        if (status != 0) break;977      }978      if (close(input) != 0 && status == 0) status = 1;979      if (close(output) != 0 && status == 0) {980        fprintf(stderr, "cp: %s: %s\n", destination, strerror(errno));981        status = 1;982      }983      return status;984    }985    986    static int copy_link(const char *source, const char *destination,987                         off_t source_size) {988      size_t capacity = source_size > 0 && (uintmax_t)source_size < SIZE_MAX - 1989                            ? (size_t)source_size + 2990                            : 256;991      char *target = malloc(capacity);992      if (target == NULL) {993        fputs("cp: out of memory\n", stderr);994        return 1;995      }996      const ssize_t length = readlink(source, target, capacity - 1);997      if (length < 0 || (size_t)length >= capacity - 1) {998        fprintf(stderr, "cp: %s: %s\n", source,999                length < 0 ? strerror(errno) : "link target is too long");1000        free(target);1001        return 1;1002      }1003      target[length] = '\0';1004      if (unlink(destination) != 0 && errno != ENOENT) {1005        fprintf(stderr, "cp: %s: %s\n", destination, strerror(errno));1006        free(target);1007        return 1;1008      }1009      if (symlink(target, destination) != 0) {1010        fprintf(stderr, "cp: %s: %s\n", destination, strerror(errno));1011        free(target);1012        return 1;1013      }1014      free(target);1015      return 0;1016    }1017    1018    static int copy_directory(const char *source, const char *destination) {1019      if (!recursive) {1020        fprintf(stderr, "cp: %s is a directory (use -R)\n", source);1021        return 1;1022      }1023      struct stat destination_metadata;1024      int created = 0;1025      if (lstat(destination, &destination_metadata) != 0) {1026        if (errno != ENOENT || mkdir(destination, 0777) != 0) {1027          fprintf(stderr, "cp: %s: %s\n", destination, strerror(errno));1028          return 1;1029        }1030        created = 1;1031      } else if (!S_ISDIR(destination_metadata.st_mode)) {1032        fprintf(stderr, "cp: %s is not a directory\n", destination);1033        return 1;1034      }1035    1036      char source_resolved[PATH_MAX];1037      char destination_resolved[PATH_MAX];1038      if (realpath(source, source_resolved) != NULL &&1039          realpath(destination, destination_resolved) != NULL) {1040        const size_t source_length = strlen(source_resolved);1041        const int nested = strcmp(source_resolved, destination_resolved) == 0 ||1042            (strncmp(source_resolved, destination_resolved, source_length) == 0 &&1043             destination_resolved[source_length] == '/');1044        if (nested) {1045          fprintf(stderr, "cp: refusing to copy %s into itself at %s\n",1046                  source, destination);1047          if (created) (void)rmdir(destination);1048          return 1;1049        }1050      }1051    1052      DIR *directory = opendir(source);1053      if (directory == NULL) {1054        fprintf(stderr, "cp: %s: %s\n", source, strerror(errno));1055        return 1;1056      }1057      int status = 0;1058      struct dirent *entry;1059      while ((entry = readdir(directory)) != NULL) {1060        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;1061        char *source_child = join_path(source, entry->d_name);1062        char *destination_child = join_path(destination, entry->d_name);1063        if (source_child == NULL || destination_child == NULL) {1064          fputs("cp: out of memory\n", stderr);1065          status = 1;1066        } else if (copy_path(source_child, destination_child) != 0) {1067          status = 1;1068        }1069        free(source_child);1070        free(destination_child);1071        if (status != 0) break;1072      }1073      if (closedir(directory) != 0 && status == 0) status = 1;1074      return status;1075    }1076    1077    static int copy_path(const char *source, const char *destination) {1078      struct stat metadata;1079      if (lstat(source, &metadata) != 0) {1080        fprintf(stderr, "cp: %s: %s\n", source, strerror(errno));1081        return 1;1082      }1083      struct stat destination_metadata;1084      if (lstat(destination, &destination_metadata) == 0 &&1085          metadata.st_dev == destination_metadata.st_dev &&1086          metadata.st_ino == destination_metadata.st_ino) {1087        fprintf(stderr, "cp: %s and %s are the same file\n", source, destination);1088        return 1;1089      }1090      if (verbose) printf("%s -> %s\n", source, destination);1091      if (S_ISREG(metadata.st_mode)) return copy_regular(source, destination);1092      if (S_ISDIR(metadata.st_mode)) return copy_directory(source, destination);1093      if (S_ISLNK(metadata.st_mode)) return copy_link(source, destination, metadata.st_size);1094      fprintf(stderr, "cp: %s: unsupported file type\n", source);1095      return 1;1096    }1097    1098    static int copy_operand(const char *source, const char *destination,1099                            int destination_is_directory) {1100      char *target = NULL;1101      if (destination_is_directory) {1102        char *base = base_name_copy(source);1103        target = base == NULL ? NULL : join_path(destination, base);1104        free(base);1105        if (target == NULL) {1106          fputs("cp: out of memory\n", stderr);1107          return 1;1108        }1109      }1110      const int status = copy_path(source, target == NULL ? destination : target);1111      free(target);1112      return status;1113    }1114    1115    int main(int argc, char **argv) {1116      int first = 1;1117      for (; first < argc; first++) {1118        const char *argument = argv[first];1119        if (strcmp(argument, "--") == 0) {1120          first++;1121          break;1122        }1123        if (strcmp(argument, "--help") == 0) {1124          fputs("usage: cp [-Rrvf] [--] SOURCE... DESTINATION\n", stdout);1125          return 0;1126        }1127        if (argument[0] != '-' || argument[1] == '\0') break;1128        for (const char *option = argument + 1; *option != '\0'; option++) {1129          if (*option == 'R' || *option == 'r') recursive = 1;1130          else if (*option == 'v') verbose = 1;1131          else if (*option != 'f') {1132            fprintf(stderr, "cp: unsupported option -%c\n", *option);1133            return 2;1134          }1135        }1136      }1137      if (argc - first < 2) {1138        fputs("usage: cp [-Rrvf] [--] SOURCE... DESTINATION\n", stderr);1139        return 2;1140      }1141    1142      const char *destination = argv[argc - 1];1143      struct stat metadata;1144      const int destination_is_directory =1145          stat(destination, &metadata) == 0 && S_ISDIR(metadata.st_mode);1146      if (argc - first > 2 && !destination_is_directory) {1147        fprintf(stderr, "cp: %s is not a directory\n", destination);1148        return 1;1149      }1150      int status = 0;1151      for (int index = first; index < argc - 1; index++) {1152        if (copy_operand(argv[index], destination, destination_is_directory) != 0) {1153          status = 1;1154        }1155      }1156      return status;1157    }1158SLOP cc \1159  /tmp/core-tools/foreground.c \1160  -o /bin/foreground1161SLOP cc \1162  /tmp/core-tools/help.c \1163  -o /bin/help1164SLOP cc \1165  /tmp/core-tools/pwd.c \1166  -o /bin/pwd1167SLOP cc \1168  /tmp/core-tools/cd.c \1169  -o /bin/cd1170SLOP cc \1171  /tmp/core-tools/cat.c \1172  -o /bin/cat1173SLOP cc \1174  /tmp/core-tools/echo.c \1175  -o /bin/echo1176SLOP cc \1177  /tmp/core-tools/touch.c \1178  -o /bin/touch1179SLOP cc \1180  /tmp/core-tools/clear.c \1181  -o /bin/clear1182SLOP cc \1183  /tmp/core-tools/ls.c \1184  -o /bin/ls1185SLOP cc \1186  /tmp/core-tools/stat.c \1187  -o /bin/stat1188SLOP cc \1189  /tmp/core-tools/file.c \1190  -o /bin/file1191SLOP cc \1192  /tmp/core-tools/test.c \1193  -o /bin/test1194SLOP cc \1195  -I /tmp/core-tools \1196  /tmp/core-tools/bracket.c \1197  -o /bin/[1198SLOP cc \1199  /tmp/core-tools/mv.c \1200  -o /bin/mv1201SLOP cc \1202  /tmp/core-tools/cp.c \1203  -o /bin/cp1204EXPORTS TOOL foreground1205EXPORTS TOOL help1206EXPORTS TOOL pwd1207EXPORTS TOOL cd1208EXPORTS TOOL cat1209EXPORTS TOOL echo1210EXPORTS TOOL touch1211EXPORTS TOOL clear1212EXPORTS TOOL ls1213EXPORTS TOOL stat1214EXPORTS TOOL file1215EXPORTS TOOL test1216EXPORTS TOOL [1217EXPORTS TOOL mv1218EXPORTS TOOL cp12191220SLOP rm \1221  -rf \1222  /tmp/core-tools1223