1DOLLY 32MODULE tar34REQUIRES HEADER libc5REQUIRES TOOL   cc6REQUIRES TOOL   rm78# Dolly's deliberately small ustar extractor, embedded directly in the module.9FILE /tmp/bootstrap/tar.c10    #include <errno.h>11    #include <fcntl.h>12    #include <stdint.h>13    #include <stdio.h>14    #include <stdlib.h>15    #include <string.h>16    #include <sys/stat.h>17    #include <unistd.h>18    19    enum { BLOCK_SIZE = 512 };20    21    static int read_exact(int descriptor, void *bytes_value, size_t length) {22      unsigned char *bytes = bytes_value;23      while (length != 0) {24        const ssize_t count = read(descriptor, bytes, length);25        if (count < 0 && errno == EINTR) continue;26        if (count < 0) return -1;27        if (count == 0) {28          errno = EIO;29          return -1;30        }31        bytes += (size_t)count;32        length -= (size_t)count;33      }34      return 0;35    }36    37    static int write_exact(int descriptor, const void *bytes_value, size_t length) {38      const unsigned char *bytes = bytes_value;39      while (length != 0) {40        const ssize_t count = write(descriptor, bytes, length);41        if (count < 0 && errno == EINTR) continue;42        if (count < 0) return -1;43        if (count == 0) { errno = EIO; return -1; }44        bytes += (size_t)count;45        length -= (size_t)count;46      }47      return 0;48    }49    50    static int all_zero(const unsigned char *bytes, size_t length) {51      for (size_t index = 0; index < length; ++index) {52        if (bytes[index] != 0) return 0;53      }54      return 1;55    }56    57    static size_t bounded_length(const unsigned char *bytes, size_t capacity) {58      size_t length = 0;59      while (length < capacity && bytes[length] != '\0') ++length;60      return length;61    }62    63    static int parse_octal(const unsigned char *bytes, size_t length,64                           uint64_t *value_out) {65      uint64_t value = 0;66      size_t index = 0;67      while (index < length && (bytes[index] == ' ' || bytes[index] == '\0')) ++index;68      int digits = 0;69      for (; index < length && bytes[index] != '\0' && bytes[index] != ' '; ++index) {70        if (bytes[index] < '0' || bytes[index] > '7' || value > (UINT64_MAX >> 3)) {71          return -1;72        }73        value = (value << 3) | (uint64_t)(bytes[index] - '0');74        digits = 1;75      }76      if (!digits) return -1;77      *value_out = value;78      return 0;79    }80    81    static int valid_member(const char *path) {82      if (path[0] == '\0' || path[0] == '/' || strlen(path) > 4096 ||83          strstr(path, "//") != NULL || strchr(path, '\\') != NULL) return 0;84      const char *cursor = path;85      while (*cursor != '\0') {86        const char *end = strchr(cursor, '/');87        const size_t length = end == NULL ? strlen(cursor) : (size_t)(end - cursor);88        if ((length == 1 && cursor[0] == '.') ||89            (length == 2 && cursor[0] == '.' && cursor[1] == '.')) return 0;90        if (end == NULL) break;91        cursor = end + 1;92      }93      return 1;94    }95    96    static int mkdir_parents(const char *path, int include_last) {97      char *copy = strdup(path);98      if (copy == NULL) return -1;99      if (!include_last) {100        char *end = strrchr(copy, '/');101        if (end != NULL) *end = '\0';102      }103      for (char *cursor = copy + 1;; ++cursor) {104        if (*cursor != '/' && *cursor != '\0') continue;105        const char saved = *cursor;106        *cursor = '\0';107        struct stat metadata;108        int exists = stat(copy, &metadata) == 0;109        if (exists && !S_ISDIR(metadata.st_mode)) {110          errno = ENOTDIR;111          free(copy);112          return -1;113        }114        if (!exists) {115          /*116           * The version-0 dynamic-command libc surface does not consistently117           * preserve errno across its WasmFS wrapper.  Treat the filesystem as118           * authoritative: after mkdir, verify the path instead of interpreting119           * errno as the result.120           */121          (void)mkdir(copy, 0755);122          if (stat(copy, &metadata) != 0 || !S_ISDIR(metadata.st_mode)) {123            if (errno == 0) errno = EIO;124            free(copy);125            return -1;126          }127        }128        *cursor = saved;129        if (saved == '\0') break;130      }131      free(copy);132      return 0;133    }134    135    static int extract(const char *archive_path, const char *directory) {136      int archive = strcmp(archive_path, "-") == 0 ? dup(STDIN_FILENO) : open(archive_path, O_RDONLY);137      if (archive < 0) return -1;138      unsigned char header[BLOCK_SIZE];139      unsigned char data[BLOCK_SIZE];140      int status = 0;141      const char *stage = "read header";142      char active_member[257] = "<header>";143      for (;;) {144        stage = "read header";145        if (read_exact(archive, header, sizeof(header)) != 0) {146          status = -1;147          break;148        }149        if (all_zero(header, sizeof(header))) break;150        uint64_t declared_checksum;151        uint64_t size;152        stage = "parse header";153        if (parse_octal(header + 148, 8, &declared_checksum) != 0 ||154            parse_octal(header + 124, 12, &size) != 0) {155          errno = EINVAL;156          status = -1;157          break;158        }159        uint64_t checksum = 0;160        for (size_t index = 0; index < sizeof(header); ++index) {161          checksum += index >= 148 && index < 156 ? ' ' : header[index];162        }163        if (checksum != declared_checksum) {164          errno = EBADMSG;165          status = -1;166          break;167        }168        char member[257];169        const size_t name_length = bounded_length(header, 100);170        const size_t prefix_length = bounded_length(header + 345, 155);171        if (prefix_length != 0) {172          if (prefix_length + name_length + 2 > sizeof(member)) {173            errno = ENAMETOOLONG;174            status = -1;175            break;176          }177          memcpy(member, header + 345, prefix_length);178          member[prefix_length] = '/';179          memcpy(member + prefix_length + 1, header, name_length);180          member[prefix_length + name_length + 1] = '\0';181        } else {182          memcpy(member, header, name_length);183          member[name_length] = '\0';184        }185        memcpy(active_member, member, strlen(member) + 1);186        stage = "validate path";187        if (!valid_member(member)) {188          errno = EINVAL;189          status = -1;190          break;191        }192        const size_t output_length = strlen(directory) + strlen(member) + 2;193        char *output = malloc(output_length);194        if (output == NULL) {195          status = -1;196          break;197        }198        snprintf(output, output_length, "%s%s%s", directory,199                 directory[strlen(directory) - 1] == '/' ? "" : "/", member);200        const unsigned char type = header[156];201        int target = -1;202        if (type == '5') {203          stage = "create directory";204          if (size != 0 || mkdir_parents(output, 1) != 0) status = -1;205        } else if (type == '\0' || type == '0') {206          stage = "create parents";207          if (size > SIZE_MAX || mkdir_parents(output, 0) != 0) {208            status = -1;209          } else {210            stage = "open output";211            target = open(output, O_WRONLY | O_CREAT | O_TRUNC, 0666);212          }213          if (target < 0) {214            status = -1;215          }216        } else {217          errno = ENOTSUP;218          status = -1;219        }220        uint64_t remaining = size;221        while (status == 0 && remaining != 0) {222          stage = "read data";223          if (read_exact(archive, data, sizeof(data)) != 0) {224            status = -1;225            break;226          }227          const size_t count = remaining < sizeof(data) ? (size_t)remaining : sizeof(data);228          if (target >= 0 && write_exact(target, data, count) != 0) {229            stage = "write data";230            status = -1;231          }232          remaining -= count;233        }234        if (target >= 0 && close(target) != 0 && status == 0) status = -1;235        free(output);236        if (status != 0) break;237      }238      if (status != 0) {239        fprintf(stderr, "tar: %s at %s (errno %d)\n", stage, active_member, errno);240      }241      if (close(archive) != 0 && status == 0) status = -1;242      return status;243    }244    245    static void usage(FILE *stream) {246      fputs("usage: tar -xf ARCHIVE [-C DIRECTORY]\nUse - as ARCHIVE to read stdin.\n", stream);247    }248    249    int main(int argc, char **argv) {250      if (argc == 2 && strcmp(argv[1], "--help") == 0) {251        usage(stdout);252        return 0;253      }254      if ((argc != 3 && argc != 5) || strcmp(argv[1], "-xf") != 0 ||255          (argc == 5 && strcmp(argv[3], "-C") != 0)) {256        usage(stderr);257        return 2;258      }259      const char *directory = argc == 5 ? argv[4] : ".";260      struct stat metadata;261      if (stat(directory, &metadata) != 0 || !S_ISDIR(metadata.st_mode)) {262        fprintf(stderr, "tar: %s: %s\n", directory, strerror(errno));263        return 1;264      }265      if (extract(argv[2], directory) != 0) {266        fprintf(stderr, "tar: %s: %s\n", argv[2], strerror(errno));267        return 1;268      }269      return 0;270    }271SLOP cc \272  -std=c17 \273  -D_DEFAULT_SOURCE \274  /tmp/bootstrap/tar.c \275  -o /bin/tar276277EXPORTS TOOL tar278279SLOP tar \280  --help281282SLOP rm \283  -rf \284  /tmp/bootstrap285