Sync-to-go: update copyright for 2015
[s-roff.git] / src / lib-roff / file_case.cpp
blob8c4460f1f6473fbc849d01ccbd73545bf90ded3f
1 /*@ file_case: input file encapsulator
3 * Copyright (c) 2014 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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.
18 #include "config.h"
19 #include "lib.h"
21 #include <assert.h>
22 #include <errno.h>
23 #include <stdio.h>
25 #ifdef HAVE_ZLIB
26 # include <zlib.h>
27 #endif
29 #include "errarg.h"
30 #include "error.h"
31 #include "posix.h"
32 #include "nonposix.h"
34 #include "file_case.h"
36 #undef getc
37 #undef _getc
38 #ifdef HAVE_DECL_GETC_UNLOCKED
39 # define _getc getc_unlocked
40 #else
41 # define _getc fgetc
42 #endif
44 // Support decompression XXX configure should say `no popen() - no unpacking'
45 #ifdef POPEN_MISSING
46 # undef HAVE_UNPACK
47 #endif
49 // (Enclosed by HAVE_UNPACK) Directly support decompression library layer?
50 // XXX We yet only support a zlib _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
56 #ifndef HAVE_ZLIB
57 # define HAVE_ZLIB 0
58 #endif
59 #if HAVE_ZLIB
60 # define _LAYER
61 #endif
63 struct args {
64 FILE *a_fp;
65 void *a_layer;
66 char const *a_path;
67 size_t a_path_len;
68 char const *a_mode; // Mode for fopen(3), if used
69 uint32_t a_flags;
70 int32_t a_errno;
73 #ifdef HAVE_UNPACK
74 struct zproc {
75 uint8_t zp_popen;
76 uint8_t zp_ext_len; // Extension including `.' (<period>)
77 uint8_t zp_cmd_len;
78 uint8_t zp_layer; // Uses I/O layer (zlib)
79 char zp_ext[5];
80 char zp_cmd[15];
83 static zproc const _zprocs[] = {
84 # define __X(L,C,E) {true, sizeof(E) -1, sizeof(C) -1, L, E, C}
85 # ifdef HAVE_UNPACK_BZ2
86 __X(0, "bzip2 -cdf", ".bz2"),
87 # endif
88 # ifdef HAVE_UNPACK_GZ
89 __X(HAVE_ZLIB, "gzip -cdf", ".gz"),
90 # endif
91 # ifdef HAVE_UNPACK_XZ
92 __X(0, "xz -cdf", ".xz")
93 # endif
94 # undef __X
96 #endif // HAVE_UNPACK
98 #ifdef HAVE_UNPACK
99 // Check wether path was explicitly specified with a packer extension.
100 // This returns a ternary: false only if we knew the extension, applied the
101 // zproc and that failed to perform, true otherwise (ap->a_fp is 2nd indicator)
102 static bool _is_ext(args *ap);
104 // Plain file didn't exist, iterate over the supported packer extensions
105 // and see if a matching file exists instead; NULL if not / on error.
106 // Note that ap->a_errno is ENOENT on entry and only overwritten if we run
107 // a zproc and that fails XXX ENOENT is blindly used in codebase, but not ISO C
108 static bool _try_all_ext(args *ap);
110 // Create a FILE* according to zp, return NULL on error
111 static args * __run_zproc(args *ap, zproc const *zp);
113 // Callee needs seek()ing or STD I/O, unpack into temporary file, NULL on error
114 static args * __unpack(args *ap);
115 #endif // HAVE_UNPACK
117 #ifdef HAVE_UNPACK
118 static bool
119 _is_ext(args *ap)
121 for (zproc const *zp = _zprocs; zp < _zprocs + NELEM(_zprocs); ++zp) {
122 size_t el = zp->zp_ext_len;
124 if (ap->a_path_len <= el)
125 continue;
126 if (memcmp(ap->a_path + ap->a_path_len - el, zp->zp_ext, el))
127 continue;
129 ap = __run_zproc(ap, zp);
130 break;
132 return (ap != NULL);
135 static bool
136 _try_all_ext(args *ap)
138 for (zproc const *zp = _zprocs; zp < _zprocs + NELEM(_zprocs); ++zp) {
139 char *np = new char[ap->a_path_len + zp->zp_ext_len +1];
141 memcpy(np, ap->a_path, ap->a_path_len);
143 struct ::stat sb;
144 memcpy(np + ap->a_path_len, zp->zp_ext, zp->zp_ext_len +1);
145 if (stat(np, &sb)) {
146 a_delete np;
147 continue;
150 // That's our zproc, let it make the deal
151 char const *pb_save = ap->a_path;
152 size_t pl_save = ap->a_path_len;
153 ap->a_path = np;
154 ap->a_path_len = pl_save + zp->zp_ext_len;
155 if ((ap = __run_zproc(ap, zp)) != NULL) {
156 ap->a_path = pb_save;
157 ap->a_path_len = pl_save;
159 a_delete np;
160 goto jleave;
162 ap = NULL;
163 jleave:
164 return (ap != NULL);
167 static args *
168 __run_zproc(args *ap, zproc const *zp)
170 # ifdef _LAYER
171 if (zp->zp_layer) {
172 if ((ap->a_layer = gzopen(ap->a_path, "rb")) == NULL) {
173 ap->a_errno = errno;
174 ap = NULL;
175 } else if (ap->a_flags &
176 (file_case::mux_need_seek | file_case::mux_need_stdio))
177 ap = __unpack(ap);
178 } else {
179 # endif
180 char *np = new char[zp->zp_cmd_len + 1 + ap->a_path_len +1];
182 size_t l;
183 memcpy(np, zp->zp_cmd, l = zp->zp_cmd_len);
184 np[l++] = ' ';
185 memcpy(np + l, ap->a_path, ap->a_path_len +1);
187 if ((ap->a_fp = popen(np, "r")) == NULL) {
188 ap->a_errno = errno;
189 ap = NULL;
190 } else if (ap->a_flags & file_case::mux_need_seek)
191 ap = __unpack(ap);
192 else
193 ap->a_flags |= file_case::fc_pipe | file_case::fc_have_stdio;
195 a_delete np;
196 # ifdef _LAYER
198 # endif
200 return ap;
203 static args *
204 __unpack(args *ap)
206 size_t const buf_len = (BUFSIZ + 0 > 1 << 15) ? BUFSIZ : 1 << 15;
207 uint8_t *buf = new uint8_t[buf_len];
209 // xtmpfile uses binary mode and fatal()s on error
210 FILE *decomp = xtmpfile(NULL, "groff_unpack"), *decomp_save = decomp;
211 for (;;) {
212 size_t oc;
214 # ifdef _LAYER
215 if (ap->a_layer != NULL) {
216 int i = gzread((gzFile)ap->a_layer, buf, buf_len);
217 if (i == -1) {
218 ap->a_errno = errno;
219 decomp = NULL;
220 break;
221 } else if (i == 0)
222 break;
223 oc = (size_t)i;
224 } else
225 # endif
226 if ((oc = fread(buf, sizeof *buf, buf_len, ap->a_fp)) == 0) {
227 if (!feof(ap->a_fp)) {
228 ap->a_errno = errno;
229 decomp = NULL;
231 break;
234 if (decomp != NULL) {
235 for (uint8_t *target = buf; oc > 0;) {
236 size_t i = fwrite(target, sizeof *buf, oc, decomp);
237 if (i == 0)
238 break;
239 oc -= i;
240 target += i;
242 if (oc > 0) {
243 ap->a_errno = errno;
244 decomp = NULL;
249 # ifdef _LAYER
250 if (ap->a_layer != NULL) {
251 if (gzclose((gzFile)ap->a_layer) != Z_OK)
252 error("decompressor gzclose(3) failed");
253 ap->a_layer = NULL;
254 } else
255 # endif
256 if (pclose(ap->a_fp) != 0)
257 error("decompressor pipe pclose(3) didn't exit cleanly");
259 if (decomp != NULL) {
260 ap->a_flags |= file_case::fc_have_stdio;
261 rewind(ap->a_fp = decomp);
262 } else {
263 fclose(decomp_save);
264 ap->a_fp = NULL;
265 ap = NULL;
268 a_delete buf;
269 return ap;
271 #endif // HAVE_UNPACK
273 bool
274 file_case::close(void)
276 assert((_file != NULL && _layer == NULL) ||
277 (_file == NULL && _layer != NULL));
279 if (!(_flags & fc_const_path))
280 a_delete _path;
282 bool rv;
283 if (_flags & fc_dont_close)
284 rv = true;
285 #ifdef _LAYER
286 else if (_layer != NULL)
287 rv = (gzclose((gzFile)_layer) == Z_OK);
288 #endif
289 #ifdef HAVE_UNPACK
290 else if (_flags & fc_pipe)
291 rv = (pclose(_file) == 0);
292 #endif
293 else
294 rv = (fclose(_file) == 0);
296 #ifndef NDEBUG
297 _path = NULL;
298 _file = NULL;
299 _layer = NULL;
300 _flags = fc_none;
301 #endif
302 return rv;
305 bool
306 file_case::is_eof(void) const
308 bool rv;
309 #ifdef _LAYER
310 if (_layer != NULL)
311 rv = (gzeof((gzFile)_layer) != 0);
312 else
313 #endif
314 rv = (feof(_file) != 0);
315 return rv;
319 file_case::get_c(void)
321 int rv;
322 #ifdef _LAYER
323 if (_layer != NULL)
324 rv = gzgetc((gzFile)_layer);
325 else
326 #endif
327 rv = _getc(_file);
328 return rv;
332 file_case::unget_c(int c)
334 int rv;
335 #ifdef _LAYER
336 if (_layer != NULL)
337 rv = gzungetc(c, (gzFile)_layer);
338 else
339 #endif
340 rv = ungetc(c, _file);
341 return rv;
344 char *
345 file_case::get_line(char *buf, size_t buf_size)
347 #ifdef _LAYER
348 if (_layer != NULL)
349 buf = gzgets((gzFile)_layer, buf, (int)buf_size);
350 else
351 #endif
352 buf = fgets(buf, (int)buf_size, _file);
353 return buf;
356 size_t
357 file_case::get_buf(void *buf, size_t buf_size)
359 size_t rv;
360 #ifdef _LAYER
361 if (_layer != NULL) {
362 int i = gzread((gzFile)_layer, buf, (unsigned int)buf_size);
363 rv = (i <= 0) ? 0 : (size_t)i;
364 } else
365 #endif
366 rv = fread(buf, 1, buf_size, _file);
367 return rv;
370 void
371 file_case::rewind(void)
373 #ifdef _LAYER
374 if (_layer != NULL)
375 gzrewind((gzFile)_layer);
376 else
377 #endif
378 ::rewind(_file);
382 file_case::seek(long offset, seek_whence whence)
384 int x = (whence == seek_set ? SEEK_SET :
385 (whence == seek_cur ? SEEK_CUR : SEEK_END));
386 #ifdef _LAYER
387 if (_layer != NULL)
388 x = gzseek((gzFile)_layer, (z_off_t)offset, x);
389 else
390 #endif
391 x = fseek(_file, offset, x);
392 return x;
395 /*static*/ file_case *
396 file_case::muxer(char const *path, uint32_t flags)
398 enum {tmpbit = 1<<(_mux_freebit+0)};
400 assert(!(flags & (fc_dont_close | fc_pipe)));
401 assert(!(flags & (fc_const_path | fc_take_path)) ||
402 !(flags & fc_const_path) != !(flags & fc_take_path));
403 assert(!(flags & (mux_unpack | mux_no_unpack)) ||
404 !(flags & mux_unpack) != !(flags & mux_no_unpack));
406 if (path == NULL || (path[0] == '-' && path[1] == '\0')) {
407 path = "-";
408 flags &= ~fc_take_path;
409 flags |= fc_const_path | tmpbit;
410 } else if (!(flags & (fc_const_path | fc_take_path))) {
411 path = strsave(path);
412 flags |= fc_take_path;
414 if (!(flags & (mux_unpack | mux_no_unpack)))
415 flags |= _mux_unpack_default;
417 file_case *fcp;
418 args a;
419 a.a_fp = NULL;
420 a.a_layer = NULL;
421 a.a_path_len = strlen(a.a_path = path);
422 a.a_mode = (flags & mux_need_binary) ? "rb" : "r";
423 a.a_flags = flags;
424 a.a_errno = 0;
426 // Shorthand: support "-" to mean stdin
427 if (flags & tmpbit) {
428 clearerr(stdin);
429 if (flags & mux_need_binary)
430 SET_BINARY(fileno(stdin));
431 a.a_fp = stdin;
432 a.a_flags |= fc_dont_close | fc_const_path | fc_have_stdio;
433 goto jnew;
436 // If we support unpacking then check wether the path already includes
437 // a packer's extension, i.e., explicitly. Anyway unpack then, despite flags
438 #ifdef HAVE_UNPACK
439 if (!_is_ext(&a)) {
440 assert(a.a_fp == NULL && a.a_layer == NULL);
441 goto jerror;
443 if (a.a_fp != NULL || a.a_layer != NULL)
444 goto jnew;
445 #endif
447 // Try a plain open
448 errno = 0;
449 if ((a.a_fp = fopen(a.a_path, a.a_mode)) != NULL) {
450 a.a_flags |= fc_have_stdio;
451 jnew:
452 assert((a.a_fp != NULL && a.a_layer == NULL) ||
453 (a.a_fp == NULL && a.a_layer != NULL));
454 fcp = new file_case(a.a_fp, path, a.a_flags & fc_mask); // XXX real path?
455 fcp->_layer = a.a_layer;
456 goto jleave;
458 a.a_errno = errno;
460 // Then auto-expand the given path if so desired
461 #ifdef HAVE_UNPACK
462 if (a.a_errno == ENOENT && (a.a_flags & mux_unpack) && _try_all_ext(&a))
463 goto jnew;
465 jerror:
466 #endif
467 if (!(a.a_flags & fc_const_path))
468 a_delete a.a_path;
469 errno = a.a_errno;
470 fcp = NULL;
471 jleave:
472 return fcp;
475 // s-it2-mode