File Coverage

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