1 /*@ file_case: input file encapsulator
3 * Copyright (c) 2014 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
34 #include "file_case.h"
38 #ifdef HAVE_DECL_GETC_UNLOCKED
39 # define a_getc getc_unlocked
44 // Support decompression XXX configure should say `no popen() - no unpacking'
49 // (Enclosed by HAVE_UNPACK) Directly support decompression library layer?
50 // XXX We yet only support a zlib a_LAYER, which is why we directly address zlib
51 // XXX functions instead of furtherly abstracting into a struct iolayer or sth.
52 // XXX If we would, that can only have read_buf() and close() and we should
53 // XXX deal with buffer handling entirely ourselfs, in which case even the
54 // XXX popen(3) code path could be enwrapped into struct iolayer; i.e., then
55 // XXX the entire public read interface could internally be driven by iolayer
68 char const *a_mode
; // Mode for fopen(3), if used
76 uint8_t zp_ext_len
; // Extension including `.' (<period>)
78 uint8_t zp_layer
; // Uses I/O layer (zlib)
83 static a_zproc
const a_zprocs
[] = {
85 # define a_X(L,C,E) {true, sizeof(E) -1, sizeof(C) -1, L, E, C}
86 # ifdef HAVE_UNPACK_BZ2
87 a_X(0, "bzip2 -cdf", ".bz2"),
89 # ifdef HAVE_UNPACK_GZ
90 a_X(HAVE_ZLIB
, "gzip -cdf", ".gz"),
92 # ifdef HAVE_UNPACK_XZ
93 a_X(0, "xz -cdf", ".xz")
100 // Check whether path was explicitly specified with a packer extension.
101 // This returns a ternary: false only if we knew the extension, applied the
102 // zproc and that failed to perform, true otherwise (ap->a_fp is 2nd indicator)
103 static bool a_is_ext(a_args
*ap
);
105 // Plain file didn't exist, iterate over the supported packer extensions
106 // and see if a matching file exists instead; NULL if not / on error.
107 // Note that ap->a_errno is ENOENT on entry and only overwritten if we run
108 // a zproc and that fails XXX ENOENT is blindly used in codebase, but not ISO C
109 static bool a_try_all_ext(a_args
*ap
);
111 // Create a FILE* according to zp, return NULL on error
112 static a_args
*a__run_zproc(a_args
*ap
, a_zproc
const *zp
);
114 // Callee needs seek()ing or STD I/O, unpack into temporary file, NULL on error
115 static a_args
*a__unpack(a_args
*ap
);
116 #endif // HAVE_UNPACK
120 a_is_ext(a_args
*ap
){
121 for(a_zproc
const *zp
= a_zprocs
; zp
< a_zprocs
+ NELEM(a_zprocs
); ++zp
){
122 size_t el
= zp
->zp_ext_len
;
123 if(ap
->a_path_len
<= el
)
125 if(memcmp(ap
->a_path
+ ap
->a_path_len
- el
, zp
->zp_ext
, el
))
128 ap
= a__run_zproc(ap
, zp
);
135 a_try_all_ext(a_args
*ap
){
136 for(a_zproc
const *zp
= a_zprocs
; zp
< a_zprocs
+ NELEM(a_zprocs
); ++zp
){
137 char *np
= new char[ap
->a_path_len
+ zp
->zp_ext_len
+1];
138 memcpy(np
, ap
->a_path
, ap
->a_path_len
);
141 memcpy(np
+ ap
->a_path_len
, zp
->zp_ext
, zp
->zp_ext_len
+1);
147 // That's our zproc, let it make the deal
148 char const *pb_save
= ap
->a_path
;
149 size_t pl_save
= ap
->a_path_len
;
151 ap
->a_path_len
= pl_save
+ zp
->zp_ext_len
;
152 if((ap
= a__run_zproc(ap
, zp
)) != NULL
){
153 ap
->a_path
= pb_save
;
154 ap
->a_path_len
= pl_save
;
165 a__run_zproc(a_args
*ap
, a_zproc
const *zp
){
168 if((ap
->a_layer
= gzopen(ap
->a_path
, "rb")) == NULL
){
171 }else if(ap
->a_flags
&
172 (file_case::mux_need_seek
| file_case::mux_need_stdio
))
176 char *np
= new char[zp
->zp_cmd_len
+ 1 + ap
->a_path_len
+1];
178 memcpy(np
, zp
->zp_cmd
, l
= zp
->zp_cmd_len
);
180 memcpy(np
+ l
, ap
->a_path
, ap
->a_path_len
+1);
182 if((ap
->a_fp
= popen(np
, "r")) == NULL
){
185 }else if(ap
->a_flags
& file_case::mux_need_seek
)
188 ap
->a_flags
|= file_case::fc_pipe
| file_case::fc_have_stdio
;
199 a__unpack(a_args
*ap
){
200 size_t const buf_len
= (BUFSIZ
+ 0 > 1 << 15) ? BUFSIZ
: 1 << 15;
201 uint8_t *buf
= new uint8_t[buf_len
];
203 // xtmpfile uses binary mode and fatal()s on error
204 FILE *decomp
= xtmpfile(NULL
, "groff_unpack"), *decomp_save
= decomp
;
209 if(ap
->a_layer
!= NULL
){
210 int i
= gzread((gzFile
)ap
->a_layer
, buf
, buf_len
);
220 if((oc
= fread(buf
, sizeof *buf
, buf_len
, ap
->a_fp
)) == 0){
229 for(uint8_t *target
= buf
; oc
> 0;){
230 size_t i
= fwrite(target
, sizeof *buf
, oc
, decomp
);
244 if(ap
->a_layer
!= NULL
){
245 if(gzclose((gzFile
)ap
->a_layer
) != Z_OK
)
246 error("decompressor gzclose(3) failed");
250 if(pclose(ap
->a_fp
) != 0)
251 error("decompressor pipe pclose(3) didn't exit cleanly");
254 ap
->a_flags
|= file_case::fc_have_stdio
;
255 rewind(ap
->a_fp
= decomp
);
265 #endif // HAVE_UNPACK
268 file_case::close(void){
269 assert((_file
!= NULL
&& _layer
== NULL
) ||
270 (_file
== NULL
&& _layer
!= NULL
));
272 if(!(_flags
& fc_const_path
))
276 if(_flags
& fc_dont_close
)
279 else if(_layer
!= NULL
)
280 rv
= (gzclose((gzFile
)_layer
) == Z_OK
);
283 else if(_flags
& fc_pipe
)
284 rv
= (pclose(_file
) == 0);
287 rv
= (fclose(_file
) == 0);
299 file_case::is_eof(void) const{
303 rv
= (gzeof((gzFile
)_layer
) != 0);
306 rv
= (feof(_file
) != 0);
311 file_case::get_c(void){
315 rv
= gzgetc((gzFile
)_layer
);
323 file_case::unget_c(int c
){
327 rv
= gzungetc(c
, (gzFile
)_layer
);
330 rv
= ungetc(c
, _file
);
335 file_case::get_line(char *buf
, size_t buf_size
){
338 buf
= gzgets((gzFile
)_layer
, buf
, (int)buf_size
);
341 buf
= fgets(buf
, (int)buf_size
, _file
);
346 file_case::get_buf(void *buf
, size_t buf_size
){
350 int i
= gzread((gzFile
)_layer
, buf
, (unsigned int)buf_size
);
351 rv
= (i
<= 0) ? 0 : (size_t)i
;
354 rv
= fread(buf
, 1, buf_size
, _file
);
359 file_case::rewind(void){
362 gzrewind((gzFile
)_layer
);
369 file_case::seek(long offset
, seek_whence whence
){
370 int x
= (whence
== seek_set
? SEEK_SET
:
371 (whence
== seek_cur
? SEEK_CUR
: SEEK_END
));
374 x
= gzseek((gzFile
)_layer
, (z_off_t
)offset
, x
);
377 x
= fseek(_file
, offset
, x
);
381 /*static*/ file_case
*
382 file_case::muxer(char const *path
, uint32_t flags
){
383 enum {tmpbit
= 1<<(_mux_freebit
+0)};
385 assert(!(flags
& (fc_dont_close
| fc_pipe
)));
386 assert(!(flags
& (fc_const_path
| fc_take_path
)) ||
387 !(flags
& fc_const_path
) != !(flags
& fc_take_path
));
388 assert(!(flags
& (mux_unpack
| mux_no_unpack
)) ||
389 !(flags
& mux_unpack
) != !(flags
& mux_no_unpack
));
391 if(path
== NULL
|| (path
[0] == '-' && path
[1] == '\0')){
393 flags
&= ~fc_take_path
;
394 flags
|= fc_const_path
| tmpbit
;
395 }else if(!(flags
& (fc_const_path
| fc_take_path
))){
396 path
= strsave(path
);
397 flags
|= fc_take_path
;
399 if(!(flags
& (mux_unpack
| mux_no_unpack
)))
400 flags
|= _mux_unpack_default
;
406 a
.a_path_len
= strlen(a
.a_path
= path
);
407 a
.a_mode
= (flags
& mux_need_binary
) ? "rb" : "r";
411 // Shorthand: support "-" to mean stdin
414 if(flags
& mux_need_binary
)
415 SET_BINARY(fileno(stdin
));
417 a
.a_flags
|= fc_dont_close
| fc_const_path
| fc_have_stdio
;
421 // If we support unpacking then check whether the path already includes
422 // a packer's extension, i.e., explicitly. Anyway unpack then, despite flags
425 assert(a
.a_fp
== NULL
&& a
.a_layer
== NULL
);
428 if(a
.a_fp
!= NULL
|| a
.a_layer
!= NULL
)
434 if((a
.a_fp
= fopen(a
.a_path
, a
.a_mode
)) != NULL
){
435 a
.a_flags
|= fc_have_stdio
;
437 assert((a
.a_fp
!= NULL
&& a
.a_layer
== NULL
) ||
438 (a
.a_fp
== NULL
&& a
.a_layer
!= NULL
));
439 fcp
= new file_case(a
.a_fp
, path
, a
.a_flags
& fc_mask
); // XXX real path?
440 fcp
->_layer
= a
.a_layer
;
445 // Then auto-expand the given path if so desired
447 if(a
.a_errno
== ENOENT
&& (a
.a_flags
& mux_unpack
) && a_try_all_ext(&a
))
452 if(!(a
.a_flags
& fc_const_path
))