File Coverage

Archive.xs
Criterion Covered Total %
statement 880 1443 60.9
branch 530 1314 40.3
condition n/a
subroutine n/a
pod n/a
total 1410 2757 51.1


line stmt bran cond sub pod time code
1             /*
2             * Archive.xs - File::Raw::Archive XS shim.
3             *
4             * Registers the built-in tar plugin at BOOT. Exposes thin XSUBs that
5             * the Perl-facing layer (File/Raw/Archive.pm) drives:
6             * _open_read, _read_next, _read_data, _read_close
7             * _open_write, _write_add, _write_close
8             * _extract_to_fd, _apply_xattrs
9             * _list_plugins, _probe
10             *
11             * Higher-level convenience (each, list, extract, extract_all, pack)
12             * lives in pure Perl over these primitives.
13             */
14              
15             #define PERL_NO_GET_CONTEXT
16             #include "EXTERN.h"
17             #include "perl.h"
18             #include "XSUB.h"
19              
20             #include "archive_plugin.h"
21             #include "arch_io.h"
22             #include "tar.h"
23             #include "extract.h"
24             #include "marshal.h"
25              
26             #include
27             #include
28             #include
29             #include
30             #include
31             #include
32             #include
33             #include
34             #include
35             #include
36              
37             #define ARCHIVE_PATH_MAX 4096
38              
39             /* ============================================================
40             * Object slot layout
41             * ============================================================
42             *
43             * Reader / Writer / Entry are blessed AVs (Object::Proto pattern).
44             * Compile-time slot indices replace runtime hash-key lookups, which
45             * is faster and keeps every per-entry access in C to a pointer + add.
46             *
47             * Reader AV: [ handle, consumed, closed, cur_entry ]
48             * Writer AV: [ handle, closed ]
49             * Entry AV: [ reader, meta, slurped ]
50             * Meta AV: [ name, size, mode, mtime, mtime_ns, uid, gid, type,
51             * link_target, xattrs, is_sparse ]
52             *
53             * `handle` is a blessed-IV-ref to archive_handle_t* (class
54             * File::Raw::Archive::Reader or ::Writer); its DESTROY frees the C
55             * struct via maybe_free_handle. `meta` is a plain RV-AV. `xattrs`
56             * stays a hashref because callers index into it by arbitrary key.
57             */
58              
59             #define R_HANDLE 0
60             #define R_CONSUMED 1
61             #define R_CLOSED 2
62             #define R_CUR_ENTRY 3
63             #define R_SLOT_COUNT 4
64              
65             #define W_HANDLE 0
66             #define W_CLOSED 1
67             #define W_SLOT_COUNT 2
68              
69             #define E_READER 0
70             #define E_META 1
71             #define E_SLURPED 2
72             #define E_SLOT_COUNT 3
73              
74             #define M_NAME 0
75             #define M_SIZE 1
76             #define M_MODE 2
77             #define M_MTIME 3
78             #define M_MTIME_NS 4
79             #define M_UID 5
80             #define M_GID 6
81             #define M_TYPE 7
82             #define M_LINK_TARGET 8
83             #define M_XATTRS 9
84             #define M_IS_SPARSE 10
85             #define M_SLOT_COUNT 11
86              
87             #ifndef XS_EXTERNAL
88             #define XS_EXTERNAL(name) XS(name)
89             #endif
90              
91             /* ============================================================
92             * Reader / writer state
93             * ============================================================ */
94              
95             typedef struct {
96             int fd;
97             archive_fd_state_t fd_state;
98             archive_gz_src_t *gz_src;
99             archive_gz_sink_t *gz_sink;
100             const ArchivePlugin *plugin;
101             void *cursor;
102             int is_writer;
103             int closed;
104             /* The most recently emitted entry from Reader::next, kept here so
105             * Reader::next can drain its payload before advancing. Owned by
106             * the handle (refcount +1); released on next/close. */
107             SV *cur_entry_rv;
108             int consumed; /* current entry's payload drained? */
109             } archive_handle_t;
110              
111             static archive_handle_t *
112 108           make_handle(void)
113             {
114 108           archive_handle_t *h = (archive_handle_t *)calloc(1, sizeof *h);
115 108 50         if (h) h->fd = -1;
116 108           return h;
117             }
118              
119             static void
120 108           free_handle(pTHX_ archive_handle_t *h)
121             {
122 108 50         if (!h || h->closed) return;
    50          
123 108           h->closed = 1;
124 108 50         if (h->cur_entry_rv) {
125 0           SvREFCNT_dec(h->cur_entry_rv);
126 0           h->cur_entry_rv = NULL;
127             }
128 108 50         if (h->plugin && h->cursor) {
    50          
129 108 100         if (h->is_writer) {
130 39 50         if (h->plugin->write_close)
131 39           h->plugin->write_close(aTHX_ h->plugin, h->cursor);
132             } else {
133 69 50         if (h->plugin->read_close)
134 69           h->plugin->read_close(aTHX_ h->plugin, h->cursor);
135             }
136             }
137 108 100         if (h->gz_src) archive_gz_src_free(h->gz_src);
138 108 100         if (h->gz_sink) {
139 4           archive_gz_sink_finish(h->gz_sink);
140 4           archive_gz_sink_free(h->gz_sink);
141             }
142 108 50         if (h->fd >= 0) close(h->fd);
143 108           free(h);
144             }
145              
146             /* Forward decls: defined further down but used by open_reader below. */
147             static int sniff_gzip(int fd);
148             static int resolve_compression(pTHX_ int fd, HV *opts);
149             static const ArchivePlugin *resolve_plugin(pTHX_ HV *opts, int fd, int gz);
150              
151             /* Common reader-open helper used by every top-level path-taking
152             * method. Mirrors what _open_read does but factored for reuse from
153             * list/extract/extract_all/each. Returns archive_handle_t* on success
154             * or NULL after croaking. */
155             static archive_handle_t *
156 70           open_reader(pTHX_ SV *path_sv, HV *opts)
157             {
158             archive_handle_t *h;
159             int gz, fd;
160             const ArchivePlugin *plugin;
161             archive_pull_fn pull;
162             void *src;
163              
164 70           fd = open(SvPV_nolen(path_sv), O_RDONLY);
165 70 100         if (fd < 0) croak("File::Raw::Archive: cannot open '%s': %s",
166             SvPV_nolen(path_sv), strerror(errno));
167              
168 69           gz = resolve_compression(aTHX_ fd, opts);
169 69           plugin = resolve_plugin(aTHX_ opts, fd, gz);
170 69 50         if (!plugin) {
171 0           close(fd);
172 0           croak("File::Raw::Archive: no plugin selected (auto-detection failed)");
173             }
174              
175 69           h = make_handle();
176 69 50         if (!h) { close(fd); croak("File::Raw::Archive: out of memory"); }
177 69           h->fd = fd;
178 69           h->plugin = plugin;
179 69           h->consumed = 1; /* no entry yet -> nothing to drain */
180              
181 69 100         if (gz) {
182 8           h->gz_src = archive_gz_src_new(fd, ARCHIVE_DEFAULT_CHUNK);
183 8 50         if (!h->gz_src) {
184 0           free_handle(aTHX_ h);
185 0           croak("File::Raw::Archive: gzip stream init failed");
186             }
187 8           pull = archive_pull_gz;
188 8           src = h->gz_src;
189             } else {
190 61           h->fd_state.fd = fd;
191 61           pull = archive_pull_fd;
192 61           src = &h->fd_state;
193             }
194              
195 69 50         if (plugin->read_open(aTHX_ plugin, pull, src, opts, &h->cursor) < 0) {
196 0           free_handle(aTHX_ h);
197 0           croak("File::Raw::Archive: read_open failed");
198             }
199 69           return h;
200             }
201              
202             /* Drain the current entry's payload bytes if not already consumed.
203             * Called between read_next invocations and during close. */
204             static void
205 0           drain_current_entry(pTHX_ archive_handle_t *h)
206             {
207 0 0         if (h->consumed) return;
208             char buf[16 * 1024];
209             int n;
210 0           while ((n = h->plugin->read_data(aTHX_ h->plugin, h->cursor,
211 0 0         buf, sizeof buf)) > 0) {}
212 0           h->consumed = 1;
213 0 0         if (n < 0) croak("File::Raw::Archive: read_data failed");
214             }
215              
216             /* Common writer-open helper. */
217             static archive_handle_t *
218 40           open_writer(pTHX_ SV *path_sv, HV *opts)
219             {
220             archive_handle_t *h;
221 40           int gz = 0, fd;
222             const ArchivePlugin *plugin;
223             archive_push_fn push;
224             void *sink;
225              
226 40 50         if (opts) {
227 40           SV **sv = hv_fetchs(opts, "compression", 0);
228 40 100         if (sv && *sv && SvOK(*sv)) {
    50          
    50          
229             STRLEN nn;
230 4           const char *pp = SvPV(*sv, nn);
231 4 50         if (nn == 4 && memcmp(pp, "gzip", 4) == 0) gz = 1;
    50          
232 0 0         else if (nn == 4 && memcmp(pp, "auto", 4) == 0) {
    0          
233             STRLEN pl;
234 0           const char *pp2 = SvPV(path_sv, pl);
235 0 0         if (pl >= 3 && memcmp(pp2 + pl - 3, ".gz", 3) == 0) gz = 1;
    0          
236             }
237             }
238             }
239              
240 40           fd = open(SvPV_nolen(path_sv), O_WRONLY | O_CREAT | O_TRUNC, 0644);
241 40 50         if (fd < 0) croak("File::Raw::Archive: cannot open '%s' for write: %s",
242             SvPV_nolen(path_sv), strerror(errno));
243              
244             {
245 40 50         SV **sv = opts ? hv_fetchs(opts, "plugin", 0) : NULL;
246 41 100         if (sv && *sv && SvOK(*sv)) {
    50          
    50          
247             STRLEN nn;
248 1           const char *pp = SvPV(*sv, nn);
249             char namebuf[64];
250 1 50         if (nn >= sizeof namebuf) {
251 0           close(fd);
252 0           croak("File::Raw::Archive: plugin name too long");
253             }
254 1           memcpy(namebuf, pp, nn);
255 1           namebuf[nn] = '\0';
256 1           plugin = archive_lookup_plugin(aTHX_ namebuf);
257             } else {
258 39           plugin = archive_lookup_plugin(aTHX_ "tar");
259             }
260             }
261 40 100         if (!plugin) { close(fd); croak("File::Raw::Archive: unknown plugin"); }
262              
263 39           h = make_handle();
264 39 50         if (!h) { close(fd); croak("File::Raw::Archive: out of memory"); }
265 39           h->fd = fd;
266 39           h->plugin = plugin;
267 39           h->is_writer = 1;
268 39           h->consumed = 1;
269              
270 39 100         if (gz) {
271 4           int level = 6;
272 4 50         if (opts) {
273 4           SV **lsv = hv_fetchs(opts, "level", 0);
274 4 100         if (lsv && *lsv && SvOK(*lsv)) level = (int)SvIV(*lsv);
    50          
    50          
275             }
276 4           h->gz_sink = archive_gz_sink_new(fd, ARCHIVE_DEFAULT_CHUNK, level);
277 4 50         if (!h->gz_sink) {
278 0           free_handle(aTHX_ h);
279 0           croak("File::Raw::Archive: gzip sink init failed");
280             }
281 4           push = archive_push_gz;
282 4           sink = h->gz_sink;
283             } else {
284 35           h->fd_state.fd = fd;
285 35           push = archive_push_fd;
286 35           sink = &h->fd_state;
287             }
288              
289 39 50         if (plugin->write_open(aTHX_ plugin, push, sink, opts, &h->cursor) < 0) {
290 0           free_handle(aTHX_ h);
291 0           croak("File::Raw::Archive: write_open failed (bad format option?)");
292             }
293 39           return h;
294             }
295              
296             /* ============================================================
297             * Helpers
298             * ============================================================ */
299              
300             /* Detect gzip magic 1f 8b at offset 0. */
301             static int
302 66           sniff_gzip(int fd)
303             {
304             unsigned char magic[2];
305 66           off_t pos = lseek(fd, 0, SEEK_CUR);
306 66 50         if (pos < 0) return 0;
307 66           ssize_t n = read(fd, magic, 2);
308 66           lseek(fd, pos, SEEK_SET);
309 66 50         if (n < 2) return 0;
310 66 100         return (magic[0] == 0x1f && magic[1] == 0x8b);
    50          
311             }
312              
313             /* Resolve compression option: returns 1 if gzip should be used. */
314             static int
315 69           resolve_compression(pTHX_ int fd, HV *opts)
316             {
317 69 50         if (!opts) return sniff_gzip(fd);
318 69           SV **sv = hv_fetchs(opts, "compression", 0);
319 69 100         if (!sv || !*sv || !SvOK(*sv)) return sniff_gzip(fd);
    50          
    50          
320             STRLEN n;
321 5           const char *p = SvPV(*sv, n);
322 5 50         if (n == 4 && memcmp(p, "auto", 4) == 0) return sniff_gzip(fd);
    100          
323 3 50         if (n == 4 && memcmp(p, "gzip", 4) == 0) return 1;
    100          
324 1 50         if (n == 4 && memcmp(p, "none", 4) == 0) return 0;
    50          
325 0           return sniff_gzip(fd);
326             }
327              
328             /* Resolve plugin option: returns plugin or NULL with error string. */
329             static const ArchivePlugin *
330 69           resolve_plugin(pTHX_ HV *opts, int fd, int gz)
331             {
332 69 50         SV **sv = opts ? hv_fetchs(opts, "plugin", 0) : NULL;
333 69 50         if (sv && *sv && SvOK(*sv)) {
    0          
    0          
334             STRLEN n;
335 0           const char *p = SvPV(*sv, n);
336 0 0         if (n == 4 && memcmp(p, "auto", 4) == 0) {
    0          
337             /* Probe by reading the first block. */
338             char probe_buf[512];
339 0           off_t pos = lseek(fd, 0, SEEK_CUR);
340 0 0         if (pos < 0) return NULL;
341             /* If gzip-wrapped, we'd need to inflate to probe. For now,
342             * default to tar when auto + gzip. */
343 0 0         if (gz) return archive_lookup_plugin(aTHX_ "tar");
344 0           ssize_t got = read(fd, probe_buf, sizeof probe_buf);
345 0           lseek(fd, pos, SEEK_SET);
346 0 0         if (got <= 0) return NULL;
347 0           return archive_probe_for(aTHX_ probe_buf, (size_t)got);
348             }
349             char namebuf[64];
350 0 0         if (n >= sizeof namebuf) return NULL;
351 0           memcpy(namebuf, p, n);
352 0           namebuf[n] = '\0';
353 0           return archive_lookup_plugin(aTHX_ namebuf);
354             }
355 69           return archive_lookup_plugin(aTHX_ "tar");
356             }
357              
358             /* Build a snapshot hashref from an ArchiveEntry. Used only by `list`
359             * for a friendlier AoH return shape. Internal Entry objects use AV
360             * slot indices (entry_to_av) for speed. */
361             static SV *
362 564           entry_to_hv_snapshot(pTHX_ const ArchiveEntry *e)
363             {
364 564           HV *h = newHV();
365 564 50         hv_stores(h, "name", newSVpvn(e->name ? e->name : "", e->name_len));
366 564           hv_stores(h, "size", newSVuv((UV)e->size));
367 564           hv_stores(h, "mode", newSVuv((UV)e->mode));
368 564           hv_stores(h, "mtime", newSVuv((UV)e->mtime));
369 564           hv_stores(h, "mtime_ns", newSVuv((UV)e->mtime_ns));
370 564           hv_stores(h, "uid", newSVuv((UV)e->uid));
371 564           hv_stores(h, "gid", newSVuv((UV)e->gid));
372 564           hv_stores(h, "type", newSViv((IV)e->type));
373 564           hv_stores(h, "is_sparse", newSViv(e->is_sparse));
374 564 50         if (e->link_target)
375 0           hv_stores(h, "link_target",
376             newSVpvn(e->link_target, e->link_target_len));
377 564 50         if (e->xattr_count > 0) {
378 0           HV *xh = newHV();
379             size_t i;
380 0 0         for (i = 0; i < e->xattr_count; i++) {
381 0           hv_store(xh, e->xattrs[i].key, (I32)e->xattrs[i].key_len,
382             newSVpvn(e->xattrs[i].value, e->xattrs[i].value_len), 0);
383             }
384 0           hv_stores(h, "xattrs", newRV_noinc((SV *)xh));
385             }
386 564           return newRV_noinc((SV *)h);
387             }
388              
389             /* Build a meta AV from an ArchiveEntry. Slot layout: M_NAME..M_IS_SPARSE.
390             * Returns RV-AV at refcount +1. Slots that aren't applicable (e.g.
391             * link_target on a regular file) are left empty rather than stored as
392             * undef so size() sees a missing slot and returns the default. */
393             static SV *
394 111           entry_to_av(pTHX_ const ArchiveEntry *e)
395             {
396 111           AV *meta = newAV();
397 111           av_extend(meta, M_SLOT_COUNT - 1);
398 111 50         av_store(meta, M_NAME,
399             newSVpvn(e->name ? e->name : "", e->name_len));
400 111           av_store(meta, M_SIZE, newSVuv((UV)e->size));
401 111           av_store(meta, M_MODE, newSVuv((UV)e->mode));
402 111           av_store(meta, M_MTIME, newSVuv((UV)e->mtime));
403 111           av_store(meta, M_MTIME_NS, newSVuv((UV)e->mtime_ns));
404 111           av_store(meta, M_UID, newSVuv((UV)e->uid));
405 111           av_store(meta, M_GID, newSVuv((UV)e->gid));
406 111           av_store(meta, M_TYPE, newSViv((IV)e->type));
407 111           av_store(meta, M_IS_SPARSE, newSViv(e->is_sparse));
408 111 100         if (e->link_target) {
409 4           av_store(meta, M_LINK_TARGET,
410             newSVpvn(e->link_target, e->link_target_len));
411             }
412 111 100         if (e->xattr_count > 0) {
413 3           HV *xh = newHV();
414             size_t i;
415 8 100         for (i = 0; i < e->xattr_count; i++) {
416 5           hv_store(xh, e->xattrs[i].key, (I32)e->xattrs[i].key_len,
417             newSVpvn(e->xattrs[i].value, e->xattrs[i].value_len), 0);
418             }
419 3           av_store(meta, M_XATTRS, newRV_noinc((SV *)xh));
420             }
421 111           return newRV_noinc((SV *)meta);
422             }
423              
424             /* Decode a Perl entry hashref into ArchiveEntry. The returned entry
425             * borrows pointers into SVs that must outlive the call - caller keeps
426             * the hash alive until write_add returns. */
427             static void
428 678           hv_to_entry(pTHX_ HV *h, ArchiveEntry *e,
429             ArchiveXattr *xattr_buf, size_t xattr_buf_n,
430             size_t *xattr_used)
431             {
432 678           memset(e, 0, sizeof *e);
433 678           *xattr_used = 0;
434              
435 678           SV **sv = hv_fetchs(h, "name", 0);
436 678 50         if (sv && *sv && SvOK(*sv)) {
    50          
    50          
437             STRLEN nl;
438 678           e->name = SvPV(*sv, nl);
439 678           e->name_len = nl;
440             }
441 678           sv = hv_fetchs(h, "type", 0);
442 678 50         if (sv && *sv && SvOK(*sv)) e->type = (int)SvIV(*sv);
    50          
    50          
443 0           else e->type = AE_FILE;
444              
445 678           sv = hv_fetchs(h, "mode", 0);
446 678 50         if (sv && *sv && SvOK(*sv)) e->mode = (uint32_t)SvUV(*sv);
    50          
    50          
447              
448 678           sv = hv_fetchs(h, "size", 0);
449 678 50         if (sv && *sv && SvOK(*sv)) e->size = (uint64_t)SvUV(*sv);
    0          
    0          
450              
451 678           sv = hv_fetchs(h, "mtime", 0);
452 678 100         if (sv && *sv && SvOK(*sv)) {
    50          
    50          
453 8 50         if (SvNOKp(*sv) || (SvPOKp(*sv) && strchr(SvPV_nolen(*sv), '.'))) {
    50          
    0          
454 0           NV nv = SvNV(*sv);
455 0           uint64_t whole = (uint64_t)nv;
456 0           double frac = nv - (double)whole;
457 0 0         if (frac < 0) frac = 0;
458 0           e->mtime = whole;
459 0           e->mtime_ns = (uint32_t)(frac * 1e9 + 0.5);
460 0 0         if (e->mtime_ns >= 1000000000U) {
461 0           e->mtime++;
462 0           e->mtime_ns -= 1000000000U;
463             }
464             } else {
465 8           e->mtime = (uint64_t)SvUV(*sv);
466             }
467             }
468 678           sv = hv_fetchs(h, "mtime_ns", 0);
469 678 100         if (sv && *sv && SvOK(*sv)) e->mtime_ns = (uint32_t)SvUV(*sv);
    50          
    50          
470              
471 678           sv = hv_fetchs(h, "uid", 0);
472 678 100         if (sv && *sv && SvOK(*sv)) e->uid = (uint32_t)SvUV(*sv);
    50          
    50          
473 678           sv = hv_fetchs(h, "gid", 0);
474 678 100         if (sv && *sv && SvOK(*sv)) e->gid = (uint32_t)SvUV(*sv);
    50          
    50          
475              
476 678           sv = hv_fetchs(h, "link_target", 0);
477 678 100         if (sv && *sv && SvOK(*sv)) {
    50          
    50          
478             STRLEN ll;
479 4           e->link_target = SvPV(*sv, ll);
480 4           e->link_target_len = ll;
481             }
482              
483 678           sv = hv_fetchs(h, "xattrs", 0);
484 678 100         if (sv && *sv && SvROK(*sv) && SvTYPE(SvRV(*sv)) == SVt_PVHV) {
    50          
    50          
    50          
485 3           HV *xh = (HV *)SvRV(*sv);
486 3           hv_iterinit(xh);
487             HE *he;
488 8 100         while ((he = hv_iternext(xh)) && *xattr_used < xattr_buf_n) {
    50          
489             I32 klen_i;
490 5           const char *k = hv_iterkey(he, &klen_i);
491 5           SV *v = hv_iterval(xh, he);
492             STRLEN vlen;
493 5           const char *vp = SvPV(v, vlen);
494 5           xattr_buf[*xattr_used].key = k;
495 5           xattr_buf[*xattr_used].key_len = (size_t)klen_i;
496 5           xattr_buf[*xattr_used].value = vp;
497 5           xattr_buf[*xattr_used].value_len = vlen;
498 5           (*xattr_used)++;
499             }
500 3           e->xattrs = xattr_buf;
501 3           e->xattr_count = *xattr_used;
502             }
503 678           }
504              
505             /* Bless a handle pointer into a class. */
506             static SV *
507 79           new_handle_obj(pTHX_ archive_handle_t *h, const char *cls)
508             {
509 79           SV *iv = newSViv(PTR2IV(h));
510 79           SV *rv = newRV_noinc(iv);
511 79           return sv_bless(rv, gv_stashpv(cls, GV_ADD));
512             }
513              
514             static archive_handle_t *
515 838           unwrap_handle(pTHX_ SV *sv)
516             {
517 838 100         if (!SvROK(sv) || !SvIOK(SvRV(sv))) return NULL;
    50          
518 837           return INT2PTR(archive_handle_t *, SvIV(SvRV(sv)));
519             }
520              
521             /* Idempotent close-and-free for a blessed-IV-ref handle SV. After the
522             * first call, the underlying IV is set to 0 so subsequent calls (e.g.
523             * from DESTROY after an explicit close) become no-ops without a double
524             * free. */
525             static void
526 158           maybe_free_handle(pTHX_ SV *handle_sv)
527             {
528 158 50         if (!handle_sv || !SvROK(handle_sv)) return;
    50          
529 158           SV *iv_sv = SvRV(handle_sv);
530 158 50         if (!SvIOK(iv_sv)) return;
531 158           archive_handle_t *h = INT2PTR(archive_handle_t *, SvIV(iv_sv));
532 158 100         if (!h) return;
533 79           free_handle(aTHX_ h);
534 79           sv_setiv(iv_sv, 0);
535             }
536              
537             /* Hash-key lookup helper. Returns the SV* at key, or NULL if not
538             * present / not defined. dTHX picks up the interpreter from
539             * PERL_GET_THX so callers don't have to thread pTHX_ through. */
540             static SV *
541 0           hv_get(HV *h, const char *key, I32 klen)
542             {
543             dTHX;
544 0           SV **p = hv_fetch(h, key, klen, 0);
545 0 0         if (!p || !*p || !SvOK(*p)) return NULL;
    0          
    0          
546 0           return *p;
547             }
548              
549             /* SAVEDESTRUCTOR_X target: closes a handle when the current dynamic
550             * scope unwinds, including via croak. Used by the new public top-
551             * level XSUBs that own the open + work + close lifecycle. */
552             static void
553 29           free_handle_destructor(pTHX_ void *data)
554             {
555 29           free_handle(aTHX_ (archive_handle_t *)data);
556 29           }
557              
558             /* Compile-time platform gate for parallel extract: needs fork(2)/
559             * pipe(2)/waitpid(2). Windows lacks them, so parallel=N silently
560             * falls back to sequential there. */
561             static int
562 1           parallel_supported(void)
563             {
564             #ifdef _WIN32
565             return 0;
566             #else
567 1           return 1;
568             #endif
569             }
570              
571             /* Build an opts HV from a variadic-style stack pointer. `args` should
572             * be the address of ST(start) at the call site so this function works
573             * outside an XSUB. The HV is mortalised. Caller must have validated
574             * that (count) is even. */
575             static HV *
576 110           build_opts_from_args(pTHX_ SV **args, int count)
577             {
578 110           HV *opts = newHV();
579 110           sv_2mortal((SV *)opts);
580             int i;
581 137 100         for (i = 0; i + 1 < count; i += 2) {
582             STRLEN klen;
583 27           const char *kp = SvPV(args[i], klen);
584 27           hv_store(opts, kp, klen, newSVsv(args[i + 1]), 0);
585             }
586 110           return opts;
587             }
588              
589             /* The shared sequential extract-all loop. Croaks on error; caller is
590             * responsible for handle lifetime via SAVEDESTRUCTOR_X. */
591             static void
592 10           do_extract_all_seq(pTHX_ archive_handle_t *h,
593             const char *dest, size_t dest_len,
594             int apply_xattrs, int unsafe_paths,
595             SV *filter_sv)
596             {
597             char path_buf[ARCHIVE_PATH_MAX];
598             char skip_buf[16 * 1024];
599             int has_filter;
600              
601 10 50         if (!h || h->is_writer || h->closed)
    50          
    50          
602 0           croak("File::Raw::Archive::extract_all: invalid handle");
603 10 50         if (dest_len == 0 || dest_len >= sizeof path_buf - 2)
    50          
604 0           croak("File::Raw::Archive::extract_all: bad dest length");
605              
606 10 50         has_filter = (filter_sv && SvOK(filter_sv) && SvROK(filter_sv) &&
    0          
    0          
607 0 0         SvTYPE(SvRV(filter_sv)) == SVt_PVCV);
608              
609 10 50         if (archive_mkpath(dest, 0755) < 0) {
610 0           croak("File::Raw::Archive::extract_all: cannot create dest '%s': %s",
611             dest, strerror(errno));
612             }
613              
614 85           for (;;) {
615             ArchiveEntry e;
616 95           int rc = h->plugin->read_next(aTHX_ h->plugin, h->cursor, &e);
617 95 50         if (rc < 0) croak("File::Raw::Archive: malformed archive");
618 95 100         if (rc == 0) break;
619              
620 87 50         if (!unsafe_paths) {
621 87 100         if (!archive_path_is_safe(e.name, e.name_len)) {
622 2           croak("File::Raw::Archive::extract_all: refusing unsafe path '%.*s'",
623             (int)e.name_len, e.name);
624             }
625             }
626              
627 85 50         if (has_filter) {
628 0           int keep = 1;
629 0           dSP;
630 0           ENTER;
631 0           SAVETMPS;
632 0 0         PUSHMARK(SP);
633 0           SV *entry_rv = entry_to_av(aTHX_ &e);
634 0           sv_2mortal(entry_rv);
635 0 0         XPUSHs(entry_rv);
636 0           PUTBACK;
637 0           int count = call_sv(filter_sv, G_SCALAR);
638 0           SPAGAIN;
639 0 0         if (count >= 1) {
640 0           SV *r = POPs;
641 0           keep = SvTRUE(r) ? 1 : 0;
642             }
643 0           PUTBACK;
644 0 0         FREETMPS;
645 0           LEAVE;
646 0 0         if (!keep) {
647             int n;
648 0           while ((n = h->plugin->read_data(aTHX_ h->plugin, h->cursor,
649 0 0         skip_buf, sizeof skip_buf)) > 0) {}
650 0 0         if (n < 0) croak("File::Raw::Archive: read_data failed");
651 0           continue;
652             }
653             }
654              
655 85 50         if (dest_len + 1 + e.name_len + 1 > sizeof path_buf) {
656 0           croak("File::Raw::Archive::extract_all: path too long for '%.*s'",
657             (int)e.name_len, e.name);
658             }
659 85           memcpy(path_buf, dest, dest_len);
660 85           path_buf[dest_len] = '/';
661 85           memcpy(path_buf + dest_len + 1, e.name, e.name_len);
662 85           path_buf[dest_len + 1 + e.name_len] = '\0';
663              
664 85           switch (e.type) {
665 2           case AE_DIR:
666 2 50         if (archive_extract_dir(path_buf, e.mode) < 0) {
667 0           croak("File::Raw::Archive::extract_all: mkdir '%s': %s",
668             path_buf, strerror(errno));
669             }
670 2           break;
671 2           case AE_SYMLINK: {
672 2 50         const char *target = e.link_target ? e.link_target : "";
673 2 50         if (archive_extract_symlink(path_buf, target) < 0) {
674 0           warn("File::Raw::Archive: symlink failed for %s: %s",
675             path_buf, strerror(errno));
676             }
677             int n;
678 2           while ((n = h->plugin->read_data(aTHX_ h->plugin, h->cursor,
679 2 50         skip_buf, sizeof skip_buf)) > 0) {}
680 2 50         if (n < 0) croak("File::Raw::Archive: read_data failed");
681 2           break;
682             }
683 81           case AE_FILE: {
684 81 50         if (archive_make_parent_dir(path_buf) < 0) {
685 0           croak("File::Raw::Archive::extract_all: mkpath parent '%s': %s",
686             path_buf, strerror(errno));
687             }
688 81           const char *errstage = NULL;
689 81 50         if (archive_extract_entry(aTHX_ h->plugin, h->cursor, &e,
690             path_buf, apply_xattrs,
691             &errstage) < 0) {
692 0 0         croak("File::Raw::Archive::extract_all: %s '%s': %s",
693             errstage ? errstage : "?", path_buf, strerror(errno));
694             }
695 81           break;
696             }
697 0           default: {
698             int n;
699 0           while ((n = h->plugin->read_data(aTHX_ h->plugin, h->cursor,
700 0 0         skip_buf, sizeof skip_buf)) > 0) {}
701 0 0         if (n < 0) croak("File::Raw::Archive: read_data failed");
702 0           break;
703             }
704             }
705             }
706 8           }
707              
708             /* Shared list helper. Returns RV-AV with one entry per archive entry. */
709             static SV *
710 13           do_list(pTHX_ archive_handle_t *h)
711             {
712             char skip_buf[16 * 1024];
713             AV *rows;
714              
715 13 50         if (!h || h->is_writer || h->closed)
    50          
    50          
716 0           croak("File::Raw::Archive::list: invalid handle");
717              
718 13           rows = newAV();
719 564           for (;;) {
720             ArchiveEntry e;
721 577           int rc = h->plugin->read_next(aTHX_ h->plugin, h->cursor, &e);
722 577 100         if (rc < 0) {
723 3           SvREFCNT_dec((SV *)rows);
724 3           croak("File::Raw::Archive: malformed archive");
725             }
726 574 100         if (rc == 0) break;
727              
728             /* list() returns an AoH for friendlier external consumption.
729             * Internal Entry objects use AV slots; this is a snapshot. */
730 564           SV *entry_rv = entry_to_hv_snapshot(aTHX_ &e);
731 564           av_push(rows, entry_rv);
732              
733             int n;
734 1128           while ((n = h->plugin->read_data(aTHX_ h->plugin, h->cursor,
735 1128 100         skip_buf, sizeof skip_buf)) > 0) {}
736 564 50         if (n < 0) {
737 0           SvREFCNT_dec((SV *)rows);
738 0           croak("File::Raw::Archive: read_data failed");
739             }
740             }
741 10           return newRV_noinc((SV *)rows);
742             }
743              
744             /* Shared single-entry extract helper. Returns 1 if found+written, 0
745             * if name not in archive. */
746             static IV
747 5           do_extract_one(pTHX_ archive_handle_t *h,
748             const char *match_name, STRLEN match_len,
749             const char *dest_path, int apply_xattrs)
750             {
751             char skip_buf[16 * 1024];
752              
753 5 50         if (!h || h->is_writer || h->closed)
    50          
    50          
754 0           croak("File::Raw::Archive::extract: invalid handle");
755              
756 8           for (;;) {
757             ArchiveEntry e;
758 13           int rc = h->plugin->read_next(aTHX_ h->plugin, h->cursor, &e);
759 13 50         if (rc < 0) croak("File::Raw::Archive: malformed archive");
760 16 100         if (rc == 0) return 0;
761              
762 11 100         if (e.name_len == match_len
763 4 100         && memcmp(e.name, match_name, match_len) == 0) {
764 3 50         if (e.type == AE_FILE) {
765 3 50         if (archive_make_parent_dir(dest_path) < 0) {
766 0           croak("File::Raw::Archive::extract: mkpath parent '%s': %s",
767             dest_path, strerror(errno));
768             }
769 3           const char *errstage = NULL;
770 3 50         if (archive_extract_entry(aTHX_ h->plugin, h->cursor, &e,
771             dest_path, apply_xattrs,
772             &errstage) < 0) {
773 0 0         croak("File::Raw::Archive::extract: %s '%s': %s",
774             errstage ? errstage : "?", dest_path, strerror(errno));
775             }
776 0 0         } else if (e.type == AE_DIR) {
777 0 0         if (archive_extract_dir(dest_path, e.mode) < 0) {
778 0           croak("File::Raw::Archive::extract: mkdir '%s': %s",
779             dest_path, strerror(errno));
780             }
781 0 0         } else if (e.type == AE_SYMLINK) {
782 0 0         const char *target = e.link_target ? e.link_target : "";
783 0 0         if (archive_extract_symlink(dest_path, target) < 0) {
784 0           warn("File::Raw::Archive::extract: symlink failed for %s: %s",
785             dest_path, strerror(errno));
786             }
787             }
788 3           return 1;
789             }
790              
791             int n;
792 14           while ((n = h->plugin->read_data(aTHX_ h->plugin, h->cursor,
793 14 100         skip_buf, sizeof skip_buf)) > 0) {}
794 8 50         if (n < 0) croak("File::Raw::Archive: read_data failed");
795             }
796             }
797              
798             /* Build a File::Raw::Archive::Entry blessed AV pointing back at the
799             * shared `reader_sv`. Slots: [reader, meta, slurped]. Returns
800             * RV-AV at +1; caller mortalises if needed. */
801             static SV *
802 111           build_entry_obj(pTHX_ SV *reader_sv, SV *meta_rv)
803             {
804 111           AV *entry = newAV();
805 111           av_extend(entry, E_SLOT_COUNT - 1);
806 111           av_store(entry, E_READER, SvREFCNT_inc(reader_sv));
807 111           av_store(entry, E_META, meta_rv); /* takes ownership */
808             /* E_SLURPED stays empty until ->slurp memoises bytes there. */
809 111           SV *obj = newRV_noinc((SV *)entry);
810 111           sv_bless(obj, gv_stashpv("File::Raw::Archive::Entry", GV_ADD));
811 111           return obj;
812             }
813              
814             /* Shared each-iteration helper. Calls cb_sv per entry, optionally
815             * filtered. cb_sv must be a CV ref. The reader_sv is a blessed Reader
816             * hashref (built by the caller) that Entry objects link back to so
817             * `$entry->slurp` / `$entry->_skip` keep working. Croaks on error. */
818             static void
819 4           do_each(pTHX_ archive_handle_t *h, SV *reader_sv,
820             SV *cb_sv, SV *filter_sv)
821             {
822             char skip_buf[16 * 1024];
823             int has_filter;
824              
825 4 50         if (!h || h->is_writer || h->closed)
    50          
    50          
826 0           croak("File::Raw::Archive::each: invalid handle");
827              
828 5 100         has_filter = (filter_sv && SvOK(filter_sv) && SvROK(filter_sv) &&
    50          
    50          
829 1 50         SvTYPE(SvRV(filter_sv)) == SVt_PVCV);
830              
831 46           for (;;) {
832             ArchiveEntry e;
833 50           int rc = h->plugin->read_next(aTHX_ h->plugin, h->cursor, &e);
834 50 50         if (rc < 0) croak("File::Raw::Archive: malformed archive");
835 50 100         if (rc == 0) break;
836              
837 46           SV *entry_rv = entry_to_av(aTHX_ &e); /* RV-HV at +1 */
838 46           SV *entry_obj = build_entry_obj(aTHX_ reader_sv, entry_rv);
839              
840 46 100         if (has_filter) {
841 40           int keep = 1;
842 40           dSP;
843 40           ENTER;
844 40           SAVETMPS;
845 40 50         PUSHMARK(SP);
846 40 50         XPUSHs(sv_2mortal(SvREFCNT_inc(entry_obj)));
847 40           PUTBACK;
848 40           int count = call_sv(filter_sv, G_SCALAR);
849 40           SPAGAIN;
850 40 50         if (count >= 1) {
851 40           SV *r = POPs;
852 40           keep = SvTRUE(r) ? 1 : 0;
853             }
854 40           PUTBACK;
855 40 50         FREETMPS;
856 40           LEAVE;
857 40 100         if (!keep) {
858 20           SvREFCNT_dec(entry_obj);
859             int n;
860 40           while ((n = h->plugin->read_data(aTHX_ h->plugin, h->cursor,
861 40 100         skip_buf, sizeof skip_buf)) > 0) {}
862 20 50         if (n < 0) croak("File::Raw::Archive: read_data failed");
863 20           continue;
864             }
865             }
866              
867             {
868 26           dSP;
869 26           ENTER;
870 26           SAVETMPS;
871 26 50         PUSHMARK(SP);
872 26 50         XPUSHs(sv_2mortal(SvREFCNT_inc(entry_obj)));
873 26           PUTBACK;
874 26           call_sv(cb_sv, G_VOID | G_DISCARD);
875 26 50         FREETMPS;
876 26           LEAVE;
877             }
878              
879 26           SvREFCNT_dec(entry_obj);
880              
881             /* Drain any unread payload bytes so the cursor advances. */
882             int n;
883 52           while ((n = h->plugin->read_data(aTHX_ h->plugin, h->cursor,
884 52 100         skip_buf, sizeof skip_buf)) > 0) {}
885 26 50         if (n < 0) croak("File::Raw::Archive: read_data failed");
886             }
887 4           }
888              
889             /* ============================================================
890             * Parallel extract: worker loop body + parent orchestrator
891             * ============================================================
892             *
893             * The parent process iterates the archive, slurps each regular file's
894             * payload, marshals (path, content, metadata, xattrs) into a single
895             * binary record, and writes it to one of N forked children's pipes
896             * round-robin. Each child runs `do_worker_loop` (this same function
897             * body) and writes file/chmod/utime errors back to a shared error
898             * pipe. After the parent finishes dispatch and closes all worker job
899             * pipes (signalling EOF), it drains the error pipe and reaps the
900             * children. Any error → consolidated croak.
901             *
902             * fork(2) inside an XSUB is safe as long as the child path doesn't
903             * unwind back through Perl - we _exit(2) directly so Perl END / DESTROY
904             * blocks don't fire and corrupt shared state. */
905              
906             static void
907 0           do_worker_loop(int job_fd, int err_fd)
908             {
909             ParallelJob job;
910             char errbuf[1024];
911             ArchiveEntry e;
912 0           for (;;) {
913 0           int rc = marshal_read_job(job_fd, &job);
914 0 0         if (rc == 1) break; /* clean EOF */
915 0 0         if (rc < 0) {
916 0           const char *m = "worker: malformed job\n";
917 0           ssize_t _ignored = write(err_fd, m, strlen(m));
918             (void)_ignored;
919 0           break;
920             }
921              
922 0           memset(&e, 0, sizeof e);
923 0           e.name = job.path;
924 0           e.name_len = job.path_len;
925 0           e.size = job.content_len;
926 0           e.mode = job.mode;
927 0           e.mtime = job.mtime;
928 0           e.mtime_ns = job.mtime_ns;
929 0           e.uid = job.uid;
930 0           e.gid = job.gid;
931 0           e.type = AE_FILE;
932 0           e.xattrs = job.xattrs;
933 0           e.xattr_count = job.xattr_count;
934              
935 0           const char *stage = NULL;
936 0 0         if (archive_extract_bytes(&e, job.path,
937 0           job.content, job.content_len,
938             job.apply_xattrs, &stage) < 0) {
939 0           int n = snprintf(errbuf, sizeof errbuf,
940             "%s: %s: %s\n",
941 0 0         job.path, stage ? stage : "?",
942 0           strerror(errno));
943 0 0         if (n > 0) {
944 0 0         if ((size_t)n > sizeof errbuf) n = (int)sizeof errbuf;
945 0           ssize_t _ignored = write(err_fd, errbuf, (size_t)n);
946             (void)_ignored;
947             }
948             }
949              
950 0           parallel_job_free(&job);
951             }
952 0           }
953              
954             typedef struct {
955             int write_fd;
956             pid_t pid;
957             } parallel_worker_t;
958              
959             static void
960 1           do_extract_all_parallel(pTHX_ archive_handle_t *h,
961             const char *dest, size_t dest_len,
962             int parallel, int apply_xattrs,
963             SV *filter_sv, int unsafe_paths)
964             {
965             char path_buf[ARCHIVE_PATH_MAX];
966             char skip_buf[16 * 1024];
967 1           parallel_worker_t *workers = NULL;
968 1           int err_r = -1, err_w = -1;
969 1           int num_started = 0;
970             int has_filter;
971 1           int dispatch_err = 0;
972 1           char err_msg[512] = "";
973             int err_pipe[2];
974             struct sigaction old_sigpipe, new_sa;
975 1           int i, rr = 0;
976              
977 1 50         if (!h || h->is_writer || h->closed)
    50          
    50          
978 0           croak("File::Raw::Archive::extract_all: invalid handle");
979 1 50         if (dest_len == 0 || dest_len >= sizeof path_buf - 2)
    50          
980 0           croak("File::Raw::Archive::extract_all: bad dest length");
981 1 50         if (parallel < 1) parallel = 1;
982              
983 1 50         has_filter = (filter_sv && SvOK(filter_sv) && SvROK(filter_sv) &&
    0          
    0          
984 0 0         SvTYPE(SvRV(filter_sv)) == SVt_PVCV);
985              
986 1 50         if (archive_mkpath(dest, 0755) < 0) {
987 0           croak("File::Raw::Archive::extract_all: cannot create dest '%s': %s",
988             dest, strerror(errno));
989             }
990              
991 1 50         if (pipe(err_pipe) < 0) croak("pipe: %s", strerror(errno));
992 1           err_r = err_pipe[0];
993 1           err_w = err_pipe[1];
994              
995 1           workers = (parallel_worker_t *)calloc(parallel, sizeof *workers);
996 1 50         if (!workers) {
997 0           close(err_r); close(err_w);
998 0           croak("File::Raw::Archive: out of memory");
999             }
1000              
1001             /* Ignore SIGPIPE while workers are alive. A worker crash would
1002             * otherwise SIGPIPE the parent on the next dispatch write. */
1003 1           new_sa.sa_handler = SIG_IGN;
1004 1           sigemptyset(&new_sa.sa_mask);
1005 1           new_sa.sa_flags = 0;
1006 1           sigaction(SIGPIPE, &new_sa, &old_sigpipe);
1007              
1008 5 100         for (i = 0; i < parallel; i++) {
1009             int job_pipe[2];
1010 4 50         if (pipe(job_pipe) < 0) {
1011 0           int saved = errno;
1012             int j;
1013 0 0         for (j = 0; j < num_started; j++) {
1014 0           close(workers[j].write_fd);
1015 0           kill(workers[j].pid, SIGTERM);
1016 0           waitpid(workers[j].pid, NULL, 0);
1017             }
1018 0           close(err_r); close(err_w);
1019 0           free(workers);
1020 0           sigaction(SIGPIPE, &old_sigpipe, NULL);
1021 0           croak("File::Raw::Archive: pipe: %s", strerror(saved));
1022             }
1023 4           pid_t pid = fork();
1024 4 50         if (pid < 0) {
1025 0           int saved = errno;
1026             int j;
1027 0           close(job_pipe[0]); close(job_pipe[1]);
1028 0 0         for (j = 0; j < num_started; j++) {
1029 0           close(workers[j].write_fd);
1030 0           kill(workers[j].pid, SIGTERM);
1031 0           waitpid(workers[j].pid, NULL, 0);
1032             }
1033 0           close(err_r); close(err_w);
1034 0           free(workers);
1035 0           sigaction(SIGPIPE, &old_sigpipe, NULL);
1036 0           croak("File::Raw::Archive: fork: %s", strerror(saved));
1037             }
1038 4 50         if (pid == 0) {
1039             /* Child: keep its job-read fd and the shared err-write fd;
1040             * everything else inherited from the parent gets closed. */
1041             int j;
1042 0           close(job_pipe[1]);
1043 0           close(err_r);
1044 0 0         for (j = 0; j < num_started; j++) {
1045 0           close(workers[j].write_fd);
1046             }
1047 0           do_worker_loop(job_pipe[0], err_w);
1048 0           close(job_pipe[0]);
1049 0           close(err_w);
1050 0           _exit(0);
1051             }
1052             /* Parent. */
1053 4           close(job_pipe[0]);
1054 4           workers[num_started].write_fd = job_pipe[1];
1055 4           workers[num_started].pid = pid;
1056 4           num_started++;
1057             }
1058 1           close(err_w); err_w = -1;
1059              
1060             /* Dispatch loop. */
1061 60           for (;;) {
1062             ArchiveEntry e;
1063 61           int rc = h->plugin->read_next(aTHX_ h->plugin, h->cursor, &e);
1064 61 50         if (rc < 0) {
1065 0           snprintf(err_msg, sizeof err_msg, "malformed archive");
1066 0           dispatch_err = 1;
1067 0           break;
1068             }
1069 61 100         if (rc == 0) break;
1070              
1071 60 50         if (!unsafe_paths) {
1072 60 50         if (!archive_path_is_safe(e.name, e.name_len)) {
1073 0           snprintf(err_msg, sizeof err_msg,
1074             "refusing unsafe path '%.*s'",
1075 0           (int)e.name_len, e.name);
1076 0           dispatch_err = 1;
1077 0           break;
1078             }
1079             }
1080              
1081 60 50         if (has_filter) {
1082 0           int keep = 1;
1083 0           dSP;
1084 0           ENTER;
1085 0           SAVETMPS;
1086 0 0         PUSHMARK(SP);
1087 0           SV *entry_rv = entry_to_av(aTHX_ &e);
1088 0           sv_2mortal(entry_rv);
1089 0 0         XPUSHs(entry_rv);
1090 0           PUTBACK;
1091 0           int count = call_sv(filter_sv, G_SCALAR);
1092 0           SPAGAIN;
1093 0 0         if (count >= 1) {
1094 0           SV *r = POPs;
1095 0           keep = SvTRUE(r) ? 1 : 0;
1096             }
1097 0           PUTBACK;
1098 0 0         FREETMPS;
1099 0           LEAVE;
1100 0 0         if (!keep) {
1101             int n;
1102 0           while ((n = h->plugin->read_data(aTHX_ h->plugin, h->cursor,
1103 0 0         skip_buf, sizeof skip_buf)) > 0) {}
1104 0 0         if (n < 0) { snprintf(err_msg, sizeof err_msg, "read_data failed"); dispatch_err = 1; break; }
1105 0           continue;
1106             }
1107             }
1108              
1109 60 50         if (dest_len + 1 + e.name_len + 1 > sizeof path_buf) {
1110 0           snprintf(err_msg, sizeof err_msg,
1111 0           "path too long for '%.*s'", (int)e.name_len, e.name);
1112 0           dispatch_err = 1;
1113 0           break;
1114             }
1115 60           memcpy(path_buf, dest, dest_len);
1116 60           path_buf[dest_len] = '/';
1117 60           memcpy(path_buf + dest_len + 1, e.name, e.name_len);
1118 60           path_buf[dest_len + 1 + e.name_len] = '\0';
1119              
1120 60 50         if (e.type == AE_DIR) {
1121 0 0         if (archive_extract_dir(path_buf, e.mode) < 0) {
1122 0           snprintf(err_msg, sizeof err_msg,
1123 0           "mkdir '%s': %s", path_buf, strerror(errno));
1124 0           dispatch_err = 1;
1125 0           break;
1126             }
1127 0           continue;
1128             }
1129 60 50         if (e.type == AE_SYMLINK) {
1130 0 0         const char *target = e.link_target ? e.link_target : "";
1131 0 0         if (archive_extract_symlink(path_buf, target) < 0) {
1132 0           warn("File::Raw::Archive: symlink failed for %s: %s",
1133             path_buf, strerror(errno));
1134             }
1135             int n;
1136 0           while ((n = h->plugin->read_data(aTHX_ h->plugin, h->cursor,
1137 0 0         skip_buf, sizeof skip_buf)) > 0) {}
1138 0 0         if (n < 0) { snprintf(err_msg, sizeof err_msg, "read_data failed"); dispatch_err = 1; break; }
1139 0           continue;
1140             }
1141 60 50         if (e.type != AE_FILE) {
1142             int n;
1143 0           while ((n = h->plugin->read_data(aTHX_ h->plugin, h->cursor,
1144 0 0         skip_buf, sizeof skip_buf)) > 0) {}
1145 0 0         if (n < 0) { snprintf(err_msg, sizeof err_msg, "read_data failed"); dispatch_err = 1; break; }
1146 0           continue;
1147             }
1148              
1149             /* Regular file: ensure parent dir exists, slurp content, marshal, dispatch. */
1150 60 50         if (archive_make_parent_dir(path_buf) < 0) {
1151 0           snprintf(err_msg, sizeof err_msg,
1152 0           "mkpath parent '%s': %s", path_buf, strerror(errno));
1153 0           dispatch_err = 1;
1154 0           break;
1155             }
1156              
1157 60           char *content = NULL;
1158 60           size_t content_cap = 0, content_len = 0;
1159 60           int read_err = 0;
1160 60           for (;;) {
1161 120 100         if (content_len + 16384 > content_cap) {
1162 60 50         size_t new_cap = content_cap ? content_cap * 2 : 65536;
1163 60 50         while (new_cap < content_len + 16384) new_cap *= 2;
1164 60           char *p = (char *)realloc(content, new_cap);
1165 60 50         if (!p) { read_err = 1; break; }
1166 60           content = p;
1167 60           content_cap = new_cap;
1168             }
1169 120           int n = h->plugin->read_data(aTHX_ h->plugin, h->cursor,
1170             content + content_len, 16384);
1171 120 50         if (n < 0) { read_err = 1; break; }
1172 120 100         if (n == 0) break;
1173 60           content_len += (size_t)n;
1174             }
1175 60 50         if (read_err) {
1176 0           free(content);
1177 0           snprintf(err_msg, sizeof err_msg, "read_data failed for '%s'", path_buf);
1178 0           dispatch_err = 1;
1179 0           break;
1180             }
1181              
1182 60           char *payload = NULL;
1183 60           size_t payload_len = 0;
1184 60 50         if (marshal_job(path_buf, strlen(path_buf),
1185             content, content_len,
1186             e.mode,
1187             e.mtime, e.mtime_ns,
1188             e.uid, e.gid,
1189             apply_xattrs,
1190             e.xattrs, e.xattr_count,
1191             &payload, &payload_len) < 0) {
1192 0           free(content);
1193 0           snprintf(err_msg, sizeof err_msg, "marshal_job: out of memory");
1194 0           dispatch_err = 1;
1195 0           break;
1196             }
1197 60           free(content);
1198              
1199 60 50         if (marshal_send(workers[rr].write_fd, payload, payload_len) < 0) {
1200 0           int saved = errno;
1201 0           free(payload);
1202 0           snprintf(err_msg, sizeof err_msg,
1203             "pipe write to worker %d: %s", rr, strerror(saved));
1204 0           dispatch_err = 1;
1205 0           break;
1206             }
1207 60           free(payload);
1208 60           rr = (rr + 1) % parallel;
1209             }
1210              
1211             /* Close all worker write fds (signals EOF). */
1212 5 100         for (i = 0; i < num_started; i++) {
1213 4 50         if (workers[i].write_fd >= 0) {
1214 4           close(workers[i].write_fd);
1215 4           workers[i].write_fd = -1;
1216             }
1217             }
1218              
1219             /* Drain error pipe (workers' per-job errors). */
1220 1           SV *errors_sv = sv_2mortal(newSVpvs(""));
1221             {
1222             char buf[4096];
1223 0           for (;;) {
1224 1           ssize_t n = read(err_r, buf, sizeof buf);
1225 1 50         if (n < 0) {
1226 0 0         if (errno == EINTR) continue;
1227 0           break;
1228             }
1229 1 50         if (n == 0) break;
1230 0           sv_catpvn(errors_sv, buf, (STRLEN)n);
1231             }
1232             }
1233 1           close(err_r);
1234              
1235             /* Reap. */
1236 5 100         for (i = 0; i < num_started; i++) {
1237 4           waitpid(workers[i].pid, NULL, 0);
1238             }
1239 1           free(workers);
1240              
1241             /* Restore SIGPIPE handler. */
1242 1           sigaction(SIGPIPE, &old_sigpipe, NULL);
1243              
1244 1 50         if (dispatch_err) {
1245 0           croak("File::Raw::Archive::extract_all: %s", err_msg);
1246             }
1247             STRLEN err_len;
1248 1           const char *err_pv = SvPV(errors_sv, err_len);
1249 1 50         if (err_len > 0) {
1250 0           croak("File::Raw::Archive::extract_all: errors during parallel extract:\n %.*s",
1251             (int)err_len, err_pv);
1252             }
1253 1           }
1254              
1255             /* ============================================================
1256             * Custom-op accessor infrastructure (Entry methods)
1257             * ============================================================
1258             *
1259             * `$entry->name`, `->size`, `->is_dir` etc. are normally method
1260             * dispatches: ENTERSUB walks @ISA, finds our XSUB, sets up a CALL
1261             * frame, runs the XSUB body, returns. We can do better at compile
1262             * time by replacing the ENTERSUB op with a custom op that jumps
1263             * straight to a lightweight pp_* handler. The handler reads the
1264             * entry's slot directly and returns - no @ISA walk, no method-cache
1265             * touch, no XSUB call frame.
1266             *
1267             * Mechanism (Object::Proto pattern):
1268             * - Define one pp_entry_accessor handler that uses op_targ to
1269             * know which Meta slot to read (M_NAME .. M_IS_SPARSE).
1270             * - Define one pp_entry_predicate for is_file / is_dir / etc.
1271             * - At BOOT, register a call checker on each accessor's CV.
1272             * The checker walks the op tree at compile time and rewrites
1273             * ENTERSUB to OP_CUSTOM with op_ppaddr / op_targ set.
1274             * - The checker bails (returns the original entersubop unchanged)
1275             * on call forms it can't handle - dynamic method names,
1276             * stashed coderefs, extra args - so the XSUB still runs.
1277             *
1278             * Skipped on perl < 5.14 (cv_set_call_checker not available);
1279             * the XSUB methods stay correct on those builds, just slower.
1280             */
1281              
1282             #if PERL_VERSION_GE(5,14,0)
1283              
1284             static XOP entry_accessor_xop;
1285             static XOP entry_predicate_xop;
1286              
1287             static OP *
1288 0           pp_entry_accessor(pTHX)
1289             {
1290 0           dSP;
1291 0           SV *self_sv = POPs;
1292 0           int slot = (int)PL_op->op_targ;
1293 0           SV *result = &PL_sv_undef;
1294              
1295 0 0         if (SvROK(self_sv) && SvTYPE(SvRV(self_sv)) == SVt_PVAV) {
    0          
1296 0           AV *self = (AV *)SvRV(self_sv);
1297 0           SV **meta_ref = av_fetch(self, E_META, 0);
1298 0 0         if (meta_ref && *meta_ref && SvROK(*meta_ref)
    0          
    0          
1299 0 0         && SvTYPE(SvRV(*meta_ref)) == SVt_PVAV) {
1300 0           AV *meta = (AV *)SvRV(*meta_ref);
1301 0           SV **val = av_fetch(meta, slot, 0);
1302 0 0         if (val && *val && SvOK(*val)) {
    0          
    0          
1303 0           result = sv_2mortal(SvREFCNT_inc(*val));
1304             } else {
1305             /* Numeric defaults for the integer-typed slots, to
1306             * match the XSUB accessor behaviour. */
1307 0 0         switch (slot) {
1308 0           case M_SIZE: case M_MODE: case M_MTIME: case M_MTIME_NS:
1309             case M_UID: case M_GID: case M_TYPE: case M_IS_SPARSE:
1310 0           result = sv_2mortal(newSViv(0));
1311 0           break;
1312 0           default:
1313             /* string-typed slots: stay undef. */
1314 0           break;
1315             }
1316             }
1317             }
1318             }
1319              
1320 0 0         XPUSHs(result);
1321 0           RETURN;
1322             }
1323              
1324             static OP *
1325 0           pp_entry_predicate(pTHX)
1326             {
1327 0           dSP;
1328 0           SV *self_sv = POPs;
1329 0           int kind = (int)PL_op->op_targ;
1330 0           IV t = (IV)AE_FILE;
1331 0           int rc = 0;
1332              
1333 0 0         if (SvROK(self_sv) && SvTYPE(SvRV(self_sv)) == SVt_PVAV) {
    0          
1334 0           AV *self = (AV *)SvRV(self_sv);
1335 0           SV **meta_ref = av_fetch(self, E_META, 0);
1336 0 0         if (meta_ref && *meta_ref && SvROK(*meta_ref)
    0          
    0          
1337 0 0         && SvTYPE(SvRV(*meta_ref)) == SVt_PVAV) {
1338 0           AV *meta = (AV *)SvRV(*meta_ref);
1339 0           SV **type_ref = av_fetch(meta, M_TYPE, 0);
1340 0 0         if (type_ref && *type_ref && SvOK(*type_ref))
    0          
    0          
1341 0           t = SvIV(*type_ref);
1342             }
1343             }
1344              
1345 0           switch (kind) {
1346 0           case 1: rc = (t == AE_FILE); break;
1347 0           case 2: rc = (t == AE_DIR); break;
1348 0           case 3: rc = (t == AE_SYMLINK); break;
1349 0 0         case 4: rc = (t == AE_SYMLINK || t == AE_HARDLINK); break;
    0          
1350             }
1351 0 0         XPUSHs(rc ? &PL_sv_yes : &PL_sv_no);
    0          
1352 0           RETURN;
1353             }
1354              
1355             /* Call checker shared by accessors and predicates. ckobj packs the
1356             * slot/kind in the high bits and a 1-bit "predicate?" flag in the low
1357             * bit so the same checker function can dispatch to either pp_*. */
1358             static OP *
1359 0           entry_method_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj)
1360             {
1361             OP *pushop, *self_op, *cv_op, *newop;
1362             IV packed;
1363             int slot_or_kind;
1364             int is_predicate;
1365              
1366             PERL_UNUSED_ARG(namegv);
1367              
1368 0           packed = SvIV(ckobj);
1369 0           is_predicate = (int)(packed & 1);
1370 0           slot_or_kind = (int)(packed >> 1);
1371              
1372             /* Walk: entersub -> pushmark -> invocant -> &method_cv. We require
1373             * exactly one invocant (no extra args), otherwise bail. */
1374 0           pushop = cUNOPx(entersubop)->op_first;
1375 0 0         if (!OpHAS_SIBLING(pushop)) {
1376 0           pushop = cUNOPx(pushop)->op_first;
1377             }
1378 0 0         self_op = OpSIBLING(pushop);
1379 0 0         if (!self_op) return entersubop;
1380 0 0         cv_op = OpSIBLING(self_op);
1381 0 0         if (!cv_op) return entersubop;
1382 0 0         if (OpSIBLING(cv_op)) return entersubop; /* extra args */
    0          
1383              
1384             /* Detach self_op from the entersub tree. */
1385 0           OpMORESIB_set(pushop, cv_op);
1386 0           OpLASTSIB_set(self_op, NULL);
1387 0           self_op = op_contextualize(self_op, G_SCALAR);
1388              
1389             /* Build OP_NULL first (avoids -DDEBUGGING newUNOP assert), then
1390             * convert to OP_CUSTOM and set op_ppaddr / op_targ. */
1391 0           newop = newUNOP(OP_NULL, 0, self_op);
1392 0           newop->op_type = OP_CUSTOM;
1393 0 0         newop->op_ppaddr = is_predicate ? pp_entry_predicate : pp_entry_accessor;
1394 0           newop->op_targ = (PADOFFSET)slot_or_kind;
1395              
1396 0           op_free(entersubop);
1397 0           return newop;
1398             }
1399              
1400             static void
1401 435           install_entry_method_checker(pTHX_ const char *method,
1402             int slot_or_kind, int is_predicate)
1403             {
1404             char full[128];
1405 435           snprintf(full, sizeof full,
1406             "File::Raw::Archive::Entry::%s", method);
1407 435           CV *cv = get_cv(full, 0);
1408 435 50         if (!cv) return;
1409 435 100         SV *ckobj = newSViv(((IV)slot_or_kind << 1)
1410             | (is_predicate ? 1 : 0));
1411 435           cv_set_call_checker(cv, entry_method_checker, ckobj);
1412             }
1413              
1414             #endif /* PERL_VERSION_GE(5,14,0) */
1415              
1416             /* ============================================================ */
1417              
1418             MODULE = File::Raw::Archive PACKAGE = File::Raw::Archive
1419              
1420             PROTOTYPES: DISABLE
1421              
1422             BOOT:
1423 29           archive_register_plugin(aTHX_ &tar_plugin);
1424             {
1425 29           HV *stash = gv_stashpv("File::Raw::Archive", GV_ADD);
1426 29           newCONSTSUB(stash, "AE_FILE", newSViv(AE_FILE));
1427 29           newCONSTSUB(stash, "AE_DIR", newSViv(AE_DIR));
1428 29           newCONSTSUB(stash, "AE_SYMLINK", newSViv(AE_SYMLINK));
1429 29           newCONSTSUB(stash, "AE_HARDLINK", newSViv(AE_HARDLINK));
1430 29           newCONSTSUB(stash, "AE_FIFO", newSViv(AE_FIFO));
1431 29           newCONSTSUB(stash, "AE_CHAR", newSViv(AE_CHAR));
1432 29           newCONSTSUB(stash, "AE_BLOCK", newSViv(AE_BLOCK));
1433 29           newCONSTSUB(stash, "AE_OTHER", newSViv(AE_OTHER));
1434             }
1435             #if PERL_VERSION_GE(5,14,0)
1436             /* Register custom-op replacements for Entry method dispatch.
1437             * After this, `$entry->name` etc. compiles into a direct AV-slot
1438             * lookup with no @ISA walk and no XSUB call frame. */
1439 29           XopENTRY_set(&entry_accessor_xop, xop_name, "entry_accessor");
1440 29           XopENTRY_set(&entry_accessor_xop, xop_desc, "Entry method accessor");
1441 29           XopENTRY_set(&entry_accessor_xop, xop_class, OA_UNOP);
1442 29           Perl_custom_op_register(aTHX_ pp_entry_accessor, &entry_accessor_xop);
1443              
1444 29           XopENTRY_set(&entry_predicate_xop, xop_name, "entry_predicate");
1445 29           XopENTRY_set(&entry_predicate_xop, xop_desc, "Entry type predicate");
1446 29           XopENTRY_set(&entry_predicate_xop, xop_class, OA_UNOP);
1447 29           Perl_custom_op_register(aTHX_ pp_entry_predicate, &entry_predicate_xop);
1448              
1449             /* Slot accessors. */
1450 29           install_entry_method_checker(aTHX_ "name", M_NAME, 0);
1451 29           install_entry_method_checker(aTHX_ "size", M_SIZE, 0);
1452 29           install_entry_method_checker(aTHX_ "mode", M_MODE, 0);
1453 29           install_entry_method_checker(aTHX_ "mtime", M_MTIME, 0);
1454 29           install_entry_method_checker(aTHX_ "mtime_ns", M_MTIME_NS, 0);
1455 29           install_entry_method_checker(aTHX_ "uid", M_UID, 0);
1456 29           install_entry_method_checker(aTHX_ "gid", M_GID, 0);
1457 29           install_entry_method_checker(aTHX_ "type", M_TYPE, 0);
1458 29           install_entry_method_checker(aTHX_ "link_target", M_LINK_TARGET, 0);
1459 29           install_entry_method_checker(aTHX_ "xattrs", M_XATTRS, 0);
1460 29           install_entry_method_checker(aTHX_ "is_sparse", M_IS_SPARSE, 0);
1461              
1462             /* Type predicates: kind=1..4 keyed to the switch in pp_entry_predicate. */
1463 29           install_entry_method_checker(aTHX_ "is_file", 1, 1);
1464 29           install_entry_method_checker(aTHX_ "is_dir", 2, 1);
1465 29           install_entry_method_checker(aTHX_ "is_symlink", 3, 1);
1466 29           install_entry_method_checker(aTHX_ "is_link", 4, 1);
1467             #endif
1468              
1469             SV *
1470             _open_read(path_sv, opts_sv)
1471             SV *path_sv
1472             SV *opts_sv
1473             PREINIT:
1474 0           HV *opts = NULL;
1475 0           archive_handle_t *h = NULL;
1476             int gz, fd;
1477             const ArchivePlugin *plugin;
1478             archive_pull_fn pull;
1479             void *src;
1480             CODE:
1481 0 0         if (!SvOK(path_sv)) croak("File::Raw::Archive::_open_read: undef path");
1482 0 0         if (SvROK(opts_sv) && SvTYPE(SvRV(opts_sv)) == SVt_PVHV)
    0          
1483 0           opts = (HV *)SvRV(opts_sv);
1484              
1485 0           fd = open(SvPV_nolen(path_sv), O_RDONLY);
1486 0 0         if (fd < 0) croak("File::Raw::Archive: cannot open '%s': %s",
1487             SvPV_nolen(path_sv), strerror(errno));
1488              
1489 0           gz = resolve_compression(aTHX_ fd, opts);
1490 0           plugin = resolve_plugin(aTHX_ opts, fd, gz);
1491 0 0         if (!plugin) {
1492 0           close(fd);
1493 0           croak("File::Raw::Archive: no plugin selected (auto-detection failed)");
1494             }
1495              
1496 0           h = make_handle();
1497 0 0         if (!h) { close(fd); croak("File::Raw::Archive: out of memory"); }
1498 0           h->fd = fd;
1499 0           h->plugin = plugin;
1500              
1501 0 0         if (gz) {
1502 0           h->gz_src = archive_gz_src_new(fd, ARCHIVE_DEFAULT_CHUNK);
1503 0 0         if (!h->gz_src) {
1504 0           free_handle(aTHX_ h);
1505 0           croak("File::Raw::Archive: gzip stream init failed");
1506             }
1507 0           pull = archive_pull_gz;
1508 0           src = h->gz_src;
1509             } else {
1510 0           h->fd_state.fd = fd;
1511 0           pull = archive_pull_fd;
1512 0           src = &h->fd_state;
1513             }
1514              
1515 0 0         if (plugin->read_open(aTHX_ plugin, pull, src, opts, &h->cursor) < 0) {
1516 0           free_handle(aTHX_ h);
1517 0           croak("File::Raw::Archive: read_open failed");
1518             }
1519              
1520 0           RETVAL = new_handle_obj(aTHX_ h, "File::Raw::Archive::Reader");
1521             OUTPUT:
1522             RETVAL
1523              
1524             SV *
1525             _read_next(handle_sv)
1526             SV *handle_sv
1527             PREINIT:
1528             archive_handle_t *h;
1529             ArchiveEntry e;
1530             int rc;
1531             CODE:
1532 0           h = unwrap_handle(aTHX_ handle_sv);
1533 0 0         if (!h || h->closed || h->is_writer)
    0          
    0          
1534 0           croak("File::Raw::Archive::_read_next: invalid handle");
1535 0           rc = h->plugin->read_next(aTHX_ h->plugin, h->cursor, &e);
1536 0 0         if (rc < 0) croak("File::Raw::Archive: malformed archive");
1537 0 0         if (rc == 0) RETVAL = &PL_sv_undef;
1538 0           else RETVAL = entry_to_av(aTHX_ &e);
1539             OUTPUT:
1540             RETVAL
1541              
1542             SV *
1543             _read_data(handle_sv, max_bytes_sv = NULL)
1544             SV *handle_sv
1545             SV *max_bytes_sv
1546             PREINIT:
1547             archive_handle_t *h;
1548 0 0         UV want = 0;
1549             SV *out;
1550             char buf[64 * 1024];
1551             int n;
1552             CODE:
1553 0           h = unwrap_handle(aTHX_ handle_sv);
1554 0 0         if (!h || h->closed || h->is_writer)
    0          
    0          
1555 0           croak("File::Raw::Archive::_read_data: invalid handle");
1556 0 0         if (max_bytes_sv && SvOK(max_bytes_sv)) want = SvUV(max_bytes_sv);
    0          
1557 0           out = newSVpvn("", 0);
1558             /* If `want` set, read exactly that many. Otherwise drain the entry. */
1559 0           while (1) {
1560 0           size_t take = sizeof buf;
1561 0 0         if (want > 0) {
1562 0           UV remain = want - (UV)SvCUR(out);
1563 0 0         if (remain == 0) break;
1564 0 0         if (remain < take) take = (size_t)remain;
1565             }
1566 0           n = h->plugin->read_data(aTHX_ h->plugin, h->cursor, buf, take);
1567 0 0         if (n < 0) { SvREFCNT_dec(out); croak("File::Raw::Archive: read_data failed"); }
1568 0 0         if (n == 0) break;
1569 0           sv_catpvn(out, buf, (STRLEN)n);
1570 0 0         if (want == 0 && (size_t)n < take) {
1571             /* Either entry done or short read; if entry has more,
1572             * the next iteration will return more. read_data returns
1573             * 0 only at end-of-entry. */
1574             }
1575             }
1576 0           RETVAL = out;
1577             OUTPUT:
1578             RETVAL
1579              
1580             void
1581             _read_close(handle_sv)
1582             SV *handle_sv
1583             PREINIT:
1584             archive_handle_t *h;
1585             CODE:
1586 0           h = unwrap_handle(aTHX_ handle_sv);
1587 0 0         if (h) free_handle(aTHX_ h);
1588              
1589             SV *
1590             _open_write(path_sv, opts_sv)
1591             SV *path_sv
1592             SV *opts_sv
1593             PREINIT:
1594 0           HV *opts = NULL;
1595 0           archive_handle_t *h = NULL;
1596 0           int gz = 0, fd;
1597             const ArchivePlugin *plugin;
1598             archive_push_fn push;
1599             void *sink;
1600             CODE:
1601 0 0         if (!SvOK(path_sv)) croak("File::Raw::Archive::_open_write: undef path");
1602 0 0         if (SvROK(opts_sv) && SvTYPE(SvRV(opts_sv)) == SVt_PVHV)
    0          
1603 0           opts = (HV *)SvRV(opts_sv);
1604              
1605             {
1606 0 0         SV **sv = opts ? hv_fetchs(opts, "compression", 0) : NULL;
1607 0 0         if (sv && *sv && SvOK(*sv)) {
    0          
    0          
1608             STRLEN nn;
1609 0           const char *pp = SvPV(*sv, nn);
1610 0 0         if ((nn == 4 && memcmp(pp, "gzip", 4) == 0)) gz = 1;
    0          
1611 0 0         else if (nn == 4 && memcmp(pp, "auto", 4) == 0) {
    0          
1612             /* On write, "auto" means look at suffix; if .gz, gzip. */
1613             STRLEN pl;
1614 0           const char *pp2 = SvPV(path_sv, pl);
1615 0 0         if (pl >= 3 && memcmp(pp2 + pl - 3, ".gz", 3) == 0) gz = 1;
    0          
1616             }
1617             }
1618             }
1619              
1620 0           fd = open(SvPV_nolen(path_sv), O_WRONLY | O_CREAT | O_TRUNC, 0644);
1621 0 0         if (fd < 0) croak("File::Raw::Archive: cannot open '%s' for write: %s",
1622             SvPV_nolen(path_sv), strerror(errno));
1623              
1624             {
1625 0 0         SV **sv = opts ? hv_fetchs(opts, "plugin", 0) : NULL;
1626 0 0         if (sv && *sv && SvOK(*sv)) {
    0          
    0          
1627             STRLEN nn;
1628 0           const char *pp = SvPV(*sv, nn);
1629             char namebuf[64];
1630 0 0         if (nn >= sizeof namebuf) {
1631 0           close(fd);
1632 0           croak("File::Raw::Archive: plugin name too long");
1633             }
1634 0           memcpy(namebuf, pp, nn);
1635 0           namebuf[nn] = '\0';
1636 0           plugin = archive_lookup_plugin(aTHX_ namebuf);
1637             } else {
1638 0           plugin = archive_lookup_plugin(aTHX_ "tar");
1639             }
1640             }
1641 0 0         if (!plugin) { close(fd); croak("File::Raw::Archive: unknown plugin"); }
1642              
1643 0           h = make_handle();
1644 0 0         if (!h) { close(fd); croak("File::Raw::Archive: out of memory"); }
1645 0           h->fd = fd;
1646 0           h->plugin = plugin;
1647 0           h->is_writer = 1;
1648              
1649 0 0         if (gz) {
1650 0           int level = 6;
1651 0 0         if (opts) {
1652 0           SV **lsv = hv_fetchs(opts, "level", 0);
1653 0 0         if (lsv && *lsv && SvOK(*lsv)) level = (int)SvIV(*lsv);
    0          
    0          
1654             }
1655 0           h->gz_sink = archive_gz_sink_new(fd, ARCHIVE_DEFAULT_CHUNK, level);
1656 0 0         if (!h->gz_sink) { free_handle(aTHX_ h); croak("File::Raw::Archive: gzip sink init failed"); }
1657 0           push = archive_push_gz;
1658 0           sink = h->gz_sink;
1659             } else {
1660 0           h->fd_state.fd = fd;
1661 0           push = archive_push_fd;
1662 0           sink = &h->fd_state;
1663             }
1664              
1665 0 0         if (plugin->write_open(aTHX_ plugin, push, sink, opts, &h->cursor) < 0) {
1666 0           free_handle(aTHX_ h);
1667 0           croak("File::Raw::Archive: write_open failed (bad format option?)");
1668             }
1669              
1670 0           RETVAL = new_handle_obj(aTHX_ h, "File::Raw::Archive::Writer");
1671             OUTPUT:
1672             RETVAL
1673              
1674             void
1675             _write_add(handle_sv, entry_sv, content_sv)
1676             SV *handle_sv
1677             SV *entry_sv
1678             SV *content_sv
1679             PREINIT:
1680             archive_handle_t *h;
1681             HV *eh;
1682             ArchiveEntry e;
1683             ArchiveXattr xbuf[64];
1684 0           size_t xused = 0;
1685 0           STRLEN clen = 0;
1686 0           const char *cp = NULL;
1687             CODE:
1688 0           h = unwrap_handle(aTHX_ handle_sv);
1689 0 0         if (!h || h->closed || !h->is_writer)
    0          
    0          
1690 0           croak("File::Raw::Archive::_write_add: invalid handle");
1691 0 0         if (!SvROK(entry_sv) || SvTYPE(SvRV(entry_sv)) != SVt_PVHV)
    0          
1692 0           croak("File::Raw::Archive::_write_add: entry must be a hashref");
1693 0           eh = (HV *)SvRV(entry_sv);
1694 0           hv_to_entry(aTHX_ eh, &e, xbuf, sizeof xbuf / sizeof xbuf[0], &xused);
1695              
1696 0 0         if (SvOK(content_sv)) {
1697 0           cp = SvPV(content_sv, clen);
1698             /* If caller didn't set entry size, use the content length. */
1699 0 0         if (e.size == 0 && clen > 0) e.size = clen;
    0          
1700             }
1701              
1702 0 0         if (h->plugin->write_add(aTHX_ h->plugin, h->cursor, &e, cp, clen) < 0)
1703 0           croak("File::Raw::Archive::_write_add: write failed (entry exceeds format limits?)");
1704              
1705             void
1706             _write_close(handle_sv)
1707             SV *handle_sv
1708             PREINIT:
1709             archive_handle_t *h;
1710             CODE:
1711 0           h = unwrap_handle(aTHX_ handle_sv);
1712 0 0         if (!h) return;
1713 0 0         if (!h->closed && h->plugin && h->cursor && h->is_writer) {
    0          
    0          
    0          
1714 0 0         if (h->plugin->write_close(aTHX_ h->plugin, h->cursor) < 0)
1715 0           croak("File::Raw::Archive::_write_close: failed");
1716 0           h->cursor = NULL; /* prevent double-close */
1717             }
1718             /* gz_sink finish is handled in free_handle. */
1719 0 0         if (h->gz_sink) {
1720 0 0         if (archive_gz_sink_finish(h->gz_sink) < 0)
1721 0           croak("File::Raw::Archive::_write_close: gzip finalise failed");
1722             }
1723 0           free_handle(aTHX_ h);
1724              
1725             int
1726             _apply_xattrs(fd_iv, xattrs_sv)
1727             IV fd_iv
1728             SV *xattrs_sv
1729             PREINIT:
1730             HV *xh;
1731             ArchiveXattr buf[64];
1732 0 0         size_t n = 0;
1733             int rc;
1734             CODE:
1735 0 0         if (!SvROK(xattrs_sv) || SvTYPE(SvRV(xattrs_sv)) != SVt_PVHV) XSRETURN_IV(0);
    0          
1736 0           xh = (HV *)SvRV(xattrs_sv);
1737 0           hv_iterinit(xh);
1738             HE *he;
1739 0 0         while ((he = hv_iternext(xh)) && n < sizeof buf / sizeof buf[0]) {
    0          
1740             I32 klen_i;
1741 0           const char *k = hv_iterkey(he, &klen_i);
1742 0           SV *v = hv_iterval(xh, he);
1743             STRLEN vlen;
1744 0           const char *vp = SvPV(v, vlen);
1745 0           buf[n].key = k;
1746 0           buf[n].key_len = (size_t)klen_i;
1747 0           buf[n].value = vp;
1748 0           buf[n].value_len = vlen;
1749 0           n++;
1750             }
1751 0           rc = archive_apply_xattrs((int)fd_iv, buf, n);
1752 0 0         RETVAL = rc;
1753             OUTPUT:
1754             RETVAL
1755              
1756             SV *
1757             _list_plugins()
1758             PREINIT:
1759             AV *av;
1760             int i;
1761             CODE:
1762 0           av = newAV();
1763             /* We don't expose iteration in the public API, so do a small
1764             * lookup-by-name dance. Currently only "tar" is built-in; sister
1765             * dists register more. */
1766             static const char *known[] = { "tar", "zip", "cpio", "ar", NULL };
1767 0 0         for (i = 0; known[i]; i++) {
1768 0 0         if (archive_lookup_plugin(aTHX_ known[i])) {
1769 0           av_push(av, newSVpv(known[i], 0));
1770             }
1771             }
1772 0           RETVAL = newRV_noinc((SV *)av);
1773             OUTPUT:
1774             RETVAL
1775              
1776             void
1777             _extract_all_xs(handle_sv, dest_sv, apply_xattrs_iv, unsafe_paths_iv, filter_sv)
1778             SV *handle_sv
1779             SV *dest_sv
1780             IV apply_xattrs_iv
1781             IV unsafe_paths_iv
1782             SV *filter_sv
1783             PREINIT:
1784             archive_handle_t *h;
1785             const char *dest;
1786             STRLEN dest_len;
1787             CODE:
1788 0           h = unwrap_handle(aTHX_ handle_sv);
1789 0 0         if (!SvOK(dest_sv))
1790 0           croak("File::Raw::Archive::_extract_all_xs: dest required");
1791 0           dest = SvPV(dest_sv, dest_len);
1792 0           do_extract_all_seq(aTHX_ h, dest, dest_len,
1793             (int)apply_xattrs_iv, (int)unsafe_paths_iv,
1794             filter_sv);
1795              
1796             SV *
1797             _list_xs(handle_sv)
1798             SV *handle_sv
1799             CODE:
1800 0           RETVAL = do_list(aTHX_ unwrap_handle(aTHX_ handle_sv));
1801             OUTPUT:
1802             RETVAL
1803              
1804             IV
1805             _extract_one_xs(handle_sv, name_sv, dest_sv, apply_xattrs_iv)
1806             SV *handle_sv
1807             SV *name_sv
1808             SV *dest_sv
1809             IV apply_xattrs_iv
1810             PREINIT:
1811             archive_handle_t *h;
1812             STRLEN match_len;
1813             const char *match_name;
1814             const char *dest_path;
1815             CODE:
1816 0           h = unwrap_handle(aTHX_ handle_sv);
1817 0 0         if (!SvOK(name_sv) || !SvOK(dest_sv))
    0          
1818 0           croak("File::Raw::Archive::_extract_one_xs: name and dest required");
1819 0           match_name = SvPV(name_sv, match_len);
1820 0           dest_path = SvPV_nolen(dest_sv);
1821 0           RETVAL = do_extract_one(aTHX_ h, match_name, match_len,
1822             dest_path, (int)apply_xattrs_iv);
1823             OUTPUT:
1824             RETVAL
1825              
1826             void
1827             _send_job_xs(fd_iv, path_sv, content_sv, mode_iv, mtime_iv, mtime_ns_iv, uid_iv, gid_iv, apply_xattrs_iv, xattrs_sv)
1828             IV fd_iv
1829             SV *path_sv
1830             SV *content_sv
1831             IV mode_iv
1832             IV mtime_iv
1833             IV mtime_ns_iv
1834             IV uid_iv
1835             IV gid_iv
1836             IV apply_xattrs_iv
1837             SV *xattrs_sv
1838             PREINIT:
1839             int fd;
1840 3           STRLEN path_len = 0;
1841 3           STRLEN content_len = 0;
1842             const char *path;
1843 3           const char *content = "";
1844 3           char *buf = NULL;
1845 3           size_t buf_len = 0;
1846             ArchiveXattr xbuf[64];
1847 3           size_t xn = 0;
1848             CODE:
1849 3           fd = (int)fd_iv;
1850 3 50         if (!SvOK(path_sv))
1851 0           croak("File::Raw::Archive::_send_job_xs: path required");
1852 3           path = SvPV(path_sv, path_len);
1853 3 50         if (SvOK(content_sv)) {
1854 3           content = SvPV(content_sv, content_len);
1855             }
1856 3 100         if (SvROK(xattrs_sv) && SvTYPE(SvRV(xattrs_sv)) == SVt_PVHV) {
    50          
1857 1           HV *xh = (HV *)SvRV(xattrs_sv);
1858 1           hv_iterinit(xh);
1859             HE *he;
1860 3 100         while ((he = hv_iternext(xh)) && xn < sizeof xbuf / sizeof xbuf[0]) {
    50          
1861             I32 klen_i;
1862 2           const char *k = hv_iterkey(he, &klen_i);
1863 2           SV *v = hv_iterval(xh, he);
1864             STRLEN vlen;
1865 2           const char *vp = SvPV(v, vlen);
1866 2           xbuf[xn].key = k;
1867 2           xbuf[xn].key_len = (size_t)klen_i;
1868 2           xbuf[xn].value = vp;
1869 2           xbuf[xn].value_len = vlen;
1870 2           xn++;
1871             }
1872             }
1873              
1874 3 50         if (marshal_job(path, path_len,
1875             content, content_len,
1876             (uint32_t)mode_iv,
1877             (uint64_t)mtime_iv, (uint32_t)mtime_ns_iv,
1878             (uint32_t)uid_iv, (uint32_t)gid_iv,
1879             (int)apply_xattrs_iv,
1880             xbuf, xn,
1881             &buf, &buf_len) < 0) {
1882 0           croak("File::Raw::Archive::_send_job_xs: out of memory");
1883             }
1884 3 50         if (marshal_send(fd, buf, buf_len) < 0) {
1885 0           int saved = errno;
1886 0           free(buf);
1887 0           errno = saved;
1888 0           croak("File::Raw::Archive::_send_job_xs: pipe write: %s",
1889             strerror(saved));
1890             }
1891 3           free(buf);
1892              
1893             void
1894             _worker_loop_xs(job_fd_iv, err_fd_iv)
1895             IV job_fd_iv
1896             IV err_fd_iv
1897             CODE:
1898 0           do_worker_loop((int)job_fd_iv, (int)err_fd_iv);
1899              
1900             # ====================================================================
1901             # Public top-level class methods. Each one owns the open + work + close
1902             # lifecycle: the user never sees a Reader/Writer handle for these. The
1903             # handle is freed via SAVEDESTRUCTOR_X so a croak inside the work
1904             # helper cleanly closes the fd and frees the cursor.
1905             # ====================================================================
1906              
1907             void
1908             list(...)
1909             PPCODE:
1910             {
1911 13 50         if (items < 2)
1912 0           croak("Usage: \\$class->list(\\$path, %%opts)");
1913 13 100         if ((items - 2) % 2 != 0)
1914 1           croak("File::Raw::Archive::list: odd number of options");
1915 12           HV *opts = build_opts_from_args(aTHX_ &ST(2), items - 2);
1916 12           archive_handle_t *h = open_reader(aTHX_ ST(1), opts);
1917 12           SAVEDESTRUCTOR_X(free_handle_destructor, h);
1918 12           SV *result = do_list(aTHX_ h);
1919 9 50         XPUSHs(sv_2mortal(result));
1920 9           XSRETURN(1);
1921             }
1922              
1923             IV
1924             extract(...)
1925             PPCODE:
1926             {
1927 5 100         if (items < 4)
1928 1           croak("Usage: \\$class->extract(\\$path, \\$name, \\$dest, %%opts)");
1929 4 50         if ((items - 4) % 2 != 0)
1930 0           croak("File::Raw::Archive::extract: odd number of options");
1931 4           HV *opts = build_opts_from_args(aTHX_ &ST(4), items - 4);
1932 4           int apply_xattrs = 1;
1933 4           SV **xv = hv_fetchs(opts, "xattrs", 0);
1934 4 50         if (xv && *xv && SvOK(*xv)) apply_xattrs = SvTRUE(*xv) ? 1 : 0;
    0          
    0          
1935              
1936 4           archive_handle_t *h = open_reader(aTHX_ ST(1), opts);
1937 4           SAVEDESTRUCTOR_X(free_handle_destructor, h);
1938              
1939             STRLEN match_len;
1940 4           const char *match_name = SvPV(ST(2), match_len);
1941 4           const char *dest_path = SvPV_nolen(ST(3));
1942 4           IV rc = do_extract_one(aTHX_ h, match_name, match_len, dest_path,
1943             apply_xattrs);
1944 4 50         XPUSHs(sv_2mortal(newSViv(rc)));
1945 4           XSRETURN(1);
1946             }
1947              
1948             IV
1949             extract_all(...)
1950             PPCODE:
1951             {
1952 11 100         if (items < 3)
1953 1           croak("Usage: \\$class->extract_all(\\$path, \\$dest, %%opts)");
1954 10 50         if ((items - 3) % 2 != 0)
1955 0           croak("File::Raw::Archive::extract_all: odd number of options");
1956 10           HV *opts = build_opts_from_args(aTHX_ &ST(3), items - 3);
1957 10           int apply_xattrs = 1;
1958 10           int unsafe_paths = 0;
1959 10           int parallel = 1;
1960 10           SV *filter = NULL;
1961             SV **xv;
1962              
1963 10           xv = hv_fetchs(opts, "xattrs", 0);
1964 10 50         if (xv && *xv && SvOK(*xv)) apply_xattrs = SvTRUE(*xv) ? 1 : 0;
    0          
    0          
1965 10           xv = hv_fetchs(opts, "unsafe_paths", 0);
1966 10 50         if (xv && *xv && SvOK(*xv)) unsafe_paths = SvTRUE(*xv) ? 1 : 0;
    0          
    0          
1967 10           xv = hv_fetchs(opts, "entry_filter", 0);
1968 10 50         if (xv && *xv && SvOK(*xv)) filter = *xv;
    0          
    0          
1969 10           xv = hv_fetchs(opts, "parallel", 0);
1970 10 100         if (xv && *xv && SvOK(*xv)) parallel = (int)SvIV(*xv);
    50          
    50          
1971              
1972 10 100         if (parallel > 1 && !parallel_supported()) {
    50          
1973 0           warn("File::Raw::Archive: parallel extract not supported on this "
1974             "platform; falling back to sequential\n");
1975 0           parallel = 1;
1976             }
1977              
1978 10           archive_handle_t *h = open_reader(aTHX_ ST(1), opts);
1979 10           SAVEDESTRUCTOR_X(free_handle_destructor, h);
1980              
1981             STRLEN dest_len;
1982 10           const char *dest = SvPV(ST(2), dest_len);
1983 10 100         if (parallel > 1) {
1984 1           do_extract_all_parallel(aTHX_ h, dest, dest_len,
1985             parallel, apply_xattrs,
1986             filter, unsafe_paths);
1987             } else {
1988 9           do_extract_all_seq(aTHX_ h, dest, dest_len,
1989             apply_xattrs, unsafe_paths, filter);
1990             }
1991 8 50         XPUSHs(sv_2mortal(newSViv(1)));
1992 8           XSRETURN(1);
1993             }
1994              
1995             void
1996             each(...)
1997             PPCODE:
1998             {
1999 5 100         if (items < 3)
2000 1           croak("Usage: \\$class->each(\\$path, %%opts, sub { ... })");
2001 4           SV *cb_sv = ST(items - 1);
2002 4 100         if (!SvROK(cb_sv) || SvTYPE(SvRV(cb_sv)) != SVt_PVCV)
    50          
2003 1           croak("File::Raw::Archive::each: last arg must be a coderef");
2004 3           int opts_end = items - 1;
2005 3 50         if ((opts_end - 2) % 2 != 0)
2006 0           croak("File::Raw::Archive::each: odd number of options");
2007 3           HV *opts = build_opts_from_args(aTHX_ &ST(2), opts_end - 2);
2008 3           SV *filter = NULL;
2009 3           SV **xv = hv_fetchs(opts, "entry_filter", 0);
2010 3 100         if (xv && *xv && SvOK(*xv)) filter = *xv;
    50          
    50          
2011              
2012 3           archive_handle_t *h = open_reader(aTHX_ ST(1), opts);
2013              
2014             /* Build a Reader AV equivalent to what File::Raw::Archive->open
2015             * returns. The Entry objects we hand to the callback link back to
2016             * this so $entry->slurp / $entry->_skip work the same as on the
2017             * iterator API. The Reader's DESTROY (XSUB) frees `h` via
2018             * maybe_free_handle when the AV refcount hits zero - either at
2019             * XSUB exit (if no Entry stashed it) or whenever the last stashed
2020             * Entry is collected. */
2021 3           AV *reader_av = newAV();
2022 3           av_extend(reader_av, R_SLOT_COUNT - 1);
2023 3           av_store(reader_av, R_HANDLE,
2024             new_handle_obj(aTHX_ h, "File::Raw::Archive::Reader"));
2025 3           av_store(reader_av, R_CONSUMED, newSViv(1));
2026 3           av_store(reader_av, R_CLOSED, newSViv(0));
2027 3           SV *reader_sv = sv_2mortal(newRV_noinc((SV *)reader_av));
2028 3           sv_bless(reader_sv, gv_stashpv("File::Raw::Archive::Reader", GV_ADD));
2029              
2030 3           do_each(aTHX_ h, reader_sv, cb_sv, filter);
2031 3           XSRETURN_EMPTY;
2032             }
2033              
2034             SV *
2035             open(...)
2036             PPCODE:
2037             {
2038 36 50         if (items < 2)
2039 0           croak("Usage: \\$class->open(\\$path, %%opts)");
2040 36 50         if ((items - 2) % 2 != 0)
2041 0           croak("File::Raw::Archive::open: odd number of options");
2042 36           HV *opts = build_opts_from_args(aTHX_ &ST(2), items - 2);
2043 36           archive_handle_t *h = open_reader(aTHX_ ST(1), opts);
2044 35           SV *handle_sv = new_handle_obj(aTHX_ h, "File::Raw::Archive::Reader");
2045              
2046 35           AV *self = newAV();
2047 35           av_extend(self, R_SLOT_COUNT - 1);
2048 35           av_store(self, R_HANDLE, handle_sv); /* takes ownership */
2049 35           av_store(self, R_CONSUMED, newSViv(1));
2050 35           av_store(self, R_CLOSED, newSViv(0));
2051             /* R_CUR_ENTRY left empty until the first ->next. */
2052 35           SV *obj = sv_bless(newRV_noinc((SV *)self),
2053             gv_stashpv("File::Raw::Archive::Reader", GV_ADD));
2054 35 50         XPUSHs(sv_2mortal(obj));
2055 35           XSRETURN(1);
2056             }
2057              
2058             SV *
2059             create(...)
2060             PPCODE:
2061             {
2062 39 50         if (items < 2)
2063 0           croak("Usage: \\$class->create(\\$path, %%opts)");
2064 39 50         if ((items - 2) % 2 != 0)
2065 0           croak("File::Raw::Archive::create: odd number of options");
2066 39           HV *opts = build_opts_from_args(aTHX_ &ST(2), items - 2);
2067 39           archive_handle_t *h = open_writer(aTHX_ ST(1), opts);
2068 38           SV *handle_sv = new_handle_obj(aTHX_ h, "File::Raw::Archive::Writer");
2069              
2070 38           AV *self = newAV();
2071 38           av_extend(self, W_SLOT_COUNT - 1);
2072 38           av_store(self, W_HANDLE, handle_sv);
2073 38           av_store(self, W_CLOSED, newSViv(0));
2074 38           SV *obj = sv_bless(newRV_noinc((SV *)self),
2075             gv_stashpv("File::Raw::Archive::Writer", GV_ADD));
2076 38 50         XPUSHs(sv_2mortal(obj));
2077 38           XSRETURN(1);
2078             }
2079              
2080             # ====================================================================
2081             MODULE = File::Raw::Archive PACKAGE = File::Raw::Archive::Reader
2082             # ====================================================================
2083              
2084             SV *
2085             next(self_sv)
2086             SV *self_sv
2087             PREINIT:
2088             AV *self;
2089             SV *handle_sv;
2090             archive_handle_t *h;
2091             SV **handle_ref, **closed_ref, **cur_entry_ref, **consumed_ref;
2092             CODE:
2093 77 50         if (!SvROK(self_sv) || SvTYPE(SvRV(self_sv)) != SVt_PVAV)
    50          
2094 0           croak("File::Raw::Archive::Reader::next: invalid invocant");
2095 77           self = (AV *)SvRV(self_sv);
2096              
2097 77           closed_ref = av_fetch(self, R_CLOSED, 0);
2098 77 50         if (closed_ref && *closed_ref && SvTRUE(*closed_ref))
    50          
    100          
2099 1           croak("File::Raw::Archive::Reader::next: reader is closed");
2100              
2101 76           handle_ref = av_fetch(self, R_HANDLE, 0);
2102 76 50         if (!handle_ref || !*handle_ref)
    50          
2103 0           croak("File::Raw::Archive::Reader::next: missing handle");
2104 76           handle_sv = *handle_ref;
2105 76           h = unwrap_handle(aTHX_ handle_sv);
2106 76 50         if (!h || h->is_writer)
    50          
2107 0           croak("File::Raw::Archive::Reader::next: invalid handle");
2108              
2109             /* Drain previous entry's payload if not consumed. */
2110 76           cur_entry_ref = av_fetch(self, R_CUR_ENTRY, 0);
2111 76           consumed_ref = av_fetch(self, R_CONSUMED, 0);
2112 76 100         if (cur_entry_ref && *cur_entry_ref && SvOK(*cur_entry_ref) &&
    50          
    50          
    50          
2113 42 50         (!consumed_ref || !*consumed_ref || !SvTRUE(*consumed_ref))) {
    100          
2114             char buf[16 * 1024];
2115             int n;
2116 22           while ((n = h->plugin->read_data(aTHX_ h->plugin, h->cursor,
2117 22 100         buf, sizeof buf)) > 0) {}
2118 13 50         if (n < 0)
2119 0           croak("File::Raw::Archive::Reader::next: read_data failed");
2120             }
2121              
2122             {
2123             ArchiveEntry e;
2124 76           int rc = h->plugin->read_next(aTHX_ h->plugin, h->cursor, &e);
2125 76 50         if (rc < 0)
2126 0           croak("File::Raw::Archive: malformed archive");
2127 76 100         if (rc == 0) {
2128 11           av_store(self, R_CUR_ENTRY, &PL_sv_undef);
2129 11           av_store(self, R_CONSUMED, newSViv(1));
2130 11           XSRETURN_UNDEF;
2131             }
2132 65           SV *entry_rv = entry_to_av(aTHX_ &e);
2133 65           SV *entry_obj = build_entry_obj(aTHX_ self_sv, entry_rv);
2134 65           av_store(self, R_CUR_ENTRY, SvREFCNT_inc(entry_obj));
2135 65           av_store(self, R_CONSUMED, newSViv(0));
2136 65           RETVAL = entry_obj;
2137             }
2138             OUTPUT:
2139             RETVAL
2140              
2141             SV *
2142             _handle(self_sv)
2143             SV *self_sv
2144             PREINIT:
2145             AV *self;
2146             SV **handle_ref;
2147             CODE:
2148 0 0         if (!SvROK(self_sv) || SvTYPE(SvRV(self_sv)) != SVt_PVAV)
    0          
2149 0           XSRETURN_UNDEF;
2150 0           self = (AV *)SvRV(self_sv);
2151 0           handle_ref = av_fetch(self, R_HANDLE, 0);
2152 0 0         if (!handle_ref || !*handle_ref) XSRETURN_UNDEF;
    0          
2153 0           RETVAL = SvREFCNT_inc(*handle_ref);
2154             OUTPUT:
2155             RETVAL
2156              
2157             void
2158             _mark_consumed(self_sv)
2159             SV *self_sv
2160             PREINIT:
2161             AV *self;
2162             CODE:
2163 0 0         if (!SvROK(self_sv) || SvTYPE(SvRV(self_sv)) != SVt_PVAV) return;
    0          
2164 0           self = (AV *)SvRV(self_sv);
2165 0           av_store(self, R_CONSUMED, newSViv(1));
2166              
2167             void
2168             close(self_sv)
2169             SV *self_sv
2170             PREINIT:
2171             AV *self;
2172             SV **closed_ref, **handle_ref;
2173             CODE:
2174 34 50         if (!SvROK(self_sv)) return;
2175 34 50         if (SvTYPE(SvRV(self_sv)) != SVt_PVAV) {
2176             /* Bare blessed-IV-ref form: close it directly. */
2177 0           maybe_free_handle(aTHX_ self_sv);
2178 0           return;
2179             }
2180 34           self = (AV *)SvRV(self_sv);
2181 34           closed_ref = av_fetch(self, R_CLOSED, 0);
2182 34 50         if (closed_ref && *closed_ref && SvTRUE(*closed_ref)) return;
    50          
    100          
2183 33           av_store(self, R_CLOSED, newSViv(1));
2184             /* Clear cur_entry so any stashed entry's slurp fails cleanly
2185             * rather than reading from a freed handle. */
2186 33           av_store(self, R_CUR_ENTRY, &PL_sv_undef);
2187 33           handle_ref = av_fetch(self, R_HANDLE, 0);
2188 33 50         if (handle_ref && *handle_ref) maybe_free_handle(aTHX_ *handle_ref);
    50          
2189 33           av_store(self, R_HANDLE, &PL_sv_undef);
2190              
2191             void
2192             DESTROY(self_sv)
2193             SV *self_sv
2194             PREINIT:
2195             AV *self;
2196             SV **closed_ref, **handle_ref;
2197             CODE:
2198 80 50         if (!SvROK(self_sv)) return;
2199 80 100         if (SvTYPE(SvRV(self_sv)) != SVt_PVAV) {
2200             /* Bare blessed-IV-ref handle: free underlying C struct now. */
2201 40           maybe_free_handle(aTHX_ self_sv);
2202 40           return;
2203             }
2204 40           self = (AV *)SvRV(self_sv);
2205 40           closed_ref = av_fetch(self, R_CLOSED, 0);
2206 40 50         if (closed_ref && *closed_ref && SvTRUE(*closed_ref)) return;
    50          
    100          
2207 7           handle_ref = av_fetch(self, R_HANDLE, 0);
2208 7 50         if (handle_ref && *handle_ref) maybe_free_handle(aTHX_ *handle_ref);
    50          
2209              
2210             # ====================================================================
2211             MODULE = File::Raw::Archive PACKAGE = File::Raw::Archive::Writer
2212             # ====================================================================
2213              
2214             void
2215             add(...)
2216             PPCODE:
2217             {
2218 678 50         if (items < 1)
2219 0           croak("Usage: \\$writer->add(name => ..., content => ..., ...)");
2220 678           SV *self_sv = ST(0);
2221 678 50         if (!SvROK(self_sv) || SvTYPE(SvRV(self_sv)) != SVt_PVAV)
    50          
2222 0           croak("File::Raw::Archive::Writer::add: invalid invocant");
2223 678           AV *self = (AV *)SvRV(self_sv);
2224 678 50         if ((items - 1) % 2 != 0)
2225 0           croak("File::Raw::Archive::Writer::add: odd number of fields");
2226              
2227 678           HV *fields = newHV();
2228 678           sv_2mortal((SV *)fields);
2229 678           SV *content_sv = NULL;
2230             int i;
2231 2635 100         for (i = 1; i + 1 < items; i += 2) {
2232             STRLEN klen;
2233 1957           const char *kp = SvPV(ST(i), klen);
2234 1957 100         if (klen == 7 && memcmp(kp, "content", 7) == 0) {
    50          
2235 669           content_sv = ST(i + 1);
2236             } else {
2237 1288           hv_store(fields, kp, klen, newSVsv(ST(i + 1)), 0);
2238             }
2239             }
2240              
2241             /* Default type by name suffix or link_target presence. */
2242             {
2243 678           SV **type_ref = hv_fetchs(fields, "type", 0);
2244 678 100         if (!type_ref || !*type_ref || !SvOK(*type_ref)) {
    50          
    50          
2245 677           int type = AE_FILE;
2246 677           SV **name_ref = hv_fetchs(fields, "name", 0);
2247 677           SV **link_ref = hv_fetchs(fields, "link_target", 0);
2248 677 100         int has_link = (link_ref && *link_ref && SvOK(*link_ref));
    50          
    50          
2249 1354 50         if (name_ref && *name_ref && SvOK(*name_ref)) {
    50          
    50          
2250             STRLEN nl;
2251 677           const char *np = SvPV(*name_ref, nl);
2252 677 50         if (nl > 0 && np[nl - 1] == '/') type = AE_DIR;
    100          
2253 672 100         else if (has_link) type = AE_SYMLINK;
2254 669           else type = AE_FILE;
2255 0 0         } else if (has_link) {
2256 0           type = AE_SYMLINK;
2257             }
2258 677           hv_stores(fields, "type", newSViv(type));
2259             }
2260             }
2261             /* Default mode by type. */
2262             {
2263 678           SV **mode_ref = hv_fetchs(fields, "mode", 0);
2264 678 100         if (!mode_ref || !*mode_ref || !SvOK(*mode_ref)) {
    50          
    50          
2265 102           SV **t = hv_fetchs(fields, "type", 0);
2266 102 50         int type = (t && *t) ? (int)SvIV(*t) : AE_FILE;
    50          
2267 102           IV mode = (type == AE_DIR ? 0755
2268 102 100         : type == AE_SYMLINK ? 0777
    100          
2269             : 0644);
2270 102           hv_stores(fields, "mode", newSViv(mode));
2271             }
2272             }
2273              
2274 678           SV **handle_ref = av_fetch(self, W_HANDLE, 0);
2275 678 50         if (!handle_ref || !*handle_ref)
    50          
2276 0           croak("File::Raw::Archive::Writer::add: missing handle");
2277 678           archive_handle_t *h = unwrap_handle(aTHX_ *handle_ref);
2278 678 50         if (!h || !h->is_writer)
    50          
2279 0           croak("File::Raw::Archive::Writer::add: invalid handle");
2280              
2281             ArchiveEntry e;
2282             ArchiveXattr xbuf[64];
2283 678           size_t xused = 0;
2284 678           hv_to_entry(aTHX_ fields, &e, xbuf, sizeof xbuf / sizeof xbuf[0], &xused);
2285              
2286 678           STRLEN clen = 0;
2287 678           const char *cp = NULL;
2288 678 100         if (content_sv && SvOK(content_sv)) {
    50          
2289 669           cp = SvPV(content_sv, clen);
2290 669 50         if (e.size == 0 && clen > 0) e.size = clen;
    50          
2291             }
2292              
2293 678 100         if (h->plugin->write_add(aTHX_ h->plugin, h->cursor, &e, cp, clen) < 0)
2294 5           croak("File::Raw::Archive::Writer::add: write failed (entry exceeds format limits?)");
2295              
2296 673           XSRETURN_YES;
2297             }
2298              
2299             void
2300             close(self_sv)
2301             SV *self_sv
2302             PREINIT:
2303             AV *self;
2304             SV **closed_ref, **handle_ref;
2305             CODE:
2306 39 50         if (!SvROK(self_sv)) return;
2307 39 50         if (SvTYPE(SvRV(self_sv)) != SVt_PVAV) {
2308 0           maybe_free_handle(aTHX_ self_sv);
2309 0           return;
2310             }
2311 39           self = (AV *)SvRV(self_sv);
2312 39           closed_ref = av_fetch(self, W_CLOSED, 0);
2313 39 50         if (closed_ref && *closed_ref && SvTRUE(*closed_ref)) return;
    50          
    50          
2314 39           av_store(self, W_CLOSED, newSViv(1));
2315 39           handle_ref = av_fetch(self, W_HANDLE, 0);
2316 39 50         if (handle_ref && *handle_ref) maybe_free_handle(aTHX_ *handle_ref);
    50          
2317 39           av_store(self, W_HANDLE, &PL_sv_undef);
2318              
2319             void
2320             DESTROY(self_sv)
2321             SV *self_sv
2322             PREINIT:
2323             AV *self;
2324             SV **closed_ref, **handle_ref;
2325             CODE:
2326 78 50         if (!SvROK(self_sv)) return;
2327 78 100         if (SvTYPE(SvRV(self_sv)) != SVt_PVAV) {
2328 39           maybe_free_handle(aTHX_ self_sv);
2329 39           return;
2330             }
2331 39           self = (AV *)SvRV(self_sv);
2332 39           closed_ref = av_fetch(self, W_CLOSED, 0);
2333 39 50         if (closed_ref && *closed_ref && SvTRUE(*closed_ref)) return;
    50          
    50          
2334 0           handle_ref = av_fetch(self, W_HANDLE, 0);
2335 0 0         if (handle_ref && *handle_ref) maybe_free_handle(aTHX_ *handle_ref);
    0          
2336              
2337             # ====================================================================
2338             MODULE = File::Raw::Archive PACKAGE = File::Raw::Archive::Entry
2339             # ====================================================================
2340             #
2341             # Entry is a blessed hashref with:
2342             # reader => Reader hashref (back-link, carries the C handle)
2343             # meta => entry-metadata hashref (name, size, mode, ...)
2344             # slurped => undef OR the cached payload SV
2345             #
2346             # Accessors fetch from `meta`. slurp pulls payload via the reader's
2347             # handle and caches it so a second slurp returns the same bytes.
2348              
2349             # Macro-style helper: read a key from $self->{meta} and return it,
2350             # or undef. Used by every accessor.
2351              
2352             SV *
2353             name(self_sv)
2354             SV *self_sv
2355             ALIAS:
2356             File::Raw::Archive::Entry::name = M_NAME
2357             File::Raw::Archive::Entry::size = M_SIZE
2358             File::Raw::Archive::Entry::mode = M_MODE
2359             File::Raw::Archive::Entry::mtime = M_MTIME
2360             File::Raw::Archive::Entry::mtime_ns = M_MTIME_NS
2361             File::Raw::Archive::Entry::uid = M_UID
2362             File::Raw::Archive::Entry::gid = M_GID
2363             File::Raw::Archive::Entry::type = M_TYPE
2364             File::Raw::Archive::Entry::link_target = M_LINK_TARGET
2365             File::Raw::Archive::Entry::xattrs = M_XATTRS
2366             File::Raw::Archive::Entry::is_sparse = M_IS_SPARSE
2367             PREINIT:
2368             AV *self, *meta;
2369             SV **meta_ref, **val;
2370             CODE:
2371 204 50         if (!SvROK(self_sv) || SvTYPE(SvRV(self_sv)) != SVt_PVAV)
    50          
2372 0           XSRETURN_UNDEF;
2373 204           self = (AV *)SvRV(self_sv);
2374 204           meta_ref = av_fetch(self, E_META, 0);
2375 204 100         if (!meta_ref || !*meta_ref || !SvROK(*meta_ref)
    50          
    50          
2376 203 50         || SvTYPE(SvRV(*meta_ref)) != SVt_PVAV)
2377 1           XSRETURN_UNDEF;
2378 203           meta = (AV *)SvRV(*meta_ref);
2379 203           val = av_fetch(meta, ix, 0);
2380 203 100         if (!val || !*val || !SvOK(*val)) {
    50          
    50          
2381             /* Numeric defaults for the integer-shaped accessors. */
2382 3 50         switch (ix) {
2383 0           case M_SIZE: case M_MODE: case M_MTIME: case M_MTIME_NS:
2384             case M_UID: case M_GID: case M_TYPE: case M_IS_SPARSE:
2385 0           RETVAL = newSViv(0);
2386 0           break;
2387 3           default:
2388 3           XSRETURN_UNDEF;
2389             }
2390             } else {
2391 200           RETVAL = SvREFCNT_inc(*val);
2392             }
2393             OUTPUT:
2394             RETVAL
2395              
2396             IV
2397             is_file(self_sv)
2398             SV *self_sv
2399             ALIAS:
2400             File::Raw::Archive::Entry::is_file = 1
2401             File::Raw::Archive::Entry::is_dir = 2
2402             File::Raw::Archive::Entry::is_symlink = 3
2403             File::Raw::Archive::Entry::is_link = 4
2404             PREINIT:
2405             AV *self, *meta;
2406             SV **meta_ref, **type_ref;
2407             IV t;
2408             CODE:
2409 35 50         if (!SvROK(self_sv) || SvTYPE(SvRV(self_sv)) != SVt_PVAV)
    50          
2410 0           XSRETURN_IV(0);
2411 35           self = (AV *)SvRV(self_sv);
2412 35           meta_ref = av_fetch(self, E_META, 0);
2413 35 50         if (!meta_ref || !*meta_ref || !SvROK(*meta_ref)
    50          
    50          
2414 35 50         || SvTYPE(SvRV(*meta_ref)) != SVt_PVAV)
2415 0           XSRETURN_IV(0);
2416 35           meta = (AV *)SvRV(*meta_ref);
2417 35           type_ref = av_fetch(meta, M_TYPE, 0);
2418 35 50         t = (type_ref && *type_ref && SvOK(*type_ref))
    50          
2419 70 50         ? SvIV(*type_ref) : (IV)AE_FILE;
2420 35           switch (ix) {
2421 12           case 1: RETVAL = (t == AE_FILE) ? 1 : 0; break;
2422 7           case 2: RETVAL = (t == AE_DIR) ? 1 : 0; break;
2423 9           case 3: RETVAL = (t == AE_SYMLINK) ? 1 : 0; break;
2424 7 100         case 4: RETVAL = (t == AE_SYMLINK || t == AE_HARDLINK) ? 1 : 0; break;
    50          
2425 0           default: RETVAL = 0;
2426             }
2427             OUTPUT:
2428             RETVAL
2429              
2430             # slurp: pull the entry's payload bytes via the reader's plugin/cursor.
2431             # Memoised in $self->{slurped} so a second call returns the same SV.
2432              
2433             SV *
2434             slurp(self_sv)
2435             SV *self_sv
2436             PREINIT:
2437             AV *self, *reader_av;
2438             SV **slurped_ref, **reader_ref, **handle_ref;
2439             archive_handle_t *h;
2440             char buf[64 * 1024];
2441             int n;
2442             SV *result;
2443             CODE:
2444 40 50         if (!SvROK(self_sv) || SvTYPE(SvRV(self_sv)) != SVt_PVAV)
    50          
2445 0           croak("File::Raw::Archive::Entry::slurp: invalid invocant");
2446 40           self = (AV *)SvRV(self_sv);
2447 40           slurped_ref = av_fetch(self, E_SLURPED, 0);
2448 40 100         if (slurped_ref && *slurped_ref && SvOK(*slurped_ref)) {
    50          
    50          
2449 3           RETVAL = SvREFCNT_inc(*slurped_ref);
2450             } else {
2451 37           reader_ref = av_fetch(self, E_READER, 0);
2452 37 50         if (!reader_ref || !*reader_ref || !SvROK(*reader_ref)
    50          
    50          
2453 37 50         || SvTYPE(SvRV(*reader_ref)) != SVt_PVAV)
2454 0           croak("File::Raw::Archive::Entry::slurp: missing reader");
2455 37           reader_av = (AV *)SvRV(*reader_ref);
2456 37           handle_ref = av_fetch(reader_av, R_HANDLE, 0);
2457 37 50         if (!handle_ref || !*handle_ref)
    50          
2458 0           croak("File::Raw::Archive::Entry::slurp: missing handle");
2459 37           h = unwrap_handle(aTHX_ *handle_ref);
2460 37 100         if (!h || h->is_writer)
    50          
2461 1           croak("File::Raw::Archive::Entry::slurp: invalid handle");
2462 36           result = newSVpvn("", 0);
2463 230           while ((n = h->plugin->read_data(aTHX_ h->plugin, h->cursor,
2464 230 100         buf, sizeof buf)) > 0) {
2465 194           sv_catpvn(result, buf, n);
2466             }
2467 36 50         if (n < 0) {
2468 0           SvREFCNT_dec(result);
2469 0           croak("File::Raw::Archive::Entry::slurp: read_data failed");
2470             }
2471             /* Memoise + mark reader's cur_entry as consumed. */
2472 36           av_store(self, E_SLURPED, SvREFCNT_inc(result));
2473 36           av_store(reader_av, R_CONSUMED, newSViv(1));
2474 36           RETVAL = result;
2475             }
2476             OUTPUT:
2477             RETVAL
2478              
2479             SV *
2480             read(self_sv, n_sv)
2481             SV *self_sv
2482             SV *n_sv
2483             PREINIT:
2484             AV *self, *reader_av;
2485             SV **reader_ref, **handle_ref;
2486             archive_handle_t *h;
2487             UV want;
2488             SV *result;
2489             char buf[64 * 1024];
2490             int got;
2491             CODE:
2492 45 50         if (!SvROK(self_sv) || SvTYPE(SvRV(self_sv)) != SVt_PVAV)
    50          
2493 0           croak("File::Raw::Archive::Entry::read: invalid invocant");
2494 45           self = (AV *)SvRV(self_sv);
2495 45           reader_ref = av_fetch(self, E_READER, 0);
2496 45 50         if (!reader_ref || !*reader_ref || !SvROK(*reader_ref)
    50          
    50          
2497 45 50         || SvTYPE(SvRV(*reader_ref)) != SVt_PVAV)
2498 0           croak("File::Raw::Archive::Entry::read: missing reader");
2499 45           reader_av = (AV *)SvRV(*reader_ref);
2500 45           handle_ref = av_fetch(reader_av, R_HANDLE, 0);
2501 45 50         if (!handle_ref || !*handle_ref)
    50          
2502 0           croak("File::Raw::Archive::Entry::read: missing handle");
2503 45           h = unwrap_handle(aTHX_ *handle_ref);
2504 45 50         if (!h || h->is_writer)
    50          
2505 0           croak("File::Raw::Archive::Entry::read: invalid handle");
2506 45 50         if (!n_sv || !SvOK(n_sv))
    50          
2507 0           croak("File::Raw::Archive::Entry::read: byte count required");
2508 45           want = SvUV(n_sv);
2509 45           result = newSVpvn("", 0);
2510 128 100         while ((UV)SvCUR(result) < want) {
2511 86           size_t chunk = sizeof buf;
2512 86           UV remain = want - (UV)SvCUR(result);
2513 86 100         if (remain < chunk) chunk = (size_t)remain;
2514 86           got = h->plugin->read_data(aTHX_ h->plugin, h->cursor, buf, chunk);
2515 86 50         if (got < 0) {
2516 0           SvREFCNT_dec(result);
2517 0           croak("File::Raw::Archive::Entry::read: read_data failed");
2518             }
2519 86 100         if (got == 0) break;
2520 83           sv_catpvn(result, buf, got);
2521             }
2522 45           RETVAL = result;
2523             OUTPUT:
2524             RETVAL
2525              
2526             void
2527             _skip(self_sv)
2528             SV *self_sv
2529             ALIAS:
2530             File::Raw::Archive::Entry::_skip = 0
2531             File::Raw::Archive::Entry::_drain = 1
2532             PREINIT:
2533             AV *self, *reader_av;
2534             SV **reader_ref, **handle_ref, **consumed_ref;
2535             archive_handle_t *h;
2536             char buf[16 * 1024];
2537             int n;
2538             CODE:
2539             PERL_UNUSED_VAR(ix);
2540 2 50         if (!SvROK(self_sv) || SvTYPE(SvRV(self_sv)) != SVt_PVAV) return;
    50          
2541 2           self = (AV *)SvRV(self_sv);
2542 2           reader_ref = av_fetch(self, E_READER, 0);
2543 2 50         if (!reader_ref || !*reader_ref || !SvROK(*reader_ref)
    50          
    50          
2544 2 50         || SvTYPE(SvRV(*reader_ref)) != SVt_PVAV) return;
2545 2           reader_av = (AV *)SvRV(*reader_ref);
2546 2           consumed_ref = av_fetch(reader_av, R_CONSUMED, 0);
2547 2 50         if (consumed_ref && *consumed_ref && SvTRUE(*consumed_ref)) return;
    50          
    50          
2548 2           handle_ref = av_fetch(reader_av, R_HANDLE, 0);
2549 2 50         if (!handle_ref || !*handle_ref) return;
    50          
2550 2           h = unwrap_handle(aTHX_ *handle_ref);
2551 2 50         if (!h) return;
2552 4           while ((n = h->plugin->read_data(aTHX_ h->plugin, h->cursor,
2553 4 100         buf, sizeof buf)) > 0) {}
2554 2 50         if (n < 0)
2555 0           croak("File::Raw::Archive::Entry::_skip: read_data failed");
2556 2           av_store(reader_av, R_CONSUMED, newSViv(1));
2557              
2558             SV *
2559             _new(class_sv, reader_sv, meta_sv)
2560             SV *class_sv
2561             SV *reader_sv
2562             SV *meta_sv
2563             PREINIT:
2564             AV *self;
2565             const char *cls;
2566             CODE:
2567 0           cls = SvPV_nolen(class_sv);
2568 0           self = newAV();
2569 0           av_extend(self, E_SLOT_COUNT - 1);
2570 0           av_store(self, E_READER, SvREFCNT_inc(reader_sv));
2571 0           av_store(self, E_META, SvREFCNT_inc(meta_sv));
2572 0           RETVAL = sv_bless(newRV_noinc((SV *)self), gv_stashpv(cls, GV_ADD));
2573             OUTPUT:
2574             RETVAL
2575              
2576             # ====================================================================
2577             MODULE = File::Raw::Archive PACKAGE = File::Raw::Archive
2578             # ====================================================================
2579             #
2580             # Function-style entry points: callable as plain functions instead of
2581             # class methods, so they slot into File::Raw's `file_` family.
2582             # Same semantics as the matching class methods minus the leading
2583             # `$class` arg. Imported into the caller's package via `import` below.
2584              
2585             SV *
2586             file_archive_open(...)
2587             PPCODE:
2588             {
2589 1 50         if (items < 1)
2590 0           croak("Usage: file_archive_open(\\$path, %%opts)");
2591 1 50         if ((items - 1) % 2 != 0)
2592 0           croak("file_archive_open: odd number of options");
2593 1           HV *opts = build_opts_from_args(aTHX_ &ST(1), items - 1);
2594 1           archive_handle_t *h = open_reader(aTHX_ ST(0), opts);
2595 1           SV *handle_sv = new_handle_obj(aTHX_ h, "File::Raw::Archive::Reader");
2596              
2597 1           AV *self = newAV();
2598 1           av_extend(self, R_SLOT_COUNT - 1);
2599 1           av_store(self, R_HANDLE, handle_sv);
2600 1           av_store(self, R_CONSUMED, newSViv(1));
2601 1           av_store(self, R_CLOSED, newSViv(0));
2602 1           SV *obj = sv_bless(newRV_noinc((SV *)self),
2603             gv_stashpv("File::Raw::Archive::Reader", GV_ADD));
2604 1 50         XPUSHs(sv_2mortal(obj));
2605 1           XSRETURN(1);
2606             }
2607              
2608             SV *
2609             file_archive_create(...)
2610             PPCODE:
2611             {
2612 1 50         if (items < 1)
2613 0           croak("Usage: file_archive_create(\\$path, %%opts)");
2614 1 50         if ((items - 1) % 2 != 0)
2615 0           croak("file_archive_create: odd number of options");
2616 1           HV *opts = build_opts_from_args(aTHX_ &ST(1), items - 1);
2617 1           archive_handle_t *h = open_writer(aTHX_ ST(0), opts);
2618 1           SV *handle_sv = new_handle_obj(aTHX_ h, "File::Raw::Archive::Writer");
2619              
2620 1           AV *self = newAV();
2621 1           av_extend(self, W_SLOT_COUNT - 1);
2622 1           av_store(self, W_HANDLE, handle_sv);
2623 1           av_store(self, W_CLOSED, newSViv(0));
2624 1           SV *obj = sv_bless(newRV_noinc((SV *)self),
2625             gv_stashpv("File::Raw::Archive::Writer", GV_ADD));
2626 1 50         XPUSHs(sv_2mortal(obj));
2627 1           XSRETURN(1);
2628             }
2629              
2630             SV *
2631             file_archive_list(...)
2632             PPCODE:
2633             {
2634 1 50         if (items < 1)
2635 0           croak("Usage: file_archive_list(\\$path, %%opts)");
2636 1 50         if ((items - 1) % 2 != 0)
2637 0           croak("file_archive_list: odd number of options");
2638 1           HV *opts = build_opts_from_args(aTHX_ &ST(1), items - 1);
2639 1           archive_handle_t *h = open_reader(aTHX_ ST(0), opts);
2640 1           SAVEDESTRUCTOR_X(free_handle_destructor, h);
2641 1           SV *result = do_list(aTHX_ h);
2642 1 50         XPUSHs(sv_2mortal(result));
2643 1           XSRETURN(1);
2644             }
2645              
2646             IV
2647             file_archive_extract(...)
2648             PPCODE:
2649             {
2650 1 50         if (items < 3)
2651 0           croak("Usage: file_archive_extract(\\$path, \\$name, \\$dest, %%opts)");
2652 1 50         if ((items - 3) % 2 != 0)
2653 0           croak("file_archive_extract: odd number of options");
2654 1           HV *opts = build_opts_from_args(aTHX_ &ST(3), items - 3);
2655 1           int apply_xattrs = 1;
2656 1           SV **xv = hv_fetchs(opts, "xattrs", 0);
2657 1 50         if (xv && *xv && SvOK(*xv)) apply_xattrs = SvTRUE(*xv) ? 1 : 0;
    0          
    0          
2658              
2659 1           archive_handle_t *h = open_reader(aTHX_ ST(0), opts);
2660 1           SAVEDESTRUCTOR_X(free_handle_destructor, h);
2661              
2662             STRLEN match_len;
2663 1           const char *match_name = SvPV(ST(1), match_len);
2664 1           const char *dest_path = SvPV_nolen(ST(2));
2665 1           IV rc = do_extract_one(aTHX_ h, match_name, match_len, dest_path,
2666             apply_xattrs);
2667 1 50         XPUSHs(sv_2mortal(newSViv(rc)));
2668 1           XSRETURN(1);
2669             }
2670              
2671             IV
2672             file_archive_extract_all(...)
2673             PPCODE:
2674             {
2675 1 50         if (items < 2)
2676 0           croak("Usage: file_archive_extract_all(\\$path, \\$dest, %%opts)");
2677 1 50         if ((items - 2) % 2 != 0)
2678 0           croak("file_archive_extract_all: odd number of options");
2679 1           HV *opts = build_opts_from_args(aTHX_ &ST(2), items - 2);
2680 1           int apply_xattrs = 1;
2681 1           int unsafe_paths = 0;
2682 1           int parallel = 1;
2683 1           SV *filter = NULL;
2684             SV **xv;
2685              
2686 1           xv = hv_fetchs(opts, "xattrs", 0);
2687 1 50         if (xv && *xv && SvOK(*xv)) apply_xattrs = SvTRUE(*xv) ? 1 : 0;
    0          
    0          
2688 1           xv = hv_fetchs(opts, "unsafe_paths", 0);
2689 1 50         if (xv && *xv && SvOK(*xv)) unsafe_paths = SvTRUE(*xv) ? 1 : 0;
    0          
    0          
2690 1           xv = hv_fetchs(opts, "entry_filter", 0);
2691 1 50         if (xv && *xv && SvOK(*xv)) filter = *xv;
    0          
    0          
2692 1           xv = hv_fetchs(opts, "parallel", 0);
2693 1 50         if (xv && *xv && SvOK(*xv)) parallel = (int)SvIV(*xv);
    0          
    0          
2694              
2695 1 50         if (parallel > 1 && !parallel_supported()) {
    0          
2696 0           warn("File::Raw::Archive: parallel extract not supported on this "
2697             "platform; falling back to sequential\n");
2698 0           parallel = 1;
2699             }
2700              
2701 1           archive_handle_t *h = open_reader(aTHX_ ST(0), opts);
2702 1           SAVEDESTRUCTOR_X(free_handle_destructor, h);
2703              
2704             STRLEN dest_len;
2705 1           const char *dest = SvPV(ST(1), dest_len);
2706 1 50         if (parallel > 1) {
2707 0           do_extract_all_parallel(aTHX_ h, dest, dest_len,
2708             parallel, apply_xattrs,
2709             filter, unsafe_paths);
2710             } else {
2711 1           do_extract_all_seq(aTHX_ h, dest, dest_len,
2712             apply_xattrs, unsafe_paths, filter);
2713             }
2714 1 50         XPUSHs(sv_2mortal(newSViv(1)));
2715 1           XSRETURN(1);
2716             }
2717              
2718             void
2719             file_archive_each(...)
2720             PPCODE:
2721             {
2722 1 50         if (items < 2)
2723 0           croak("Usage: file_archive_each(\\$path, %%opts, sub { ... })");
2724 1           SV *cb_sv = ST(items - 1);
2725 1 50         if (!SvROK(cb_sv) || SvTYPE(SvRV(cb_sv)) != SVt_PVCV)
    50          
2726 0           croak("file_archive_each: last arg must be a coderef");
2727 1           int opts_end = items - 1;
2728 1 50         if ((opts_end - 1) % 2 != 0)
2729 0           croak("file_archive_each: odd number of options");
2730 1           HV *opts = build_opts_from_args(aTHX_ &ST(1), opts_end - 1);
2731 1           SV *filter = NULL;
2732 1           SV **xv = hv_fetchs(opts, "entry_filter", 0);
2733 1 50         if (xv && *xv && SvOK(*xv)) filter = *xv;
    0          
    0          
2734              
2735 1           archive_handle_t *h = open_reader(aTHX_ ST(0), opts);
2736              
2737             /* Build a Reader AV equivalent so Entry objects can call back. */
2738 1           AV *reader_av = newAV();
2739 1           av_extend(reader_av, R_SLOT_COUNT - 1);
2740 1           av_store(reader_av, R_HANDLE,
2741             new_handle_obj(aTHX_ h, "File::Raw::Archive::Reader"));
2742 1           av_store(reader_av, R_CONSUMED, newSViv(1));
2743 1           av_store(reader_av, R_CLOSED, newSViv(0));
2744 1           SV *reader_sv = sv_2mortal(newRV_noinc((SV *)reader_av));
2745 1           sv_bless(reader_sv, gv_stashpv("File::Raw::Archive::Reader", GV_ADD));
2746              
2747 1           do_each(aTHX_ h, reader_sv, cb_sv, filter);
2748 1           XSRETURN_EMPTY;
2749             }
2750              
2751             # Public surface installer. Called as
2752             # use File::Raw::Archive qw(import); # all six
2753             # or use File::Raw::Archive qw(each list); # specific subset
2754             # Walks the requested name list and aliases each into the caller's
2755             # stash as `file_archive_`.
2756              
2757             void
2758             import(...)
2759             PPCODE:
2760             {
2761             /* ST(0) is the class. The rest are export-tag style names. */
2762             static const char * const known_names[] = {
2763             "open", "create", "list", "each", "extract", "extract_all", NULL
2764             };
2765             HV *caller_stash;
2766             {
2767 30           const char *caller_pkg = NULL;
2768 30           const PERL_CONTEXT *cx = caller_cx(0, NULL);
2769 30 50         if (cx && CxTYPE(cx) == CXt_SUB) {
    50          
2770 30 50         caller_pkg = HvNAME_get(CopSTASH(cx->blk_oldcop));
    50          
    50          
    0          
    50          
    50          
2771             }
2772 30 50         if (!caller_pkg) caller_pkg = "main";
2773 30           caller_stash = gv_stashpv(caller_pkg, GV_ADD);
2774             }
2775              
2776             /* If only the bareword "import" is requested, install all known
2777             * names. Otherwise install just the requested subset. */
2778 30           int install_all = 0;
2779             int i;
2780 30 100         for (i = 1; i < items; i++) {
2781 1 50         if (!SvOK(ST(i))) continue;
2782             STRLEN nl;
2783 1           const char *np = SvPV(ST(i), nl);
2784 1 50         if ((nl == 6 && memcmp(np, "import", 6) == 0)
    50          
2785 0 0         || (nl == 4 && memcmp(np, ":all", 4) == 0)) {
    0          
2786 1           install_all = 1;
2787 1           break;
2788             }
2789             }
2790              
2791             int n;
2792 210 100         for (n = 0; known_names[n]; n++) {
2793 180           const char *name = known_names[n];
2794 180           int wanted = install_all;
2795 180 100         if (!wanted) {
2796             int j;
2797 174 50         for (j = 1; j < items; j++) {
2798 0 0         if (!SvOK(ST(j))) continue;
2799             STRLEN nl;
2800 0           const char *np = SvPV(ST(j), nl);
2801 0 0         if (nl == strlen(name) && memcmp(np, name, nl) == 0) {
    0          
2802 0           wanted = 1;
2803 0           break;
2804             }
2805             }
2806             }
2807 180 100         if (!wanted) continue;
2808              
2809             /* Source CV: File::Raw::Archive::file_archive_ */
2810             char src_full[128];
2811 6           snprintf(src_full, sizeof src_full,
2812             "File::Raw::Archive::file_archive_%s", name);
2813 6           CV *src_cv = get_cv(src_full, 0);
2814 6 50         if (!src_cv) {
2815 0           warn("File::Raw::Archive::import: %s not installed at BOOT",
2816             src_full);
2817 0           continue;
2818             }
2819              
2820             /* Destination CV name: file_archive_ in caller's stash. */
2821             char dst_short[64];
2822 6           snprintf(dst_short, sizeof dst_short, "file_archive_%s", name);
2823 6           GV *dst_gv = (GV *)*hv_fetch(caller_stash, dst_short,
2824             (I32)strlen(dst_short), 1);
2825 6 50         if (!isGV(dst_gv)) gv_init(dst_gv, caller_stash, dst_short,
2826             strlen(dst_short), GV_ADDMULTI);
2827 6           GvCV_set(dst_gv, src_cv);
2828 6           SvREFCNT_inc((SV *)src_cv);
2829             }
2830 30           XSRETURN_EMPTY;
2831             }
2832              
2833              
2834              
2835