cpio.c (39265B)
1 #include <kit/compress.h> 2 #include <kit/core.h> 3 #include <stddef.h> 4 #include <stdint.h> 5 #include <string.h> 6 7 #include "driver.h" 8 #include "env.h" 9 10 /* `kit cpio` — create / list / extract SVR4 "newc" cpio archives, the format 11 * the Linux kernel unpacks as its initramfs (magic 070701, or 070702 for the 12 * CRC variant). This is an archive packager in the byte-utility family, a 13 * sibling of `ar`; it does not boot, mount, or interpret the archive. 14 * 15 * The newc framing is plain container bookkeeping (110-byte ASCII-hex headers, 16 * 4-byte-aligned name and data, a closing TRAILER!!! record, a 512-byte tail 17 * pad), so it lives here driver-local rather than in libkit — only this tool 18 * consumes it. Compression rides on the public kit/compress.h codecs. 19 * 20 * Determinism: members are sorted by archived path; metadata is normalized 21 * (mode = type | perms with perms keyed on the source executable bit, uid/gid 22 * 0, mtime 0, sequential inode). Identical inputs yield byte-identical output. 23 */ 24 25 #define CPIO_TOOL "cpio" 26 27 #define CPIO_HDR_LEN 110u 28 #define CPIO_MAGIC_NEWC "070701" 29 #define CPIO_MAGIC_CRC "070702" 30 31 /* mode type bits (the octal S_IF* values, target-independent). */ 32 #define CPIO_S_IFMT 0170000u 33 #define CPIO_S_IFREG 0100000u 34 #define CPIO_S_IFDIR 0040000u 35 #define CPIO_S_IFLNK 0120000u 36 37 /* Largest symlink target / read buffer for an extracted link. */ 38 #define CPIO_LINK_MAX 4096u 39 40 /* --------------------------------------------------------------------------- 41 * Low-level field codec 42 * ------------------------------------------------------------------------- */ 43 44 static void cpio_put_hex8(uint8_t* p, uint32_t v) { 45 static const char hx[] = "0123456789ABCDEF"; 46 int i; 47 for (i = 7; i >= 0; --i) { 48 p[i] = (uint8_t)hx[v & 0xFu]; 49 v >>= 4; 50 } 51 } 52 53 static int cpio_get_hex8(const uint8_t* p, uint32_t* out) { 54 uint32_t v = 0; 55 int i; 56 for (i = 0; i < 8; ++i) { 57 int d = driver_hex_nibble((char)p[i]); 58 if (d < 0) return 1; 59 v = (v << 4) | (uint32_t)d; 60 } 61 *out = v; 62 return 0; 63 } 64 65 static size_t cpio_round4(size_t n) { return (n + 3u) & ~(size_t)3u; } 66 67 static uint8_t cpio_ft_from_mode(uint32_t mode) { 68 switch (mode & CPIO_S_IFMT) { 69 case CPIO_S_IFDIR: 70 return 3; 71 case CPIO_S_IFLNK: 72 return 7; 73 case CPIO_S_IFREG: 74 return 4; 75 default: 76 return 0; 77 } 78 } 79 80 /* --------------------------------------------------------------------------- 81 * Writer side: stream newc records to a KitWriter, tracking the running 82 * archive offset so both 4-byte record alignment and the final 512-byte pad 83 * are measured from the start of the archive. 84 * ------------------------------------------------------------------------- */ 85 86 typedef struct CpioOut { 87 KitWriter* w; 88 uint64_t total; /* bytes written so far */ 89 uint32_t next_ino; 90 int crc; /* emit 070702 with a data-byte-sum checksum */ 91 int err; 92 } CpioOut; 93 94 static int cpio_emit(CpioOut* o, const void* p, size_t n) { 95 if (o->err) return 1; 96 if (n && kit_writer_write(o->w, p, n) != KIT_OK) { 97 o->err = 1; 98 return 1; 99 } 100 o->total += n; 101 return 0; 102 } 103 104 /* Pad the archive up to the next 4-byte boundary. Record starts are always 105 * 4-aligned, so this lands data (after the name) and the next header (after 106 * data) on 4-byte boundaries as newc requires. */ 107 static int cpio_pad4(CpioOut* o) { 108 static const uint8_t z[4] = {0, 0, 0, 0}; 109 size_t pad = (size_t)((4u - (o->total & 3u)) & 3u); 110 return pad ? cpio_emit(o, z, pad) : 0; 111 } 112 113 static int cpio_emit_record(CpioOut* o, const char* name, size_t namelen, 114 uint32_t mode, uint32_t nlink, uint32_t ino, 115 const uint8_t* data, uint32_t size) { 116 uint8_t hdr[CPIO_HDR_LEN]; 117 uint32_t namesize = (uint32_t)namelen + 1u; /* includes the trailing NUL */ 118 uint32_t check = 0; 119 static const uint8_t nul = 0; 120 121 if (o->crc && data) { 122 uint32_t i; 123 for (i = 0; i < size; ++i) check += data[i]; 124 } 125 126 memcpy(hdr, o->crc ? CPIO_MAGIC_CRC : CPIO_MAGIC_NEWC, 6); 127 cpio_put_hex8(hdr + 6, ino); 128 cpio_put_hex8(hdr + 14, mode); 129 cpio_put_hex8(hdr + 22, 0u); /* uid */ 130 cpio_put_hex8(hdr + 30, 0u); /* gid */ 131 cpio_put_hex8(hdr + 38, nlink); 132 cpio_put_hex8(hdr + 46, 0u); /* mtime */ 133 cpio_put_hex8(hdr + 54, size); 134 cpio_put_hex8(hdr + 62, 0u); /* devmajor */ 135 cpio_put_hex8(hdr + 70, 0u); /* devminor */ 136 cpio_put_hex8(hdr + 78, 0u); /* rdevmajor */ 137 cpio_put_hex8(hdr + 86, 0u); /* rdevminor */ 138 cpio_put_hex8(hdr + 94, namesize); 139 cpio_put_hex8(hdr + 102, check); 140 141 if (cpio_emit(o, hdr, CPIO_HDR_LEN)) return 1; 142 if (cpio_emit(o, name, namelen)) return 1; 143 if (cpio_emit(o, &nul, 1)) return 1; 144 if (cpio_pad4(o)) return 1; 145 if (size && cpio_emit(o, data, size)) return 1; 146 if (cpio_pad4(o)) return 1; 147 return 0; 148 } 149 150 /* Append one member, assigning the next sequential inode. */ 151 static int cpio_append(CpioOut* o, const char* name, size_t namelen, 152 uint32_t mode, uint32_t nlink, const uint8_t* data, 153 uint32_t size) { 154 if (cpio_emit_record(o, name, namelen, mode, nlink, o->next_ino, data, size)) 155 return 1; 156 o->next_ino++; 157 return 0; 158 } 159 160 /* Write the TRAILER!!! record (inode 0, nlink 1, no data) then pad the whole 161 * archive to a 512-byte boundary so concatenated segments stay aligned. */ 162 static int cpio_finish(CpioOut* o) { 163 if (cpio_emit_record(o, "TRAILER!!!", 10u, 0u, 1u, 0u, NULL, 0u)) return 1; 164 { 165 static const uint8_t z[64] = {0}; 166 size_t pad = (size_t)((512u - (o->total & 511u)) & 511u); 167 while (pad) { 168 size_t n = pad > sizeof z ? sizeof z : pad; 169 if (cpio_emit(o, z, n)) return 1; 170 pad -= n; 171 } 172 } 173 return 0; 174 } 175 176 /* --------------------------------------------------------------------------- 177 * Reader side: parse newc records (handling concatenated segments) and hand 178 * each non-trailer entry to a visitor. 179 * ------------------------------------------------------------------------- */ 180 181 typedef struct CpioEntry { 182 const char* name; /* NUL-terminated, aliases the input buffer */ 183 uint32_t name_len; 184 uint32_t mode; 185 uint8_t filetype; /* derived from mode */ 186 const uint8_t* data; 187 uint32_t size; 188 } CpioEntry; 189 190 /* Returns nonzero to stop the walk with an error. */ 191 typedef int (*CpioVisit)(void* user, const CpioEntry* e); 192 193 static int cpio_is_magic(const uint8_t* p) { 194 return memcmp(p, CPIO_MAGIC_NEWC, 6) == 0 || memcmp(p, CPIO_MAGIC_CRC, 6) == 0; 195 } 196 197 static int cpio_parse(const uint8_t* data, size_t len, CpioVisit fn, 198 void* user) { 199 size_t off = 0; 200 int saw_trailer = 0; 201 202 while (off + CPIO_HDR_LEN <= len) { 203 const uint8_t* h = data + off; 204 uint32_t fields[13]; 205 uint32_t mode, filesize, namesize, check; 206 size_t name_off, data_off; 207 const char* name; 208 size_t i; 209 210 if (!cpio_is_magic(h)) { 211 driver_errf(CPIO_TOOL, 212 off == 0 ? "not a cpio newc archive (bad magic)" 213 : "corrupt cpio header (bad magic mid-stream)"); 214 return 1; 215 } 216 for (i = 0; i < 13; ++i) 217 if (cpio_get_hex8(h + 6u + i * 8u, &fields[i])) { 218 driver_errf(CPIO_TOOL, "corrupt cpio header (non-hex field)"); 219 return 1; 220 } 221 mode = fields[1]; 222 filesize = fields[6]; 223 namesize = fields[11]; 224 check = fields[12]; 225 if (namesize == 0) { 226 driver_errf(CPIO_TOOL, "corrupt cpio header (zero name size)"); 227 return 1; 228 } 229 name_off = off + CPIO_HDR_LEN; 230 if ((size_t)namesize > len - name_off) { 231 driver_errf(CPIO_TOOL, "truncated cpio archive (name runs past end)"); 232 return 1; 233 } 234 name = (const char*)(data + name_off); 235 if (name[namesize - 1u] != '\0') { 236 driver_errf(CPIO_TOOL, "corrupt cpio header (name not NUL-terminated)"); 237 return 1; 238 } 239 if (name_off + (size_t)namesize > SIZE_MAX - 3u) { 240 driver_errf(CPIO_TOOL, "corrupt cpio header (name size overflow)"); 241 return 1; 242 } 243 data_off = cpio_round4(name_off + namesize); 244 if (data_off > len) { 245 driver_errf(CPIO_TOOL, "truncated cpio archive (name padding missing)"); 246 return 1; 247 } 248 249 if (namesize == 11u && memcmp(name, "TRAILER!!!", 11) == 0) { 250 if (filesize != 0) { 251 driver_errf(CPIO_TOOL, "corrupt cpio TRAILER!!! record"); 252 return 1; 253 } 254 saw_trailer = 1; 255 off = data_off; /* filesize is 0 for the trailer */ 256 /* Skip inter-segment zero padding; a following non-zero run that is a 257 * cpio magic starts a concatenated archive, otherwise it is trailing 258 * data we do not parse (e.g. an appended compressed image). */ 259 while (off < len && data[off] == 0) ++off; 260 if (off >= len) break; 261 if (off + 6u > len || !cpio_is_magic(data + off)) { 262 driver_errf(CPIO_TOOL, 263 "corrupt cpio archive: %lu trailing byte(s) after TRAILER", 264 (unsigned long)(len - off)); 265 return 1; 266 } 267 saw_trailer = 0; /* the concatenated segment needs its own trailer */ 268 continue; 269 } 270 271 if ((size_t)filesize > len - data_off) { 272 driver_errf(CPIO_TOOL, "truncated cpio archive (data runs past end)"); 273 return 1; 274 } 275 if (memcmp(h, CPIO_MAGIC_CRC, 6) == 0) { 276 uint32_t actual = 0; 277 for (i = 0; i < filesize; ++i) actual += data[data_off + i]; 278 if (actual != check) { 279 driver_errf(CPIO_TOOL, "corrupt cpio checksum for: %s", name); 280 return 1; 281 } 282 } 283 { 284 CpioEntry e; 285 e.name = name; 286 e.name_len = namesize - 1u; 287 e.mode = mode; 288 e.filetype = cpio_ft_from_mode(mode); 289 e.data = data + data_off; 290 e.size = filesize; 291 if (fn && fn(user, &e)) return 1; 292 } 293 if (data_off + (size_t)filesize > SIZE_MAX - 3u) { 294 driver_errf(CPIO_TOOL, "corrupt cpio header (data size overflow)"); 295 return 1; 296 } 297 off = cpio_round4(data_off + filesize); 298 } 299 300 if (!saw_trailer) { 301 if (off < len) 302 driver_errf(CPIO_TOOL, "truncated cpio archive (incomplete header)"); 303 else 304 driver_errf(CPIO_TOOL, "corrupt cpio archive: missing TRAILER!!! record"); 305 return 1; 306 } 307 return 0; 308 } 309 310 /* --------------------------------------------------------------------------- 311 * Member collection for `-o` create. 312 * ------------------------------------------------------------------------- */ 313 314 typedef struct CpioMember { 315 char* name; /* archived path (cleaned), heap-owned */ 316 size_t name_alloc; 317 uint32_t name_len; 318 char* src; /* host path to read, heap-owned, or NULL */ 319 size_t src_alloc; 320 uint8_t filetype; /* 3 dir, 4 regular, 7 symlink */ 321 int executable; 322 uint64_t size; 323 } CpioMember; 324 325 typedef struct CpioBuild { 326 DriverEnv* env; 327 CpioMember* items; 328 size_t count; 329 size_t cap; 330 } CpioBuild; 331 332 static char* cpio_strdup(DriverEnv* env, const char* s, size_t* out_alloc) { 333 size_t n = driver_strlen(s) + 1u; 334 char* p = (char*)driver_alloc(env, n); 335 if (p) memcpy(p, s, n); 336 if (out_alloc) *out_alloc = n; 337 return p; 338 } 339 340 static int cpio_build_grow(CpioBuild* b) { 341 size_t nc = b->cap ? b->cap * 2u : 16u; 342 CpioMember* nv = 343 (CpioMember*)driver_alloc(b->env, nc * sizeof(CpioMember)); 344 if (!nv) return 1; 345 if (b->items) { 346 memcpy(nv, b->items, b->count * sizeof(CpioMember)); 347 driver_free(b->env, b->items, b->cap * sizeof(CpioMember)); 348 } 349 b->items = nv; 350 b->cap = nc; 351 return 0; 352 } 353 354 static int cpio_build_add(CpioBuild* b, const char* arch, const char* src, 355 uint8_t ft, int exe, uint64_t size) { 356 CpioMember* m; 357 if (b->count >= b->cap && cpio_build_grow(b)) return 1; 358 m = &b->items[b->count]; 359 memset(m, 0, sizeof *m); 360 m->name = cpio_strdup(b->env, arch, &m->name_alloc); 361 if (!m->name) return 1; 362 m->name_len = (uint32_t)(m->name_alloc - 1u); 363 if (src) { 364 m->src = cpio_strdup(b->env, src, &m->src_alloc); 365 if (!m->src) return 1; 366 } 367 m->filetype = ft; 368 m->executable = exe; 369 m->size = size; 370 b->count++; 371 return 0; 372 } 373 374 static void cpio_build_free(CpioBuild* b) { 375 size_t i; 376 for (i = 0; i < b->count; ++i) { 377 if (b->items[i].name) driver_free(b->env, b->items[i].name, 378 b->items[i].name_alloc); 379 if (b->items[i].src) driver_free(b->env, b->items[i].src, 380 b->items[i].src_alloc); 381 } 382 if (b->items) driver_free(b->env, b->items, b->cap * sizeof(CpioMember)); 383 b->items = NULL; 384 b->count = b->cap = 0; 385 } 386 387 /* Walk one source path (`src`) recording it (and, for directories, its 388 * contents) under the archived name `arch`. An empty `arch` means "emit the 389 * contents only" (used for a "." operand), so no entry is written for the 390 * root itself. Symlinks are recorded, never followed. */ 391 static int cpio_collect(CpioBuild* b, const char* src, const char* arch) { 392 DriverEnv* env = b->env; 393 uint64_t size = 0; 394 uint8_t ft = 0; 395 int exe = 0; 396 int rc = driver_path_lstat(src, &size, &ft, &exe); 397 if (rc != 0) { 398 driver_errf(CPIO_TOOL, "cannot stat: %s", src); 399 return 1; 400 } 401 402 if (ft == 4) return cpio_build_add(b, arch, src, 4, exe, size); 403 if (ft == 7) return cpio_build_add(b, arch, src, 7, 0, 0); 404 if (ft != 3) { 405 driver_errf(CPIO_TOOL, "skipping unsupported file type: %s", src); 406 return 0; /* not fatal — special/device nodes are out of scope */ 407 } 408 409 /* Directory: emit its own entry (unless this is the contents-only root), 410 * then recurse over a snapshot of its children. */ 411 if (arch[0] != '\0' && cpio_build_add(b, arch, NULL, 3, 0, 0)) return 1; 412 { 413 DriverDirHandle* h = driver_open_dir(env, src); 414 uint64_t i; 415 if (!h) { 416 driver_errf(CPIO_TOOL, "cannot read directory: %s", src); 417 return 1; 418 } 419 for (i = 0;; ++i) { 420 const char* nm; 421 uint32_t nl; 422 uint64_t ino, sz, mt; 423 uint8_t cft; 424 char* csrc; 425 char* carch; 426 size_t csrc_sz = 0, carch_sz = 0; 427 int crc; 428 if (driver_read_dir_entry(h, i, &nm, &nl, &ino, &sz, &mt, &cft) != 0) 429 break; 430 csrc = driver_path_join(env, src, nm, &csrc_sz); 431 carch = driver_path_join(env, arch, nm, &carch_sz); 432 if (!csrc || !carch) { 433 if (csrc) driver_free(env, csrc, csrc_sz); 434 if (carch) driver_free(env, carch, carch_sz); 435 driver_close_dir(env, h); 436 return 1; 437 } 438 crc = cpio_collect(b, csrc, carch); 439 driver_free(env, csrc, csrc_sz); 440 driver_free(env, carch, carch_sz); 441 if (crc) { 442 driver_close_dir(env, h); 443 return 1; 444 } 445 } 446 driver_close_dir(env, h); 447 } 448 return 0; 449 } 450 451 static int cpio_name_cmp(const CpioMember* a, const CpioMember* b) { 452 const unsigned char* x = (const unsigned char*)a->name; 453 const unsigned char* y = (const unsigned char*)b->name; 454 size_t i = 0; 455 while (x[i] && y[i]) { 456 if (x[i] != y[i]) return (int)x[i] - (int)y[i]; 457 ++i; 458 } 459 return (int)x[i] - (int)y[i]; 460 } 461 462 /* Bottom-up merge sort by archived name. Stable and O(n log n); a plain 463 * lexicographic order places every directory before its descendants (a 464 * parent name is a strict prefix of "parent/child"), so the emitted stream 465 * is a valid sorted DFS. */ 466 static int cpio_sort(CpioBuild* b) { 467 size_t n = b->count, width; 468 CpioMember* src = b->items; 469 CpioMember* tmp; 470 if (n < 2) return 0; 471 tmp = (CpioMember*)driver_alloc(b->env, n * sizeof(CpioMember)); 472 if (!tmp) return 1; 473 for (width = 1; width < n; width *= 2) { 474 size_t i; 475 for (i = 0; i < n; i += 2 * width) { 476 size_t l = i; 477 size_t mid = i + width < n ? i + width : n; 478 size_t r = i + 2 * width < n ? i + 2 * width : n; 479 size_t a = l, c = mid, k = l; 480 while (a < mid && c < r) 481 tmp[k++] = cpio_name_cmp(&src[a], &src[c]) <= 0 ? src[a++] : src[c++]; 482 while (a < mid) tmp[k++] = src[a++]; 483 while (c < r) tmp[k++] = src[c++]; 484 } 485 { 486 CpioMember* t = src; 487 src = tmp; 488 tmp = t; 489 } 490 } 491 if (src != b->items) { 492 memcpy(b->items, src, n * sizeof(CpioMember)); 493 tmp = src; 494 } 495 driver_free(b->env, tmp, n * sizeof(CpioMember)); 496 return 0; 497 } 498 499 /* Strip a leading "./" run and any leading '/'. Returns the cleaned pointer 500 * into the original string and reports whether an absolute prefix was 501 * dropped. A trailing '/' is handled separately by the caller. */ 502 static const char* cpio_clean_name(const char* raw, int* stripped_abs) { 503 *stripped_abs = 0; 504 while (raw[0] == '.' && raw[1] == '/') raw += 2; 505 while (raw[0] == '/') { 506 raw += 1; 507 *stripped_abs = 1; 508 } 509 return raw; 510 } 511 512 /* Whether `n` contains a ".." path component. Used to refuse archiving an 513 * operand whose name would escape the archive root (and which our own extract 514 * would then reject). */ 515 static int cpio_name_has_dotdot(const char* n) { 516 size_t i = 0, comp = 0; 517 for (;; ++i) { 518 char c = n[i]; 519 if (c == '/' || c == '\0') { 520 if (i - comp == 2u && n[comp] == '.' && n[comp + 1u] == '.') return 1; 521 comp = i + 1u; 522 if (c == '\0') return 0; 523 } 524 } 525 } 526 527 /* --------------------------------------------------------------------------- 528 * Options + dispatch 529 * ------------------------------------------------------------------------- */ 530 531 typedef struct CpioOpts { 532 int mode; /* 'o' create, 't' list, 'i' extract */ 533 const char* file; /* -F archive path, or NULL for stdin/stdout */ 534 int crc; /* emit/accept 070702 */ 535 int verbose; /* -v */ 536 int compress; /* create: compress the output */ 537 KitCompressFormat cfmt; 538 int decompress; /* -d (read); auto-detect is always on regardless */ 539 } CpioOpts; 540 541 static int cpio_parse_format(const char* s, int* crc) { 542 if (driver_streq(s, "newc")) { 543 *crc = 0; 544 return 0; 545 } 546 if (driver_streq(s, "crc") || driver_streq(s, "newcrc") || 547 driver_streq(s, "sv4crc")) { 548 *crc = 1; 549 return 0; 550 } 551 return 1; 552 } 553 554 static int cpio_parse_compress(const char* s, KitCompressFormat* fmt) { 555 if (driver_streq(s, "gzip") || driver_streq(s, "gz")) { 556 *fmt = KIT_COMPRESS_GZIP; 557 return 0; 558 } 559 if (driver_streq(s, "lz4")) { 560 *fmt = KIT_COMPRESS_LZ4_FRAME; 561 return 0; 562 } 563 return 1; /* zstd/xz/unknown */ 564 } 565 566 void driver_help_cpio(void) { 567 driver_printf( 568 "%.*s", 569 KIT_SLICE_ARG(KIT_SLICE_LIT( 570 "kit cpio — create / list / extract SVR4 newc cpio archives\n" 571 "\n" 572 "USAGE\n" 573 " kit cpio -o [-F FILE] [-H newc|crc] [-z|--lz4] [-v] PATH...\n" 574 " kit cpio -t [-F FILE] [-v]\n" 575 " kit cpio -i [-F FILE] [-v]\n" 576 "\n" 577 "DESCRIPTION\n" 578 " Packages the SVR4 \"newc\" cpio format (magic 070701, or 070702\n" 579 " with -H crc) the Linux kernel unpacks as initramfs. Create reads\n" 580 " the given files and directories (recursed); list and extract read\n" 581 " the archive from -F FILE or stdin. Regular files, directories,\n" 582 " and symlinks are supported; special/device nodes are not.\n" 583 "\n" 584 " Output is deterministic: members are sorted by path and metadata\n" 585 " is normalized (uid/gid 0, mtime 0, mode by type and exec bit).\n" 586 "\n" 587 "MODES\n" 588 " -o, --create create an archive from PATH operands\n" 589 " -t, --list list the members of an archive\n" 590 " -i, --extract extract the members of an archive\n" 591 "\n" 592 "OPTIONS\n" 593 " -F, --file FILE archive file (default: stdout on -o, stdin " 594 "else)\n" 595 " -H, --format FMT newc (default) | crc (070702 checksum variant)\n" 596 " -z gzip-compress the created archive\n" 597 " --lz4 LZ4-frame-compress the created archive\n" 598 " --compress=C compress with C = gzip | lz4\n" 599 " -d decompress on read (auto-detected regardless)\n" 600 " -v, --verbose list/announce each member\n" 601 " -h, --help show this help\n" 602 "\n" 603 " Compression is gzip or lz4 only; zstd/xz are rejected. On read a\n" 604 " gzip/lz4 archive is decompressed automatically. Concatenated\n" 605 " archives (early-init segments) are accepted on list/extract.\n" 606 "\n" 607 "OPERANDS AND EXTRACTION\n" 608 " Create mode takes PATH operands; list/extract take no member\n" 609 " operands. Extraction writes below the current working directory,\n" 610 " so change to the intended destination first. Use -- before a\n" 611 " create operand beginning with `-`.\n" 612 "\n" 613 "EXAMPLES\n" 614 " kit cpio -o -F root.cpio root/\n" 615 " kit cpio -t -F root.cpio\n" 616 " mkdir unpacked\n" 617 " (cd unpacked && kit cpio -i -F ../root.cpio)\n" 618 " kit cpio -o root/ | kit cpio -t\n" 619 " kit cpio -o -z root/ > root.cpio.gz\n" 620 " kit cpio -t < root.cpio.gz # gzip auto-detected\n" 621 " kit cpio -o --lz4 root/ > root.cpio.lz4\n" 622 " kit cpio -i < root.cpio.lz4 # extract into cwd\n" 623 "\n" 624 "EXIT CODES\n" 625 " 0 success 1 I/O or format error 2 bad usage\n"))); 626 } 627 628 /* ---- list ---- */ 629 630 typedef struct CpioListCtx { 631 int verbose; 632 } CpioListCtx; 633 634 static int cpio_list_visit(void* user, const CpioEntry* e) { 635 CpioListCtx* c = (CpioListCtx*)user; 636 if (!c->verbose) { 637 driver_printf("%.*s\n", (int)e->name_len, e->name); 638 return 0; 639 } 640 if (e->filetype == 7) { 641 driver_printf("%06o %8u %.*s -> %.*s\n", (unsigned)(e->mode & 07777u), 642 (unsigned)e->size, (int)e->name_len, e->name, (int)e->size, 643 (const char*)e->data); 644 } else { 645 driver_printf("%06o %8u %.*s\n", (unsigned)(e->mode & 07777u), 646 (unsigned)e->size, (int)e->name_len, e->name); 647 } 648 return 0; 649 } 650 651 /* ---- extract ---- */ 652 653 typedef struct CpioExtractCtx { 654 DriverEnv* env; 655 KitContext* ctx; 656 int verbose; 657 } CpioExtractCtx; 658 659 /* Reject absolute paths, ".." components, embedded NUL, and empty names so a 660 * crafted archive cannot escape the destination directory. */ 661 static int cpio_name_safe(const char* n, uint32_t len) { 662 uint32_t i, comp = 0; 663 if (len == 0 || n[0] == '/') return 0; 664 for (i = 0; i <= len; ++i) { 665 char ch = (i < len) ? n[i] : '/'; 666 if (i < len && ch == '\0') return 0; 667 if (ch == '/') { 668 uint32_t cl = i - comp; 669 if (cl == 2u && n[comp] == '.' && n[comp + 1u] == '.') return 0; 670 comp = i + 1u; 671 } 672 } 673 return 1; 674 } 675 676 /* mkdir -p the parent directory of `name` (which has no trailing slash). */ 677 static int cpio_make_parents(DriverEnv* env, const char* name) { 678 size_t len = driver_strlen(name); 679 size_t i = len; 680 char* p; 681 int rc; 682 while (i > 0 && name[i - 1u] != '/') --i; 683 if (i <= 1u) return 0; /* no parent component */ 684 p = (char*)driver_alloc(env, i); /* i-1 chars + NUL */ 685 if (!p) return 1; 686 memcpy(p, name, i - 1u); 687 p[i - 1u] = '\0'; 688 rc = driver_mkdir_p(env, p); 689 driver_free(env, p, i); 690 return rc; 691 } 692 693 static int cpio_extract_visit(void* user, const CpioEntry* e) { 694 CpioExtractCtx* c = (CpioExtractCtx*)user; 695 DriverEnv* env = c->env; 696 const char* name = e->name; 697 698 if (!cpio_name_safe(name, e->name_len)) { 699 driver_errf(CPIO_TOOL, "refusing unsafe member name: %.*s", 700 (int)e->name_len, name); 701 return 1; 702 } 703 704 if (e->filetype == 3) { 705 if (driver_mkdir_p(env, name)) { 706 driver_errf(CPIO_TOOL, "cannot create directory: %s", name); 707 return 1; 708 } 709 } else if (e->filetype == 4) { 710 KitWriter* w = NULL; 711 if (cpio_make_parents(env, name)) { 712 driver_errf(CPIO_TOOL, "cannot create parent of: %s", name); 713 return 1; 714 } 715 if (c->ctx->file_io->open_writer(c->ctx->file_io->user, name, &w) != 716 KIT_OK) { 717 driver_errf(CPIO_TOOL, "cannot create file: %s", name); 718 return 1; 719 } 720 if (e->size) (void)kit_writer_write(w, e->data, e->size); 721 if (kit_writer_status(w) != KIT_OK) { 722 driver_writer_abort(w); 723 kit_writer_close(w); 724 driver_errf(CPIO_TOOL, "write failed: %s", name); 725 return 1; 726 } 727 kit_writer_close(w); 728 if (e->mode & 0111u) (void)driver_mark_executable_output(name); 729 } else if (e->filetype == 7) { 730 char tgt[CPIO_LINK_MAX]; 731 if (e->size >= sizeof tgt) { 732 driver_errf(CPIO_TOOL, "symlink target too long: %s", name); 733 return 1; 734 } 735 if (cpio_make_parents(env, name)) { 736 driver_errf(CPIO_TOOL, "cannot create parent of: %s", name); 737 return 1; 738 } 739 memcpy(tgt, e->data, e->size); 740 tgt[e->size] = '\0'; 741 (void)driver_remove_file(name); /* replace any stale entry */ 742 if (driver_create_symlink(tgt, name) != 0) { 743 driver_errf(CPIO_TOOL, "cannot create symlink: %s", name); 744 return 1; 745 } 746 } else { 747 driver_errf(CPIO_TOOL, "unsupported member type: %.*s", 748 (int)e->name_len, name); 749 return 1; 750 } 751 752 if (c->verbose) driver_printf("%.*s\n", (int)e->name_len, name); 753 return 0; 754 } 755 756 /* Semantic validation runs over the complete archive before extraction starts, 757 * so an invalid later member cannot leave a partially trusted tree behind. */ 758 static int cpio_extract_validate(void* user, const CpioEntry* e) { 759 size_t i; 760 (void)user; 761 if (!cpio_name_safe(e->name, e->name_len)) { 762 driver_errf(CPIO_TOOL, "refusing unsafe member name: %.*s", 763 (int)e->name_len, e->name); 764 return 1; 765 } 766 if (e->filetype != 3 && e->filetype != 4 && e->filetype != 7) { 767 driver_errf(CPIO_TOOL, "unsupported member type: %.*s", 768 (int)e->name_len, e->name); 769 return 1; 770 } 771 if (e->filetype == 7) { 772 if (e->size >= CPIO_LINK_MAX) { 773 driver_errf(CPIO_TOOL, "symlink target too long: %s", e->name); 774 return 1; 775 } 776 for (i = 0; i < e->size; ++i) 777 if (e->data[i] == 0) { 778 driver_errf(CPIO_TOOL, "symlink target contains NUL: %s", e->name); 779 return 1; 780 } 781 } 782 return 0; 783 } 784 785 /* --------------------------------------------------------------------------- 786 * Create 787 * ------------------------------------------------------------------------- */ 788 789 static int cpio_write_members(KitContext* ctx, CpioBuild* b, CpioOut* o) { 790 size_t i; 791 for (i = 0; i < b->count; ++i) { 792 CpioMember* m = &b->items[i]; 793 if (m->filetype == 3) { 794 if (cpio_append(o, m->name, m->name_len, CPIO_S_IFDIR | 0755u, 2u, NULL, 795 0u)) 796 return 1; 797 } else if (m->filetype == 7) { 798 char tgt[CPIO_LINK_MAX]; 799 if (driver_readlink(m->src, tgt, sizeof tgt) != 0) { 800 driver_errf(CPIO_TOOL, "cannot read symlink target: %s", m->src); 801 return 1; 802 } 803 if (cpio_append(o, m->name, m->name_len, CPIO_S_IFLNK | 0777u, 1u, 804 (const uint8_t*)tgt, (uint32_t)driver_strlen(tgt))) 805 return 1; 806 } else { /* regular file */ 807 KitFileData fd = {0}; 808 uint32_t mode = CPIO_S_IFREG | (m->executable ? 0755u : 0644u); 809 int wrc; 810 if (ctx->file_io->read_all(ctx->file_io->user, m->src, &fd) != KIT_OK) { 811 driver_errf(CPIO_TOOL, "cannot read file: %s", m->src); 812 return 1; 813 } 814 if (fd.size > 0xFFFFFFFFu) { 815 ctx->file_io->release(ctx->file_io->user, &fd); 816 driver_errf(CPIO_TOOL, "file too large for newc (>= 4 GiB): %s", 817 m->src); 818 return 1; 819 } 820 wrc = cpio_append(o, m->name, m->name_len, mode, 1u, fd.data, 821 (uint32_t)fd.size); 822 ctx->file_io->release(ctx->file_io->user, &fd); 823 if (wrc) return 1; 824 } 825 } 826 return cpio_finish(o); 827 } 828 829 static int cpio_create(DriverEnv* env, KitContext* ctx, const CpioOpts* opt, 830 const char** ops, size_t nops) { 831 CpioBuild b; 832 CpioOut co; 833 KitWriter* target = NULL; /* where records are written */ 834 KitWriter* mem = NULL; /* buffer when compressing */ 835 KitWriter* out = NULL; /* final output (file/stdout) */ 836 int owned_out = 0; 837 int rc = 1; 838 size_t i; 839 840 memset(&b, 0, sizeof b); 841 b.env = env; 842 843 for (i = 0; i < nops; ++i) { 844 int abs_stripped = 0; 845 const char* cleaned = cpio_clean_name(ops[i], &abs_stripped); 846 char* owned = NULL; 847 size_t owned_sz = 0; 848 size_t cl; 849 if (abs_stripped) { 850 driver_errf(CPIO_TOOL, 851 "warning: storing %s as relative (leading '/' stripped)", 852 ops[i]); 853 } 854 /* Drop a trailing slash so a directory operand archives as "dir" not 855 * "dir/". An empty cleaned name ("." / "/" / "./") means contents-only. */ 856 cl = driver_strlen(cleaned); 857 while (cl > 0 && cleaned[cl - 1u] == '/') --cl; 858 owned = (char*)driver_alloc(env, cl + 1u); 859 if (!owned) goto done; 860 owned_sz = cl + 1u; 861 memcpy(owned, cleaned, cl); 862 owned[cl] = '\0'; 863 if (cl > 0 && cpio_name_has_dotdot(owned)) { 864 driver_errf(CPIO_TOOL, 865 "operand escapes the archive root (.. component): %s; cd into " 866 "the directory and pass a relative path", 867 ops[i]); 868 driver_free(env, owned, owned_sz); 869 rc = 1; 870 goto done; 871 } 872 rc = cpio_collect(&b, ops[i], owned); 873 driver_free(env, owned, owned_sz); 874 if (rc) goto done; 875 } 876 877 if (cpio_sort(&b)) { 878 driver_errf(CPIO_TOOL, "out of memory sorting members"); 879 rc = 1; 880 goto done; 881 } 882 883 if (opt->compress) { 884 if (kit_writer_mem(env->heap, &mem) != KIT_OK) { 885 driver_errf(CPIO_TOOL, "out of memory"); 886 rc = 1; 887 goto done; 888 } 889 target = mem; 890 } else if (opt->file) { 891 if (ctx->file_io->open_writer(ctx->file_io->user, opt->file, &out) != 892 KIT_OK) { 893 driver_errf(CPIO_TOOL, "cannot open output: %s", opt->file); 894 rc = 1; 895 goto done; 896 } 897 owned_out = 1; 898 target = out; 899 } else { 900 out = driver_stdout_writer(env); 901 owned_out = 1; 902 target = out; 903 } 904 905 memset(&co, 0, sizeof co); 906 co.w = target; 907 co.next_ino = 1u; 908 co.crc = opt->crc; 909 910 if (cpio_write_members(ctx, &b, &co) || co.err) { 911 if (owned_out && out) driver_writer_abort(out); 912 rc = 1; 913 goto done; 914 } 915 916 if (opt->compress) { 917 const uint8_t* bytes; 918 size_t blen = 0; 919 bytes = kit_writer_mem_bytes(mem, &blen); 920 if (opt->file) { 921 if (ctx->file_io->open_writer(ctx->file_io->user, opt->file, &out) != 922 KIT_OK) { 923 driver_errf(CPIO_TOOL, "cannot open output: %s", opt->file); 924 rc = 1; 925 goto done; 926 } 927 } else { 928 out = driver_stdout_writer(env); 929 } 930 owned_out = 1; 931 if (kit_compress(ctx, opt->cfmt, bytes, blen, out) != KIT_OK) { 932 driver_writer_abort(out); 933 rc = 1; 934 goto done; 935 } 936 } 937 938 rc = 0; 939 940 done: 941 if (owned_out && out) kit_writer_close(out); 942 if (mem) kit_writer_close(mem); 943 cpio_build_free(&b); 944 return rc; 945 } 946 947 /* --------------------------------------------------------------------------- 948 * Read (list / extract) — load, optionally decompress, then parse. 949 * ------------------------------------------------------------------------- */ 950 951 /* Recognize zstd / xz so we can reject them specifically instead of letting 952 * the cpio parser report a meaningless "bad magic". */ 953 static int cpio_is_unsupported_compressed(const uint8_t* d, size_t n) { 954 static const uint8_t zstd[4] = {0x28, 0xB5, 0x2F, 0xFD}; 955 static const uint8_t xz[6] = {0xFD, '7', 'z', 'X', 'Z', 0x00}; 956 if (n >= 4 && memcmp(d, zstd, 4) == 0) return 1; 957 if (n >= 6 && memcmp(d, xz, 6) == 0) return 1; 958 return 0; 959 } 960 961 static int cpio_read(DriverEnv* env, KitContext* ctx, const CpioOpts* opt) { 962 const uint8_t* raw = NULL; 963 size_t rawlen = 0; 964 DriverLoad ld = {0}; 965 uint8_t* sbuf = NULL; 966 size_t sbuf_len = 0; 967 int loaded_file = 0, loaded_stdin = 0; 968 const uint8_t* payload; 969 size_t payload_len; 970 KitWriter* dmem = NULL; 971 KitCompressFormat cfmt; 972 int rc = 1; 973 974 if (opt->file) { 975 KitSlice in; 976 if (driver_load_bytes(&env->file_io, CPIO_TOOL, opt->file, &ld, &in) != 0) 977 return 1; 978 loaded_file = 1; 979 raw = in.data; 980 rawlen = in.len; 981 } else { 982 if (!driver_read_stdin(env, &sbuf, &sbuf_len)) { 983 driver_errf(CPIO_TOOL, "failed to read stdin"); 984 return 1; 985 } 986 loaded_stdin = 1; 987 raw = sbuf; 988 rawlen = sbuf_len; 989 } 990 991 payload = raw; 992 payload_len = rawlen; 993 994 if (cpio_is_unsupported_compressed(raw, rawlen)) { 995 driver_errf(CPIO_TOOL, 996 "archive is zstd/xz-compressed; kit cpio supports gzip and " 997 "lz4 only"); 998 goto done; 999 } 1000 if (kit_compress_detect(raw, rawlen, &cfmt) == KIT_OK) { 1001 size_t dlen = 0; 1002 if (kit_writer_mem(env->heap, &dmem) != KIT_OK) { 1003 driver_errf(CPIO_TOOL, "out of memory"); 1004 goto done; 1005 } 1006 if (kit_decompress(ctx, cfmt, raw, rawlen, dmem) != KIT_OK) goto done; 1007 payload = kit_writer_mem_bytes(dmem, &dlen); 1008 payload_len = dlen; 1009 } else if (opt->decompress) { 1010 driver_errf(CPIO_TOOL, "-d given but input is not gzip/lz4-compressed"); 1011 goto done; 1012 } 1013 1014 /* Structural validation is intentionally separate from presentation and 1015 * extraction. For extraction the first pass also validates every path and 1016 * supported member type before the first filesystem mutation. */ 1017 if (cpio_parse(payload, payload_len, 1018 opt->mode == 'i' ? cpio_extract_validate : NULL, NULL) != 0) 1019 goto done; 1020 1021 if (opt->mode == 't') { 1022 CpioListCtx lc; 1023 lc.verbose = opt->verbose; 1024 rc = cpio_parse(payload, payload_len, cpio_list_visit, &lc); 1025 } else { 1026 CpioExtractCtx xc; 1027 xc.env = env; 1028 xc.ctx = ctx; 1029 xc.verbose = opt->verbose; 1030 rc = cpio_parse(payload, payload_len, cpio_extract_visit, &xc); 1031 } 1032 1033 done: 1034 if (dmem) kit_writer_close(dmem); 1035 if (loaded_file) driver_release_bytes(&env->file_io, &ld); 1036 if (loaded_stdin && sbuf) driver_free(env, sbuf, sbuf_len); 1037 return rc; 1038 } 1039 1040 /* --------------------------------------------------------------------------- 1041 * Entry point 1042 * ------------------------------------------------------------------------- */ 1043 1044 static int cpio_opt_arg(int argc, char** argv, int* i, const char* tool, 1045 const char* flag, const char** out) { 1046 /* Accept "--flag=value", "-Fvalue", or a separate next argument. */ 1047 const char* a = argv[*i]; 1048 size_t flen = driver_strlen(flag); 1049 if (driver_strneq(a, flag, flen) && a[flen] == '=') { 1050 *out = a + flen + 1u; 1051 return 0; 1052 } 1053 if (*i + 1 >= argc) { 1054 driver_errf(tool, "%s requires an argument", flag); 1055 return 1; 1056 } 1057 *out = argv[++(*i)]; 1058 return 0; 1059 } 1060 1061 static int cpio_set_mode(CpioOpts* opt, char mode, const char* spelling) { 1062 if (opt->mode && opt->mode != mode) { 1063 driver_errf(CPIO_TOOL, "conflicting archive modes: -%c and %s", opt->mode, 1064 spelling); 1065 return 1; 1066 } 1067 opt->mode = mode; 1068 return 0; 1069 } 1070 1071 int driver_cpio(int argc, char** argv) { 1072 DriverEnv env; 1073 KitContext ctx; 1074 CpioOpts o; 1075 const char** ops = NULL; 1076 size_t nops = 0; 1077 int i, rc = 2; 1078 1079 if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { 1080 driver_help_cpio(); 1081 return 0; 1082 } 1083 1084 memset(&o, 0, sizeof o); 1085 driver_env_init(&env); 1086 ctx = driver_env_to_context(&env); 1087 1088 ops = (const char**)driver_alloc(&env, sizeof(char*) * (size_t)(argc)); 1089 if (!ops) { 1090 driver_errf(CPIO_TOOL, "out of memory"); 1091 rc = 1; 1092 goto done; 1093 } 1094 1095 for (i = 1; i < argc; ++i) { 1096 const char* a = argv[i]; 1097 1098 if (driver_streq(a, "--")) { /* end of options */ 1099 for (++i; i < argc; ++i) ops[nops++] = argv[i]; 1100 break; 1101 } 1102 1103 if (driver_strneq(a, "--", 2)) { /* long option */ 1104 if (driver_streq(a, "--create")) { 1105 if (cpio_set_mode(&o, 'o', a)) goto done; 1106 } else if (driver_streq(a, "--list")) { 1107 if (cpio_set_mode(&o, 't', a)) goto done; 1108 } else if (driver_streq(a, "--extract")) { 1109 if (cpio_set_mode(&o, 'i', a)) goto done; 1110 } else if (driver_streq(a, "--verbose")) { 1111 o.verbose = 1; 1112 } else if (driver_streq(a, "--decompress")) { 1113 o.decompress = 1; 1114 } else if (driver_streq(a, "--lz4")) { 1115 o.compress = 1; 1116 o.cfmt = KIT_COMPRESS_LZ4_FRAME; 1117 } else if (driver_streq(a, "--file") || driver_strneq(a, "--file=", 7)) { 1118 if (cpio_opt_arg(argc, argv, &i, CPIO_TOOL, "--file", &o.file)) 1119 goto done; 1120 } else if (driver_streq(a, "--format") || 1121 driver_strneq(a, "--format=", 9)) { 1122 const char* v; 1123 if (cpio_opt_arg(argc, argv, &i, CPIO_TOOL, "--format", &v)) goto done; 1124 if (cpio_parse_format(v, &o.crc)) { 1125 driver_errf(CPIO_TOOL, "unsupported format: %s (use newc or crc)", v); 1126 goto done; 1127 } 1128 } else if (driver_streq(a, "--compress") || 1129 driver_strneq(a, "--compress=", 11)) { 1130 const char* v; 1131 if (cpio_opt_arg(argc, argv, &i, CPIO_TOOL, "--compress", &v)) goto done; 1132 if (cpio_parse_compress(v, &o.cfmt)) { 1133 driver_errf(CPIO_TOOL, 1134 "unsupported compressor: %s (kit cpio supports gzip and " 1135 "lz4 only)", 1136 v); 1137 goto done; 1138 } 1139 o.compress = 1; 1140 } else { 1141 driver_errf(CPIO_TOOL, "unknown option: %s", a); 1142 goto done; 1143 } 1144 continue; 1145 } 1146 1147 if (a[0] == '-' && a[1] != '\0') { /* short cluster, e.g. -tv, -Fout */ 1148 int j; 1149 for (j = 1; a[j]; ++j) { 1150 char ch = a[j]; 1151 if (ch == 'o') { 1152 if (cpio_set_mode(&o, 'o', "-o")) goto done; 1153 } else if (ch == 't') { 1154 if (cpio_set_mode(&o, 't', "-t")) goto done; 1155 } else if (ch == 'i') { 1156 if (cpio_set_mode(&o, 'i', "-i")) goto done; 1157 } else if (ch == 'v') { 1158 o.verbose = 1; 1159 } else if (ch == 'd') { 1160 o.decompress = 1; 1161 } else if (ch == 'z') { 1162 o.compress = 1; 1163 o.cfmt = KIT_COMPRESS_GZIP; 1164 } else if (ch == 'F' || ch == 'H') { 1165 const char* v; 1166 if (a[j + 1]) { 1167 v = a + j + 1; /* -Fvalue */ 1168 } else if (i + 1 < argc) { 1169 v = argv[++i]; /* -F value */ 1170 } else { 1171 driver_errf(CPIO_TOOL, "-%c requires an argument", ch); 1172 goto done; 1173 } 1174 if (ch == 'F') { 1175 o.file = v; 1176 } else if (cpio_parse_format(v, &o.crc)) { 1177 driver_errf(CPIO_TOOL, "unsupported format: %s (use newc or crc)", 1178 v); 1179 goto done; 1180 } 1181 break; /* value consumed the rest of the cluster */ 1182 } else { 1183 driver_errf(CPIO_TOOL, "unknown option: -%c", ch); 1184 goto done; 1185 } 1186 } 1187 continue; 1188 } 1189 1190 ops[nops++] = a; /* operand (a file/dir, or "-") */ 1191 } 1192 1193 if (o.mode == 0) { 1194 driver_errf(CPIO_TOOL, "one of -o, -t, or -i is required"); 1195 goto done; 1196 } 1197 if (o.compress && o.mode != 'o') { 1198 driver_errf(CPIO_TOOL, "compression flags apply to -o (create) only"); 1199 goto done; 1200 } 1201 if (o.decompress && o.mode == 'o') { 1202 driver_errf(CPIO_TOOL, "-d applies to -t/-i (read) only"); 1203 goto done; 1204 } 1205 if (o.mode != 'o' && nops > 0) { 1206 driver_errf(CPIO_TOOL, 1207 "member patterns are not supported: %s", ops[0]); 1208 rc = 1; 1209 goto done; 1210 } 1211 1212 if (o.mode == 'o') { 1213 rc = cpio_create(&env, &ctx, &o, ops, nops); 1214 } else { 1215 rc = cpio_read(&env, &ctx, &o); 1216 } 1217 1218 done: 1219 if (ops) driver_free(&env, ops, sizeof(char*) * (size_t)(argc)); 1220 driver_env_fini(&env); 1221 return rc; 1222 }