1DOLLY 32MODULE tar34REQUIRES HEADER libc5REQUIRES TOOL cc6REQUIRES TOOL rm789FILE /tmp/bootstrap/tar.c101112131415161718 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