strip.c (20180B)
1 #include <kit/archive.h> 2 #include <kit/core.h> 3 #include <kit/object.h> 4 #include <stdint.h> 5 #include <string.h> 6 7 #include "driver.h" 8 #include "inputs.h" 9 10 /* `kit strip` — drop debug sections and / or unwanted symbols from a 11 * relocatable object or static archive, then write the result back. Scope 12 * for the first cut matches the CTOOLCHAIN.md plan: relocatable .o and 13 * .a inputs only — linked ELF (ET_EXEC / ET_DYN) is rejected. 14 * 15 * Operations (the last one wins; default is --strip-all): 16 * --strip-debug drop sections whose kind is KIT_SEC_DEBUG 17 * --strip-unneeded drop debug + symbols not referenced by any reloc 18 * --strip-all drop debug + every non-essential symbol (default) 19 * 20 * Filters applied on top of the operation: 21 * --keep-symbol=NAME, -K NAME keep NAME even if the operation would drop 22 * it 23 * --strip-symbol=NAME, -N NAME always drop NAME 24 * 25 * I/O: 26 * -o PATH write output to PATH (else rewrite the input in place) 27 */ 28 29 #define STRIP_TOOL "strip" 30 31 void driver_help_strip(void) { 32 driver_printf( 33 "%.*s", 34 KIT_SLICE_ARG(KIT_SLICE_LIT( 35 "kit strip — drop debug sections and/or symbols\n" 36 "\n" 37 "USAGE\n" 38 " kit strip [OPTIONS] FILE\n" 39 "\n" 40 "OPERATIONS (last one wins; default is --strip-all)\n" 41 " --strip-debug remove debug-info sections\n" 42 " --strip-unneeded remove debug + symbols not needed by " 43 "relocs\n" 44 " --strip-all remove debug + all non-essential symbols\n" 45 "\n" 46 "SYMBOL FILTERS (may repeat)\n" 47 " --keep-symbol=NAME, -K NAME keep NAME even when the operation\n" 48 " would otherwise drop it\n" 49 " --strip-symbol=NAME, -N NAME always drop NAME\n" 50 "\n" 51 "OUTPUT\n" 52 " -o PATH write to PATH (default: rewrite FILE in " 53 "place)\n" 54 "\n" 55 "INPUTS\n" 56 " FILE may be a relocatable .o or a static .a archive. Linked\n" 57 " executables / shared libraries are not supported yet.\n" 58 "\n" 59 "EXIT CODES\n" 60 " 0 success 1 I/O or strip error 2 bad " 61 "usage\n"))); 62 } 63 64 typedef enum StripOp { 65 STRIP_OP_DEBUG, 66 STRIP_OP_UNNEEDED, 67 STRIP_OP_ALL, 68 } StripOp; 69 70 typedef struct StripOpts { 71 StripOp op; 72 const char** keep; 73 uint32_t nkeep; 74 uint32_t cap_keep; 75 const char** strip; 76 uint32_t nstrip; 77 uint32_t cap_strip; 78 const char* output; 79 const char* input; 80 } StripOpts; 81 82 static int name_in_list(KitSlice name, const char* const* list, uint32_t n) { 83 uint32_t i; 84 if (!name.len) return 0; 85 for (i = 0; i < n; ++i) { 86 if (list[i] && kit_slice_eq_cstr(name, list[i])) return 1; 87 } 88 return 0; 89 } 90 91 static int push_name(DriverEnv* env, const char*** arr, uint32_t* n, 92 uint32_t* cap, const char* name) { 93 if (*n >= *cap) { 94 uint32_t newcap = *cap ? *cap * 2u : 8u; 95 const char** nb = 96 (const char**)driver_alloc_zeroed(env, (size_t)newcap * sizeof(*nb)); 97 if (!nb) { 98 driver_errf(STRIP_TOOL, "out of memory"); 99 return -1; 100 } 101 if (*arr) { 102 memcpy(nb, *arr, (size_t)(*n) * sizeof(*nb)); 103 driver_free(env, (void*)*arr, (size_t)(*cap) * sizeof(*nb)); 104 } 105 *arr = nb; 106 *cap = newcap; 107 } 108 (*arr)[(*n)++] = name; 109 return 0; 110 } 111 112 static int parse_name_arg(int* i, int argc, char** argv, const char* flag, 113 const char* short_flag, const char** out) { 114 const char* a = argv[*i]; 115 size_t flen = kit_slice_cstr(flag).len; 116 /* --flag=NAME ; prefix match */ 117 if (driver_strneq(a, flag, flen) && a[flen] == '=') { 118 *out = a + flen + 1; 119 return 1; 120 } 121 /* --flag NAME (no '='): treat as "next argv" form, used when --flag is 122 * passed without value. Not standard; skip. */ 123 if (driver_streq(a, flag)) { 124 if (*i + 1 >= argc) return -1; 125 *out = argv[++(*i)]; 126 return 1; 127 } 128 /* -K NAME / -N NAME */ 129 if (short_flag && driver_streq(a, short_flag)) { 130 if (*i + 1 >= argc) return -1; 131 *out = argv[++(*i)]; 132 return 1; 133 } 134 return 0; 135 } 136 137 /* Collect the set of KitObjSymbol ids targeted by any reloc whose 138 * containing section will survive emit — relocs inside the 139 * about-to-be-removed KIT_SEC_DEBUG sections don't count. Otherwise a 140 * symbol that's referenced only from DWARF (e.g. main's debug_info entry) 141 * keeps every function symbol alive even though the on-disk relocs 142 * holding it won't make it to the output. */ 143 static int collect_needed_syms(DriverEnv* env, KitObjFile* of, 144 KitObjSymbol** needed_out, uint32_t* n_out, 145 uint32_t* cap_out) { 146 KitObjRelocIter* rit = NULL; 147 KitObjSymbol* arr = NULL; 148 uint32_t n = 0, cap = 0; 149 150 if (kit_obj_reliter_new(of, &rit) != KIT_OK) { 151 driver_errf(STRIP_TOOL, "out of memory"); 152 return 1; 153 } 154 for (;;) { 155 KitObjReloc r; 156 KitIterResult ir = kit_obj_reliter_next(rit, &r); 157 uint32_t k; 158 int seen = 0; 159 if (ir != KIT_ITER_ITEM) break; 160 if (r.sym == KIT_OBJ_SYMBOL_NONE) continue; 161 /* Skip relocs hosted in a debug section — that section is being 162 * dropped, so its relocs don't actually "need" their targets. */ 163 if (r.section != KIT_SECTION_NONE) { 164 KitObjSecInfo hi; 165 if (kit_obj_section(of, r.section, &hi) == KIT_OK && 166 hi.kind == KIT_SEC_DEBUG) { 167 continue; 168 } 169 } 170 for (k = 0; k < n; ++k) { 171 if (arr[k] == r.sym) { 172 seen = 1; 173 break; 174 } 175 } 176 if (seen) continue; 177 if (n >= cap) { 178 uint32_t newcap = cap ? cap * 2u : 32u; 179 KitObjSymbol* nb = 180 (KitObjSymbol*)driver_alloc_zeroed(env, (size_t)newcap * sizeof(*nb)); 181 if (!nb) { 182 kit_obj_reliter_free(rit); 183 if (arr) driver_free(env, arr, (size_t)cap * sizeof(*arr)); 184 driver_errf(STRIP_TOOL, "out of memory"); 185 return 1; 186 } 187 if (arr) { 188 memcpy(nb, arr, (size_t)n * sizeof(*arr)); 189 driver_free(env, arr, (size_t)cap * sizeof(*arr)); 190 } 191 arr = nb; 192 cap = newcap; 193 } 194 arr[n++] = r.sym; 195 } 196 kit_obj_reliter_free(rit); 197 *needed_out = arr; 198 *n_out = n; 199 *cap_out = cap; 200 return 0; 201 } 202 203 static int id_in_set(KitObjSymbol id, const KitObjSymbol* arr, uint32_t n) { 204 uint32_t i; 205 for (i = 0; i < n; ++i) { 206 if (arr[i] == id) return 1; 207 } 208 return 0; 209 } 210 211 /* The core strip pass: drop debug sections, then walk symbols and apply 212 * keep/strip lists and the operation policy. Mutations are issued 213 * against the builder; emit-time sweep cleans up cascades (orphan 214 * relocs against removed sections, dropped group memberships, etc.). */ 215 static int strip_one_builder(DriverEnv* env, KitObjFile* of, KitObjBuilder* b, 216 const StripOpts* opts) { 217 uint32_t i, nsec; 218 int filter_syms = (opts->op == STRIP_OP_UNNEEDED || opts->op == STRIP_OP_ALL); 219 KitObjSymbol* needed = NULL; 220 uint32_t nneeded = 0, cap_needed = 0; 221 KitObjSymIter* sit = NULL; 222 int rc = 1; 223 224 /* Step 1: drop debug sections (every supported op does this). */ 225 nsec = kit_obj_nsections(of); 226 for (i = 0; i < nsec; ++i) { 227 KitObjSecInfo si; 228 if (kit_obj_section(of, i, &si) != KIT_OK) continue; 229 if (si.kind == KIT_SEC_DEBUG) { 230 kit_obj_builder_remove_section(b, i); 231 } 232 } 233 234 /* Step 2: compute the needed-sym set. */ 235 if (filter_syms) { 236 if (collect_needed_syms(env, of, &needed, &nneeded, &cap_needed) != 0) { 237 return 1; 238 } 239 } 240 241 /* Step 3: walk symbols and apply filters. */ 242 if (kit_obj_symiter_new(of, &sit) != KIT_OK) { 243 driver_errf(STRIP_TOOL, "out of memory"); 244 goto done; 245 } 246 for (;;) { 247 KitObjSymInfo si; 248 KitIterResult ir = kit_obj_symiter_next(sit, &si); 249 int drop = 0; 250 if (ir != KIT_ITER_ITEM) break; 251 /* --strip-symbol wins over --keep-symbol if both list the same name. */ 252 if (opts->nstrip && name_in_list(si.name, opts->strip, opts->nstrip)) { 253 drop = 1; 254 } else if (opts->nkeep && name_in_list(si.name, opts->keep, opts->nkeep)) { 255 drop = 0; 256 } else if (filter_syms) { 257 /* Keep undefined externals so the .o stays linkable; keep symbols 258 * targeted by a surviving reloc; drop everything else. Note that 259 * section symbols defined in removed debug sections are already 260 * tombstoned by the emit-time sweep cascade — no explicit handling 261 * needed here. */ 262 if (si.kind == KIT_SK_UNDEF) { 263 drop = 0; 264 } else if (id_in_set(si.id, needed, nneeded)) { 265 drop = 0; 266 } else { 267 drop = 1; 268 } 269 } 270 if (drop) { 271 kit_obj_builder_remove_symbol(b, si.id); 272 } 273 } 274 kit_obj_symiter_free(sit); 275 rc = 0; 276 277 done: 278 if (needed) driver_free(env, needed, (size_t)cap_needed * sizeof(*needed)); 279 return rc; 280 } 281 282 static int strip_object_bytes(DriverEnv* env, const KitContext* ctx, 283 const KitSlice* input, const StripOpts* opts, 284 uint8_t** out_data, size_t* out_size) { 285 KitObjFile* of = NULL; 286 KitObjBuilder* b; 287 KitWriter* w = NULL; 288 size_t n = 0; 289 const uint8_t* data; 290 uint8_t* copy; 291 int rc = 1; 292 293 *out_data = NULL; 294 *out_size = 0; 295 296 if (kit_obj_open(ctx, KIT_SLICE_NULL, input, &of) != KIT_OK) { 297 driver_errf(STRIP_TOOL, "not a recognized object"); 298 return 1; 299 } 300 b = kit_obj_file_builder(of); 301 if (!b) { 302 driver_errf(STRIP_TOOL, "no builder for object"); 303 kit_obj_free(of); 304 return 1; 305 } 306 307 if (strip_one_builder(env, of, b, opts) != 0) { 308 kit_obj_free(of); 309 return 1; 310 } 311 312 if (kit_writer_mem(env->heap, &w) != KIT_OK || !w) { 313 driver_errf(STRIP_TOOL, "out of memory"); 314 kit_obj_free(of); 315 return 1; 316 } 317 if (kit_obj_builder_emit(b, w) != KIT_OK) { 318 driver_errf(STRIP_TOOL, "emit failed"); 319 kit_writer_close(w); 320 kit_obj_free(of); 321 return 1; 322 } 323 data = kit_writer_mem_bytes(w, &n); 324 copy = (uint8_t*)driver_alloc(env, n ? n : 1u); 325 if (!copy) { 326 driver_errf(STRIP_TOOL, "out of memory"); 327 kit_writer_close(w); 328 kit_obj_free(of); 329 return 1; 330 } 331 if (n) memcpy(copy, data, n); 332 kit_writer_close(w); 333 kit_obj_free(of); 334 335 *out_data = copy; 336 *out_size = n; 337 rc = 0; 338 return rc; 339 } 340 341 static uint64_t strip_epoch_from_env(void) { 342 const char* s = driver_getenv("SOURCE_DATE_EPOCH"); 343 uint64_t v = 0; 344 if (!s || !*s) return 0; 345 for (; *s; ++s) { 346 if (*s < '0' || *s > '9') return 0; 347 v = v * 10 + (uint64_t)(*s - '0'); 348 } 349 return v; 350 } 351 352 /* Strip every object member of an archive, write a fresh archive with 353 * a refreshed System-V symbol index. Non-object members pass through 354 * unchanged. */ 355 static int strip_archive(DriverEnv* env, const KitContext* ctx, 356 const KitSlice* input, const StripOpts* opts, 357 const char* output_path) { 358 KitArIter* it = NULL; 359 KitArMember m; 360 KitArInput* members = NULL; 361 uint8_t** owned_data = NULL; 362 size_t* owned_size = NULL; 363 char* name_storage = NULL; 364 size_t name_bytes_total = 0; 365 uint32_t nmembers = 0, k; 366 KitArMemberSymbols* msyms = NULL; 367 void** sym_allocs = NULL; 368 size_t* sym_alloc_szs = NULL; 369 KitWriter* out = NULL; 370 KitArWriteOptions opts_ar = {0}; 371 int rc = 1; 372 373 /* Pass 1: count members + total name bytes. */ 374 if (kit_ar_iter_new(ctx, input, &it) != KIT_OK) { 375 driver_errf(STRIP_TOOL, "not an archive"); 376 return 1; 377 } 378 for (;;) { 379 KitIterResult r = kit_ar_iter_next(it, &m); 380 if (r != KIT_ITER_ITEM) break; 381 nmembers++; 382 name_bytes_total += m.name.len + 1; 383 } 384 kit_ar_iter_free(it); 385 it = NULL; 386 387 if (nmembers) { 388 members = (KitArInput*)driver_alloc_zeroed( 389 env, (size_t)nmembers * sizeof(*members)); 390 owned_data = (uint8_t**)driver_alloc_zeroed( 391 env, (size_t)nmembers * sizeof(*owned_data)); 392 owned_size = (size_t*)driver_alloc_zeroed( 393 env, (size_t)nmembers * sizeof(*owned_size)); 394 if (!members || !owned_data || !owned_size) { 395 driver_errf(STRIP_TOOL, "out of memory"); 396 goto done; 397 } 398 } 399 if (name_bytes_total) { 400 name_storage = (char*)driver_alloc_zeroed(env, name_bytes_total); 401 if (!name_storage) { 402 driver_errf(STRIP_TOOL, "out of memory"); 403 goto done; 404 } 405 } 406 407 /* Pass 2: walk members; strip object members, pass others through. */ 408 if (kit_ar_iter_new(ctx, input, &it) != KIT_OK) { 409 driver_errf(STRIP_TOOL, "iter re-open failed"); 410 goto done; 411 } 412 { 413 size_t cursor = 0; 414 k = 0; 415 while (k < nmembers) { 416 KitIterResult r = kit_ar_iter_next(it, &m); 417 size_t j; 418 char* dst; 419 KitBinFmt fmt; 420 KitSlice mbytes; 421 if (r != KIT_ITER_ITEM) break; 422 dst = name_storage + cursor; 423 for (j = 0; j < m.name.len; ++j) *dst++ = m.name.s[j]; 424 *dst++ = '\0'; 425 members[k].name.s = name_storage + cursor; 426 members[k].name.len = m.name.len; 427 cursor = (size_t)(dst - name_storage); 428 429 mbytes.data = m.data; 430 mbytes.len = m.size; 431 fmt = kit_detect_fmt(m.data, m.size); 432 if (fmt == KIT_BIN_ELF || fmt == KIT_BIN_COFF || fmt == KIT_BIN_MACHO || 433 fmt == KIT_BIN_WASM) { 434 uint8_t* sd = NULL; 435 size_t ss = 0; 436 if (strip_object_bytes(env, ctx, &mbytes, opts, &sd, &ss) != 0) { 437 kit_ar_iter_free(it); 438 it = NULL; 439 goto done; 440 } 441 owned_data[k] = sd; 442 owned_size[k] = ss; 443 members[k].bytes.data = sd; 444 members[k].bytes.len = ss; 445 } else { 446 members[k].bytes.data = m.data; 447 members[k].bytes.len = m.size; 448 } 449 k++; 450 } 451 } 452 kit_ar_iter_free(it); 453 it = NULL; 454 455 /* Pass 3: rebuild the System-V symbol index from the new bytes. */ 456 if (nmembers) { 457 msyms = (KitArMemberSymbols*)driver_alloc_zeroed( 458 env, (size_t)nmembers * sizeof(*msyms)); 459 sym_allocs = (void**)driver_alloc_zeroed( 460 env, (size_t)nmembers * sizeof(*sym_allocs)); 461 sym_alloc_szs = (size_t*)driver_alloc_zeroed( 462 env, (size_t)nmembers * sizeof(*sym_alloc_szs)); 463 if (!msyms || !sym_allocs || !sym_alloc_szs) { 464 driver_errf(STRIP_TOOL, "out of memory"); 465 goto done; 466 } 467 for (k = 0; k < nmembers; ++k) { 468 void* blob = NULL; 469 size_t blob_size = 0; 470 const KitSlice* names = NULL; 471 uint32_t count = 0; 472 if (driver_collect_obj_global_syms(env, ctx, STRIP_TOOL, 473 &members[k].bytes, &blob, &blob_size, 474 &names, &count) != 0) { 475 goto done; 476 } 477 sym_allocs[k] = blob; 478 sym_alloc_szs[k] = blob_size; 479 msyms[k].names = names; 480 msyms[k].count = count; 481 } 482 } 483 484 if (ctx->file_io->open_writer(ctx->file_io->user, output_path, &out) != 485 KIT_OK) { 486 driver_errf(STRIP_TOOL, "failed to open: %.*s", 487 KIT_SLICE_ARG(kit_slice_cstr(output_path))); 488 goto done; 489 } 490 opts_ar.epoch = strip_epoch_from_env(); 491 opts_ar.long_names = 1; 492 opts_ar.symbol_index = 1; 493 opts_ar.member_symbols = msyms; 494 rc = kit_ar_write(out, members, nmembers, &opts_ar) == KIT_OK ? 0 : 1; 495 if (rc == 0 && kit_writer_status(out) != KIT_OK) rc = 1; 496 497 done: 498 if (out) kit_writer_close(out); 499 if (it) kit_ar_iter_free(it); 500 if (sym_allocs) { 501 for (k = 0; k < nmembers; ++k) { 502 if (sym_allocs[k]) 503 driver_collect_obj_global_syms_free(env, sym_allocs[k], 504 sym_alloc_szs[k]); 505 } 506 driver_free(env, sym_allocs, (size_t)nmembers * sizeof(*sym_allocs)); 507 } 508 if (sym_alloc_szs) 509 driver_free(env, sym_alloc_szs, (size_t)nmembers * sizeof(*sym_alloc_szs)); 510 if (msyms) driver_free(env, msyms, (size_t)nmembers * sizeof(*msyms)); 511 if (owned_data) { 512 for (k = 0; k < nmembers; ++k) { 513 if (owned_data[k]) driver_free(env, owned_data[k], owned_size[k]); 514 } 515 driver_free(env, owned_data, (size_t)nmembers * sizeof(*owned_data)); 516 } 517 if (owned_size) 518 driver_free(env, owned_size, (size_t)nmembers * sizeof(*owned_size)); 519 if (members) driver_free(env, members, (size_t)nmembers * sizeof(*members)); 520 if (name_storage) driver_free(env, name_storage, name_bytes_total); 521 return rc; 522 } 523 524 int driver_strip(int argc, char** argv) { 525 DriverEnv env; 526 KitContext ctx; 527 StripOpts opts; 528 KitFileData input_fd = {0}; 529 KitSlice input; 530 KitWriter* w = NULL; 531 uint8_t* out_data = NULL; 532 size_t out_size = 0; 533 int have_input = 0; 534 int rc = 1; 535 int i; 536 537 if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { 538 driver_help_strip(); 539 return 0; 540 } 541 542 memset(&opts, 0, sizeof opts); 543 opts.op = STRIP_OP_ALL; 544 driver_env_init(&env); 545 ctx = driver_env_to_context(&env); 546 547 for (i = 1; i < argc; ++i) { 548 const char* a = argv[i]; 549 const char* val = NULL; 550 int matched; 551 if (driver_streq(a, "--strip-debug")) { 552 opts.op = STRIP_OP_DEBUG; 553 continue; 554 } 555 if (driver_streq(a, "--strip-unneeded")) { 556 opts.op = STRIP_OP_UNNEEDED; 557 continue; 558 } 559 if (driver_streq(a, "--strip-all") || driver_streq(a, "-s")) { 560 opts.op = STRIP_OP_ALL; 561 continue; 562 } 563 if (driver_streq(a, "-o")) { 564 if (i + 1 >= argc) { 565 driver_errf(STRIP_TOOL, "-o requires a path"); 566 rc = 2; 567 goto done; 568 } 569 opts.output = argv[++i]; 570 continue; 571 } 572 matched = parse_name_arg(&i, argc, argv, "--keep-symbol", "-K", &val); 573 if (matched < 0) { 574 driver_errf(STRIP_TOOL, "%.*s requires a symbol name", 575 KIT_SLICE_ARG(kit_slice_cstr(a))); 576 rc = 2; 577 goto done; 578 } 579 if (matched) { 580 if (push_name(&env, &opts.keep, &opts.nkeep, &opts.cap_keep, val) != 0) { 581 rc = 1; 582 goto done; 583 } 584 continue; 585 } 586 matched = parse_name_arg(&i, argc, argv, "--strip-symbol", "-N", &val); 587 if (matched < 0) { 588 driver_errf(STRIP_TOOL, "%.*s requires a symbol name", 589 KIT_SLICE_ARG(kit_slice_cstr(a))); 590 rc = 2; 591 goto done; 592 } 593 if (matched) { 594 if (push_name(&env, &opts.strip, &opts.nstrip, &opts.cap_strip, val) != 595 0) { 596 rc = 1; 597 goto done; 598 } 599 continue; 600 } 601 if (a[0] == '-' && a[1] != '\0') { 602 driver_errf(STRIP_TOOL, "unknown option: %.*s", 603 KIT_SLICE_ARG(kit_slice_cstr(a))); 604 rc = 2; 605 goto done; 606 } 607 if (opts.input) { 608 driver_errf(STRIP_TOOL, "only one input file is supported"); 609 rc = 2; 610 goto done; 611 } 612 opts.input = a; 613 } 614 615 if (!opts.input) { 616 driver_errf(STRIP_TOOL, "missing input file"); 617 rc = 2; 618 goto done; 619 } 620 621 if (ctx.file_io->read_all(ctx.file_io->user, opts.input, &input_fd) != 622 KIT_OK) { 623 driver_errf(STRIP_TOOL, "failed to read: %.*s", 624 KIT_SLICE_ARG(kit_slice_cstr(opts.input))); 625 goto done; 626 } 627 have_input = 1; 628 input.data = input_fd.data; 629 input.len = input_fd.size; 630 631 { 632 KitBinFmt fmt = kit_detect_fmt(input.data, input.len); 633 const char* out_path = opts.output ? opts.output : opts.input; 634 if (fmt == KIT_BIN_AR) { 635 rc = strip_archive(&env, &ctx, &input, &opts, out_path); 636 goto done; 637 } 638 if (strip_object_bytes(&env, &ctx, &input, &opts, &out_data, &out_size) != 639 0) { 640 goto done; 641 } 642 if (ctx.file_io->open_writer(ctx.file_io->user, out_path, &w) != KIT_OK) { 643 driver_errf(STRIP_TOOL, "failed to open: %.*s", 644 KIT_SLICE_ARG(kit_slice_cstr(out_path))); 645 goto done; 646 } 647 kit_writer_write(w, out_data, out_size); 648 if (kit_writer_status(w) != KIT_OK) { 649 driver_errf(STRIP_TOOL, "write failed: %.*s", 650 KIT_SLICE_ARG(kit_slice_cstr(out_path))); 651 goto done; 652 } 653 rc = 0; 654 } 655 656 done: 657 if (w) kit_writer_close(w); 658 if (out_data) driver_free(&env, out_data, out_size); 659 if (have_input) ctx.file_io->release(ctx.file_io->user, &input_fd); 660 if (opts.keep) 661 driver_free(&env, (void*)opts.keep, 662 (size_t)opts.cap_keep * sizeof(*opts.keep)); 663 if (opts.strip) 664 driver_free(&env, (void*)opts.strip, 665 (size_t)opts.cap_strip * sizeof(*opts.strip)); 666 driver_env_fini(&env); 667 return rc; 668 }