1DOLLY 32MODULE core-tools34REQUIRES HEADER libc5REQUIRES HEADER runtime6REQUIRES TOOL cc7REQUIRES TOOL rm891011FILE /tmp/core-tools/foreground.c1213141516 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.c4748495051 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.c7980818283 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.c104105106107108109 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.c130131132133 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.c214215216 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.c239240241242243 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.c294295296 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.c311312 313314315316317318319320321 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.c494495 496497498499500501 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.c560561 562563564565566567 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.c618619 620621622623624625 626627628629 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.c786787788FILE /tmp/core-tools/mv.c789790 791792793794795796797 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.c883884885 886887888889890891892893894895896 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