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