kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

archive.c (20686B)


      1 /* POSIX ar archive reader/writer. */
      2 
      3 #include <kit/archive.h>
      4 #include <kit/object.h>
      5 
      6 #include "core/bytes.h"
      7 #include "core/core.h"
      8 #include "core/heap.h"
      9 #include "core/slice.h"
     10 
     11 /* ============================================================
     12  * Write helpers
     13  * ============================================================ */
     14 
     15 static KitStatus wh_bytes(Writer* w, const void* p, size_t n) {
     16   return kit_writer_write(w, p, n);
     17 }
     18 static KitStatus wh_char(Writer* w, char c) {
     19   return kit_writer_write(w, &c, 1);
     20 }
     21 static KitStatus wh_nl(Writer* w) { return wh_char(w, '\n'); }
     22 
     23 static void wh_ar_num(char* dst, int width, u64 v) {
     24   char tmp[20];
     25   int len = 0, i;
     26   if (v == 0) {
     27     tmp[len++] = '0';
     28   } else {
     29     u64 t = v;
     30     while (t) {
     31       tmp[len++] = '0' + (int)(t % 10);
     32       t /= 10;
     33     }
     34   }
     35   for (i = 0; i < len / 2; ++i) {
     36     char x = tmp[i];
     37     tmp[i] = tmp[len - 1 - i];
     38     tmp[len - 1 - i] = x;
     39   }
     40   for (i = 0; i < len && i < width; ++i) dst[i] = tmp[i];
     41   for (; i < width; ++i) dst[i] = ' ';
     42 }
     43 
     44 static KitStatus wh_be32(Writer* w, u32 v) {
     45   u8 b[4];
     46   wr_u32_be(b, v);
     47   return wh_bytes(w, b, 4);
     48 }
     49 
     50 static Slice ar_name_basename(Slice in) {
     51   size_t start = 0, i;
     52   for (i = 0; i < in.len; ++i) {
     53     if (in.s[i] == '/') start = i + 1;
     54   }
     55   return (Slice){.s = in.s + start, .len = in.len - start};
     56 }
     57 
     58 static int ar_name_needs_longtable(const char* name, size_t len) {
     59   size_t i;
     60   if (len > 15) return 1;
     61   for (i = 0; i < len; ++i)
     62     if (name[i] == '/') return 1;
     63   return 0;
     64 }
     65 
     66 static size_t ar_member_padded_size(size_t len) { return 60 + len + (len & 1); }
     67 
     68 static void ar_fill_header(char hdr[60], const char name_field[16],
     69                            uint64_t epoch, uint64_t size) {
     70   size_t j;
     71   for (j = 0; j < 16; ++j) hdr[j] = name_field[j];
     72   for (j = 16; j < 28; ++j) hdr[j] = ' ';
     73   if (epoch)
     74     wh_ar_num(hdr + 16, 12, epoch);
     75   else
     76     hdr[16] = '0';
     77   for (j = 28; j < 34; ++j) hdr[j] = ' ';
     78   hdr[28] = '0';
     79   for (j = 34; j < 40; ++j) hdr[j] = ' ';
     80   hdr[34] = '0';
     81   for (j = 40; j < 48; ++j) hdr[j] = ' ';
     82   hdr[40] = '6';
     83   hdr[41] = '4';
     84   hdr[42] = '4';
     85   wh_ar_num(hdr + 48, 10, size);
     86   hdr[58] = '`';
     87   hdr[59] = '\n';
     88 }
     89 
     90 KitStatus kit_ar_write(KitWriter* out, const KitArInput* members,
     91                        uint32_t nmembers, const KitArWriteOptions* opts) {
     92   static const char magic[] = "!<arch>\n";
     93   static const KitArWriteOptions default_opts = {0, 0, 0, NULL};
     94   uint32_t i;
     95   uint64_t epoch;
     96   int long_names;
     97   int symbol_index;
     98   const KitArMemberSymbols* msyms;
     99   uint64_t longtab_size = 0;
    100   uint32_t nsyms = 0;
    101   uint64_t names_size = 0;
    102   uint64_t index_payload = 0;
    103   uint64_t index_total = 0;
    104   uint64_t longtab_total = 0;
    105   char pad = '\n';
    106 
    107   if (!out) return KIT_INVALID;
    108   if (!members && nmembers) return KIT_INVALID;
    109 
    110   if (!opts) opts = &default_opts;
    111   epoch = opts->epoch;
    112   long_names = opts->long_names;
    113   symbol_index = opts->symbol_index;
    114   msyms = opts->member_symbols;
    115 
    116   if (long_names) {
    117     for (i = 0; i < nmembers; ++i) {
    118       Slice base;
    119       if (!members[i].name.s) return KIT_INVALID;
    120       base = ar_name_basename(members[i].name);
    121       if (ar_name_needs_longtable(base.s, base.len)) {
    122         longtab_size += (uint64_t)base.len + 2;
    123       }
    124     }
    125   } else {
    126     for (i = 0; i < nmembers; ++i) {
    127       if (!members[i].name.s) return KIT_INVALID;
    128     }
    129   }
    130 
    131   if (symbol_index) {
    132     if (msyms) {
    133       for (i = 0; i < nmembers; ++i) {
    134         u32 k;
    135         if (msyms[i].count && !msyms[i].names) return KIT_INVALID;
    136         for (k = 0; k < msyms[i].count; ++k) {
    137           Slice nm = msyms[i].names[k];
    138           if (!nm.s) return KIT_INVALID;
    139           nsyms += 1;
    140           names_size += (uint64_t)nm.len + 1;
    141         }
    142       }
    143     }
    144     index_payload = (uint64_t)4 + (uint64_t)4 * (uint64_t)nsyms + names_size;
    145     index_total = 60 + index_payload + (index_payload & 1);
    146   }
    147   if (longtab_size) {
    148     longtab_total = 60 + longtab_size + (longtab_size & 1);
    149   }
    150 
    151   wh_bytes(out, magic, 8);
    152 
    153   if (symbol_index) {
    154     char hdr[60];
    155     char name_field[16];
    156     size_t j;
    157     uint64_t cur_offset;
    158 
    159     for (j = 0; j < 16; ++j) name_field[j] = ' ';
    160     name_field[0] = '/';
    161     ar_fill_header(hdr, name_field, epoch, index_payload);
    162     wh_bytes(out, hdr, 60);
    163 
    164     wh_be32(out, nsyms);
    165 
    166     cur_offset = (uint64_t)8 + index_total + longtab_total;
    167     if (msyms) {
    168       for (i = 0; i < nmembers; ++i) {
    169         u32 k;
    170         for (k = 0; k < msyms[i].count; ++k) {
    171           wh_be32(out, (u32)cur_offset);
    172         }
    173         cur_offset += ar_member_padded_size(members[i].bytes.len);
    174       }
    175     }
    176 
    177     if (msyms) {
    178       for (i = 0; i < nmembers; ++i) {
    179         u32 k;
    180         for (k = 0; k < msyms[i].count; ++k) {
    181           Slice nm = msyms[i].names[k];
    182           static const char nul = '\0';
    183           wh_bytes(out, nm.s, nm.len);
    184           wh_bytes(out, &nul, 1);
    185         }
    186       }
    187     }
    188 
    189     if (index_payload & 1) wh_bytes(out, &pad, 1);
    190   }
    191 
    192   if (longtab_size) {
    193     char hdr[60];
    194     char name_field[16];
    195     size_t j;
    196     for (j = 0; j < 16; ++j) name_field[j] = ' ';
    197     name_field[0] = '/';
    198     name_field[1] = '/';
    199     ar_fill_header(hdr, name_field, 0, longtab_size);
    200     wh_bytes(out, hdr, 60);
    201     for (i = 0; i < nmembers; ++i) {
    202       Slice base = ar_name_basename(members[i].name);
    203       if (ar_name_needs_longtable(base.s, base.len)) {
    204         wh_bytes(out, base.s, base.len);
    205         wh_bytes(out, "/\n", 2);
    206       }
    207     }
    208     if (longtab_size & 1) wh_bytes(out, &pad, 1);
    209   }
    210 
    211   {
    212     uint64_t longtab_off = 0;
    213     for (i = 0; i < nmembers; ++i) {
    214       const KitArInput* m = &members[i];
    215       Slice base = ar_name_basename(m->name);
    216       char hdr[60];
    217       char name_field[16];
    218       size_t j;
    219 
    220       for (j = 0; j < 16; ++j) name_field[j] = ' ';
    221       if (long_names && ar_name_needs_longtable(base.s, base.len)) {
    222         name_field[0] = '/';
    223         wh_ar_num(name_field + 1, 15, longtab_off);
    224         longtab_off += (uint64_t)base.len + 2;
    225       } else {
    226         size_t emit = base.len > 15 ? 15 : base.len;
    227         for (j = 0; j < emit; ++j) name_field[j] = base.s[j];
    228         name_field[emit] = '/';
    229       }
    230 
    231       ar_fill_header(hdr, name_field, epoch, (uint64_t)m->bytes.len);
    232       wh_bytes(out, hdr, 60);
    233       if (m->bytes.data && m->bytes.len)
    234         wh_bytes(out, m->bytes.data, m->bytes.len);
    235       if (m->bytes.len & 1) wh_bytes(out, &pad, 1);
    236     }
    237   }
    238 
    239   return kit_writer_status(out);
    240 }
    241 
    242 /* ============================================================
    243  * Read (iterator) and list
    244  * ============================================================ */
    245 
    246 #define AR_NAMEBUF 256
    247 
    248 struct KitArIter {
    249   const KitContext* ctx;
    250   const u8* p;
    251   const u8* end;
    252   const u8* longnames;
    253   size_t longnames_len;
    254   char namebuf[AR_NAMEBUF];
    255 };
    256 
    257 static size_t ar_resolve_longname(KitArIter* it, uint64_t off) {
    258   size_t i;
    259   if (!it->longnames) return 0;
    260   if (off >= it->longnames_len) return 0;
    261   for (i = 0; i + 1 < sizeof(it->namebuf); ++i) {
    262     size_t k = (size_t)off + i;
    263     char ch;
    264     if (k >= it->longnames_len) break;
    265     ch = (char)it->longnames[k];
    266     if (ch == '/' || ch == '\n') break;
    267     it->namebuf[i] = ch;
    268   }
    269   it->namebuf[i] = '\0';
    270   return i;
    271 }
    272 
    273 KitStatus kit_ar_iter_new(const KitContext* ctx, const KitSlice* archive,
    274                           KitArIter** out) {
    275   Heap* h;
    276   KitArIter* it;
    277   if (!out) return KIT_INVALID;
    278   if (!ctx || !ctx->heap || !archive) return KIT_INVALID;
    279   if (!archive->data && archive->len) return KIT_INVALID;
    280   if (kit_detect_fmt(archive->data, archive->len) != KIT_BIN_AR)
    281     return KIT_MALFORMED;
    282   h = ctx->heap;
    283   it = (KitArIter*)h->alloc(h, sizeof(*it), _Alignof(KitArIter));
    284   if (!it) return KIT_NOMEM;
    285   it->ctx = ctx;
    286   it->p = archive->data + 8;
    287   it->end = archive->data + archive->len;
    288   it->longnames = NULL;
    289   it->longnames_len = 0;
    290   it->namebuf[0] = '\0';
    291   *out = it;
    292   return KIT_OK;
    293 }
    294 
    295 KitIterResult kit_ar_iter_next(KitArIter* it, KitArMember* out) {
    296   if (!it || !out) return KIT_ITER_ERROR;
    297   for (;;) {
    298     uint64_t size;
    299     size_t avail;
    300     int j;
    301     int namelen;
    302     char name_field[16];
    303 
    304     if (it->p + 60 > it->end) return KIT_ITER_END;
    305 
    306     for (j = 0; j < 16; ++j) name_field[j] = (char)it->p[j];
    307 
    308     size = 0;
    309     for (j = 48; j < 58; ++j) {
    310       char ch = (char)it->p[j];
    311       if (ch < '0' || ch > '9') break;
    312       size = size * 10 + (uint64_t)(unsigned char)(ch - '0');
    313     }
    314 
    315     it->p += 60;
    316     avail = (size_t)(it->end - it->p);
    317     if ((uint64_t)avail < size) return KIT_ITER_END;
    318 
    319     if (name_field[0] == '/' && name_field[1] == '/') {
    320       it->longnames = it->p;
    321       it->longnames_len = (size_t)size;
    322       goto advance;
    323     }
    324     if (name_field[0] == '/' && name_field[1] == ' ') {
    325       goto advance;
    326     }
    327 
    328     if (name_field[0] == '/' && name_field[1] >= '0' && name_field[1] <= '9') {
    329       uint64_t off = 0;
    330       for (j = 1; j < 16; ++j) {
    331         char ch = name_field[j];
    332         if (ch < '0' || ch > '9') break;
    333         off = off * 10 + (uint64_t)(unsigned char)(ch - '0');
    334       }
    335       namelen = (int)ar_resolve_longname(it, off);
    336     } else if (name_field[0] == '#' && name_field[1] == '1' &&
    337                name_field[2] == '/') {
    338       uint64_t nlen = 0;
    339       size_t k;
    340       for (j = 3; j < 16; ++j) {
    341         char ch = name_field[j];
    342         if (ch < '0' || ch > '9') break;
    343         nlen = nlen * 10 + (uint64_t)(unsigned char)(ch - '0');
    344       }
    345       if (nlen > size || nlen + 1 > sizeof(it->namebuf)) return KIT_ITER_ERROR;
    346       namelen = 0;
    347       for (k = 0; k < (size_t)nlen; ++k) {
    348         char ch = (char)it->p[k];
    349         if (ch == '\0') break;
    350         it->namebuf[namelen++] = ch;
    351       }
    352       it->namebuf[namelen] = '\0';
    353       it->p += (size_t)nlen;
    354       size -= nlen;
    355     } else {
    356       namelen = 0;
    357       for (j = 0; j < 16; ++j) {
    358         char ch = name_field[j];
    359         if (ch == '/' || ch == ' ' || ch == '\0') break;
    360         it->namebuf[namelen++] = ch;
    361       }
    362       it->namebuf[namelen] = '\0';
    363     }
    364 
    365     out->name.s = it->namebuf;
    366     out->name.len = namelen;
    367     out->data = it->p;
    368     out->size = (size_t)size;
    369 
    370     it->p += (size_t)size;
    371     if ((size & 1) && it->p < it->end) it->p++;
    372 
    373     if (it->namebuf[0] == '_' && it->namebuf[1] == '_' &&
    374         it->namebuf[2] == '.') {
    375       continue;
    376     }
    377     if (namelen > 0) return KIT_ITER_ITEM;
    378     continue;
    379 
    380   advance:
    381     it->p += (size_t)size;
    382     if ((size & 1) && it->p < it->end) it->p++;
    383   }
    384 }
    385 
    386 void kit_ar_iter_free(KitArIter* it) {
    387   Heap* h;
    388   if (!it) return;
    389   h = it->ctx->heap;
    390   h->free(h, it, sizeof(*it));
    391 }
    392 
    393 /* ============================================================
    394  * Symbol-index automation
    395  * ============================================================ */
    396 
    397 /* The owned block is laid out as
    398  *   [size_t alloc_sz][KitSlice names[count]][NUL-separated name bytes]
    399  * so the exact size for the size-tracking heap free travels with the block.
    400  * *out_owned points at the header; out_syms->names points past it. */
    401 #define AR_SYMBLOB_HDR_BYTES \
    402   ((sizeof(size_t) + _Alignof(KitSlice) - 1u) & ~(_Alignof(KitSlice) - 1u))
    403 
    404 KitStatus kit_obj_global_syms(const KitContext* ctx, const KitSlice* obj_bytes,
    405                               KitArMemberSymbols* out_syms, void** out_owned) {
    406   Heap* h;
    407   KitObjFile* of = NULL;
    408   KitObjSymIter* it = NULL;
    409   KitObjSymInfo si;
    410   u32 count = 0;
    411   size_t name_bytes = 0;
    412   size_t alloc_sz;
    413   char* blob;
    414   KitSlice* name_arr;
    415   char* name_storage;
    416   size_t cursor = 0;
    417 
    418   if (!out_syms || !out_owned) return KIT_INVALID;
    419   out_syms->names = NULL;
    420   out_syms->count = 0;
    421   *out_owned = NULL;
    422   if (!ctx || !ctx->heap || !obj_bytes) return KIT_INVALID;
    423   h = ctx->heap;
    424 
    425   if (kit_obj_open(ctx, KIT_SLICE_NULL, obj_bytes, &of) != KIT_OK) {
    426     /* Not a recognized object — the empty set, not an error. */
    427     return KIT_OK;
    428   }
    429 
    430   /* Pass A: count + measure name bytes. */
    431   if (kit_obj_symiter_new(of, &it) != KIT_OK) {
    432     kit_obj_free(of);
    433     return KIT_NOMEM;
    434   }
    435   for (;;) {
    436     KitIterResult r = kit_obj_symiter_next(it, &si);
    437     if (r != KIT_ITER_ITEM) break;
    438     if (si.bind != KIT_SB_GLOBAL) continue;
    439     if (si.section == KIT_SECTION_NONE) continue;
    440     if (!si.name.len) continue;
    441     count += 1;
    442     name_bytes += si.name.len + 1; /* +NUL */
    443   }
    444   kit_obj_symiter_free(it);
    445 
    446   if (count == 0) {
    447     kit_obj_free(of);
    448     return KIT_OK;
    449   }
    450 
    451   alloc_sz = AR_SYMBLOB_HDR_BYTES + (size_t)count * sizeof(KitSlice) + name_bytes;
    452   blob = (char*)h->alloc(h, alloc_sz, _Alignof(KitSlice));
    453   if (!blob) {
    454     kit_obj_free(of);
    455     return KIT_NOMEM;
    456   }
    457   *(size_t*)(void*)blob = alloc_sz;
    458   name_arr = (KitSlice*)(void*)(blob + AR_SYMBLOB_HDR_BYTES);
    459   name_storage = (char*)name_arr + (size_t)count * sizeof(KitSlice);
    460 
    461   /* Pass B: copy names. */
    462   if (kit_obj_symiter_new(of, &it) != KIT_OK) {
    463     h->free(h, blob, alloc_sz);
    464     kit_obj_free(of);
    465     return KIT_NOMEM;
    466   }
    467   {
    468     u32 k = 0;
    469     for (;;) {
    470       KitIterResult r;
    471       char* dst;
    472       size_t j;
    473       if (k >= count) break;
    474       r = kit_obj_symiter_next(it, &si);
    475       if (r != KIT_ITER_ITEM) break;
    476       if (si.bind != KIT_SB_GLOBAL) continue;
    477       if (si.section == KIT_SECTION_NONE) continue;
    478       if (!si.name.len) continue;
    479       dst = name_storage + cursor;
    480       name_arr[k].s = dst;
    481       name_arr[k].len = si.name.len;
    482       for (j = 0; j < si.name.len; ++j) *dst++ = si.name.s[j];
    483       *dst++ = '\0';
    484       cursor = (size_t)(dst - name_storage);
    485       k++;
    486     }
    487     count = k;
    488   }
    489   kit_obj_symiter_free(it);
    490   kit_obj_free(of);
    491 
    492   out_syms->names = name_arr;
    493   out_syms->count = count;
    494   *out_owned = blob;
    495   return KIT_OK;
    496 }
    497 
    498 void kit_obj_global_syms_free(const KitContext* ctx, void* owned) {
    499   Heap* h;
    500   size_t alloc_sz;
    501   if (!ctx || !ctx->heap || !owned) return;
    502   h = ctx->heap;
    503   alloc_sz = *(const size_t*)owned;
    504   h->free(h, owned, alloc_sz);
    505 }
    506 
    507 /* Serialize one builder into a fresh in-memory writer; the returned bytes
    508  * alias that writer's buffer, so the writer must stay open until the bytes
    509  * have been consumed. */
    510 static KitStatus ar_emit_member(const KitContext* ctx, KitObjBuilder* b,
    511                                 KitSlice name, KitWriter** w_out,
    512                                 KitSlice* bytes_out) {
    513   const uint8_t* bytes;
    514   size_t len = 0;
    515   (void)name;
    516   if (kit_writer_mem(ctx->heap, w_out) != KIT_OK) return KIT_NOMEM;
    517   if (kit_obj_builder_emit(b, *w_out) != KIT_OK ||
    518       kit_writer_status(*w_out) != KIT_OK)
    519     return KIT_ERR;
    520   bytes = kit_writer_mem_bytes(*w_out, &len);
    521   bytes_out->s = (const char*)bytes;
    522   bytes_out->len = len;
    523   return KIT_OK;
    524 }
    525 
    526 /* Shared tail of kit_ar_write_objs / kit_ar_reindex: given member name/byte
    527  * slices already laid out in `members`, compute each member's symbol index and
    528  * write the long-names + symbol-index archive. */
    529 static KitStatus ar_write_with_index(const KitContext* ctx, KitArInput* members,
    530                                      u32 n, u64 epoch, KitWriter* out) {
    531   Heap* h = ctx->heap;
    532   KitArMemberSymbols* msyms = NULL;
    533   void** owned = NULL;
    534   KitArWriteOptions opts = {0};
    535   KitStatus st = KIT_OK;
    536   u32 i;
    537 
    538   if (n) {
    539     msyms = (KitArMemberSymbols*)h->alloc(h, (size_t)n * sizeof(*msyms),
    540                                           _Alignof(KitArMemberSymbols));
    541     owned = (void**)h->alloc(h, (size_t)n * sizeof(*owned), _Alignof(void*));
    542     if (!msyms || !owned) {
    543       st = KIT_NOMEM;
    544       goto out;
    545     }
    546     for (i = 0; i < n; ++i) {
    547       msyms[i].names = NULL;
    548       msyms[i].count = 0;
    549       owned[i] = NULL;
    550     }
    551     for (i = 0; i < n; ++i) {
    552       st = kit_obj_global_syms(ctx, &members[i].bytes, &msyms[i], &owned[i]);
    553       if (st != KIT_OK) goto out;
    554     }
    555   }
    556 
    557   opts.epoch = epoch;
    558   opts.long_names = 1;
    559   opts.symbol_index = 1;
    560   opts.member_symbols = msyms;
    561   st = kit_ar_write(out, members, n, &opts);
    562   if (st == KIT_OK && kit_writer_status(out) != KIT_OK) st = KIT_ERR;
    563 
    564 out:
    565   if (owned) {
    566     for (i = 0; i < n; ++i)
    567       if (owned[i]) kit_obj_global_syms_free(ctx, owned[i]);
    568     h->free(h, owned, (size_t)n * sizeof(*owned));
    569   }
    570   if (msyms) h->free(h, msyms, (size_t)n * sizeof(*msyms));
    571   return st;
    572 }
    573 
    574 KitStatus kit_ar_write_objs(const KitContext* ctx, KitObjBuilder* const* objs,
    575                             const KitSlice* names, uint32_t n, uint64_t epoch,
    576                             KitWriter* out) {
    577   Heap* h;
    578   KitArInput* members = NULL;
    579   KitWriter** memw = NULL;
    580   KitStatus st = KIT_OK;
    581   u32 i;
    582 
    583   if (!ctx || !ctx->heap || !out) return KIT_INVALID;
    584   if (n && (!objs || !names)) return KIT_INVALID;
    585   h = ctx->heap;
    586 
    587   if (n) {
    588     members =
    589         (KitArInput*)h->alloc(h, (size_t)n * sizeof(*members), _Alignof(KitArInput));
    590     memw = (KitWriter**)h->alloc(h, (size_t)n * sizeof(*memw), _Alignof(KitWriter*));
    591     if (!members || !memw) {
    592       st = KIT_NOMEM;
    593       goto out;
    594     }
    595     for (i = 0; i < n; ++i) {
    596       members[i].name = names[i];
    597       members[i].bytes = KIT_SLICE_NULL;
    598       memw[i] = NULL;
    599     }
    600     /* Serialize every builder; the member bytes alias each writer's buffer, so
    601      * the writers stay open until ar_write_with_index has consumed them. */
    602     for (i = 0; i < n; ++i) {
    603       st = ar_emit_member(ctx, objs[i], names[i], &memw[i], &members[i].bytes);
    604       if (st != KIT_OK) goto out;
    605     }
    606   }
    607 
    608   st = ar_write_with_index(ctx, members, n, epoch, out);
    609 
    610 out:
    611   if (memw) {
    612     for (i = 0; i < n; ++i)
    613       if (memw[i]) kit_writer_close(memw[i]);
    614     h->free(h, memw, (size_t)n * sizeof(*memw));
    615   }
    616   if (members) h->free(h, members, (size_t)n * sizeof(*members));
    617   return st;
    618 }
    619 
    620 KitStatus kit_ar_reindex(const KitContext* ctx, const KitSlice* archive_bytes,
    621                          uint64_t epoch, KitWriter* out) {
    622   Heap* h;
    623   KitArIter* it = NULL;
    624   KitArMember m;
    625   KitArInput* members = NULL;
    626   char* name_storage = NULL;
    627   size_t name_bytes_total = 0;
    628   u32 nmembers = 0;
    629   KitStatus st = KIT_OK;
    630 
    631   if (!ctx || !ctx->heap || !archive_bytes || !out) return KIT_INVALID;
    632   h = ctx->heap;
    633 
    634   /* Pass 1: count members + total name bytes (member names alias an internal
    635    * buffer overwritten on each next(), so a stable copy is stashed). */
    636   if (kit_ar_iter_new(ctx, archive_bytes, &it) != KIT_OK) return KIT_MALFORMED;
    637   for (;;) {
    638     KitIterResult r = kit_ar_iter_next(it, &m);
    639     if (r != KIT_ITER_ITEM) break;
    640     nmembers++;
    641     name_bytes_total += m.name.len + 1;
    642   }
    643   kit_ar_iter_free(it);
    644   it = NULL;
    645 
    646   if (nmembers == 0) {
    647     /* Empty archive: still emit an empty symbol index (matches GNU ranlib). */
    648     return ar_write_with_index(ctx, NULL, 0, epoch, out);
    649   }
    650 
    651   members =
    652       (KitArInput*)h->alloc(h, (size_t)nmembers * sizeof(*members),
    653                             _Alignof(KitArInput));
    654   if (!members) return KIT_NOMEM;
    655   if (name_bytes_total) {
    656     name_storage = (char*)h->alloc(h, name_bytes_total, 1);
    657     if (!name_storage) {
    658       h->free(h, members, (size_t)nmembers * sizeof(*members));
    659       return KIT_NOMEM;
    660     }
    661   }
    662 
    663   /* Pass 2: copy names and member byte-spans into the parallel arrays. */
    664   if (kit_ar_iter_new(ctx, archive_bytes, &it) != KIT_OK) {
    665     st = KIT_MALFORMED;
    666     goto out;
    667   }
    668   {
    669     size_t cursor = 0;
    670     u32 k = 0;
    671     while (k < nmembers) {
    672       KitIterResult r = kit_ar_iter_next(it, &m);
    673       char* dst;
    674       size_t j;
    675       if (r != KIT_ITER_ITEM) break;
    676       dst = name_storage + cursor;
    677       for (j = 0; j < m.name.len; ++j) *dst++ = m.name.s[j];
    678       *dst++ = '\0';
    679       members[k].name.s = name_storage + cursor;
    680       members[k].name.len = m.name.len;
    681       members[k].bytes.data = m.data;
    682       members[k].bytes.len = m.size;
    683       cursor = (size_t)(dst - name_storage);
    684       k++;
    685     }
    686   }
    687   kit_ar_iter_free(it);
    688   it = NULL;
    689 
    690   st = ar_write_with_index(ctx, members, nmembers, epoch, out);
    691 
    692 out:
    693   if (it) kit_ar_iter_free(it);
    694   if (name_storage) h->free(h, name_storage, name_bytes_total);
    695   if (members) h->free(h, members, (size_t)nmembers * sizeof(*members));
    696   return st;
    697 }
    698 
    699 KitStatus kit_ar_list(const KitSlice* archive, KitWriter* out) {
    700   /* Iter API requires a context; emulate locally without heap allocation. */
    701   struct KitArIter local;
    702   KitArMember m;
    703 
    704   if (!out) return KIT_INVALID;
    705   if (!archive) return KIT_INVALID;
    706   if (kit_detect_fmt(archive->data, archive->len) != KIT_BIN_AR)
    707     return KIT_MALFORMED;
    708   local.ctx = NULL;
    709   local.p = archive->data + 8;
    710   local.end = archive->data + archive->len;
    711   local.longnames = NULL;
    712   local.longnames_len = 0;
    713   local.namebuf[0] = '\0';
    714 
    715   for (;;) {
    716     KitIterResult r = kit_ar_iter_next(&local, &m);
    717     if (r != KIT_ITER_ITEM) break;
    718     wh_bytes(out, m.name.s, m.name.len);
    719     wh_nl(out);
    720   }
    721 
    722   return kit_writer_status(out);
    723 }