1 /** \file zfile.c
2 * \brief Transparent handling of compressed files
3 * \author Ettore Perazzoli <ettore@comm2000.it>
4 * \author Andreas Boose <viceteam@t-online.de>
5 * \author Bas Wassink <b.wassink@ziggo.nl>
6 *
7 * ARCHIVE, ZIPCODE and LYNX supports added by
8 * Teemu Rantanen <tvr@cs.hut.fi>
9 */
10
11 /*
12 * This file is part of VICE, the Versatile Commodore Emulator.
13 * See README for copyright notice.
14 *
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
28 * 02111-1307 USA.
29 *
30 */
31
32 /* This code might be improved a lot... */
33
34 #include "vice.h"
35
36 #include <ctype.h>
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <stdint.h>
42
43 #ifdef HAVE_ERRNO_H
44 #include <errno.h>
45 #endif
46 #ifdef HAVE_ZLIB
47 #include <zlib.h>
48 #endif
49
50 #ifdef HAVE_STRINGS_H
51 #include <strings.h>
52 #endif
53
54 #include "archdep.h"
55 #include "ioutil.h"
56 #include "lib.h"
57 #include "log.h"
58 #include "util.h"
59 #include "zfile.h"
60 #include "zipcode.h"
61
62
63 /* ------------------------------------------------------------------------- */
64
65 /* #define DEBUG_ZFILE */
66
67 #ifdef DEBUG_ZFILE
68 #define ZDEBUG(a) log_debug a
69 #else
70 #define ZDEBUG(a)
71 #endif
72
73 /* We could add more here... */
74 enum compression_type {
75 COMPR_NONE,
76 COMPR_GZIP,
77 COMPR_BZIP,
78 COMPR_ARCHIVE,
79 COMPR_ZIPCODE,
80 COMPR_LYNX,
81 COMPR_TZX
82 };
83
84 /* This defines a linked list of all the compressed files that have been
85 opened. */
86 struct zfile_s {
87 char *tmp_name; /* Name of the temporary file. */
88 char *orig_name; /* Name of the original file. */
89 int write_mode; /* Non-zero if the file is open for writing.*/
90 FILE *stream; /* Associated stdio-style stream. */
91 FILE *fd; /* Associated file descriptor. */
92 enum compression_type type; /* Compression algorithm. */
93 struct zfile_s *prev, *next; /* Link to the previous and next nodes. */
94 zfile_action_t action; /* action on close */
95 char *request_string; /* ui string for action=ZFILE_REQUEST */
96 };
97 typedef struct zfile_s zfile_t;
98
99 static zfile_t *zfile_list = NULL;
100
101 static log_t zlog = LOG_ERR;
102
103 /* ------------------------------------------------------------------------- */
104
105 static int zinit_done = 0;
106
107
108 /** \@brief 'Check' is file \a name is a gzip or compress file
109 *
110 * \param[in] name filename or path
111 *
112 * \return bool
113 *
114 * \fixme this is a silly function and should be reimplemented using the
115 * 2-byte header of the file
116 */
file_is_gzip(const char * name)117 static int file_is_gzip(const char *name)
118 {
119 size_t l = strlen(name);
120
121 if ((l < 4 || strcasecmp(name + l - 3, ".gz"))
122 && (l < 3 || strcasecmp(name + l - 2, ".z"))
123 && (l < 4 || toupper(name[l - 1]) != 'Z' || name[l - 4] != '.')) {
124 return 0;
125 }
126 return 1;
127 }
128
129
zfile_list_destroy(void)130 static void zfile_list_destroy(void)
131 {
132 zfile_t *p;
133
134 for (p = zfile_list; p != NULL; ) {
135 zfile_t *next;
136
137 lib_free(p->orig_name);
138 lib_free(p->tmp_name);
139 next = p->next;
140 lib_free(p);
141 p = next;
142 }
143
144 zfile_list = NULL;
145 }
146
zinit(void)147 static int zinit(void)
148 {
149 zlog = log_open("ZFile");
150
151 /* Free the `zfile_list' if not empty. */
152 zfile_list_destroy();
153
154 zinit_done = 1;
155
156 return 0;
157 }
158
159 /* Add one zfile to the list. `orig_name' is automatically expanded to the
160 complete path. */
zfile_list_add(const char * tmp_name,const char * orig_name,enum compression_type type,int write_mode,FILE * stream,FILE * fd)161 static void zfile_list_add(const char *tmp_name,
162 const char *orig_name,
163 enum compression_type type,
164 int write_mode,
165 FILE *stream, FILE *fd)
166 {
167 zfile_t *new_zfile = lib_malloc(sizeof(zfile_t));
168
169 /* Make sure we have the complete path of the file. */
170 archdep_expand_path(&new_zfile->orig_name, orig_name);
171
172 /* The new zfile becomes first on the list. */
173 new_zfile->tmp_name = tmp_name ? lib_strdup(tmp_name) : NULL;
174 new_zfile->write_mode = write_mode;
175 new_zfile->stream = stream;
176 new_zfile->fd = fd;
177 new_zfile->type = type;
178 new_zfile->action = ZFILE_KEEP;
179 new_zfile->request_string = NULL;
180 new_zfile->next = zfile_list;
181 new_zfile->prev = NULL;
182 if (zfile_list != NULL) {
183 zfile_list->prev = new_zfile;
184 }
185 zfile_list = new_zfile;
186 }
187
zfile_shutdown(void)188 void zfile_shutdown(void)
189 {
190 zfile_list_destroy();
191 }
192
193 /* ------------------------------------------------------------------------ */
194
195 /* Uncompression. */
196
197 /* If `name' has a gzip-like extension, try to uncompress it into a temporary
198 file using gzip or zlib if available. If this succeeds, return the name
199 of the temporary file; return NULL otherwise. */
try_uncompress_with_gzip(const char * name)200 static char *try_uncompress_with_gzip(const char *name)
201 {
202 #ifdef HAVE_ZLIB
203 FILE *fddest;
204 gzFile fdsrc;
205 char *tmp_name = NULL;
206 int len;
207
208 if (!file_is_gzip(name)) {
209 return NULL;
210 }
211
212 fddest = archdep_mkstemp_fd(&tmp_name, MODE_WRITE);
213
214 if (fddest == NULL) {
215 return NULL;
216 }
217
218 fdsrc = gzopen(name, MODE_READ);
219 if (fdsrc == NULL) {
220 fclose(fddest);
221 ioutil_remove(tmp_name);
222 lib_free(tmp_name);
223 return NULL;
224 }
225
226 do {
227 char buf[256];
228
229 len = gzread(fdsrc, (void *)buf, 256);
230 if (len > 0) {
231 if (fwrite((void *)buf, 1, (size_t)len, fddest) < len) {
232 gzclose(fdsrc);
233 fclose(fddest);
234 ioutil_remove(tmp_name);
235 lib_free(tmp_name);
236 return NULL;
237 }
238 }
239 } while (len > 0);
240
241 gzclose(fdsrc);
242 fclose(fddest);
243
244 return tmp_name;
245 #else
246 char *tmp_name = NULL;
247 int exit_status;
248 char *argv[4];
249
250 if (!file_is_gzip(name)) {
251 return NULL;
252 }
253
254 /* `exec*()' does not want these to be constant... */
255 argv[0] = lib_strdup("gzip");
256 argv[1] = lib_strdup("-cd");
257 argv[2] = archdep_filename_parameter(name);
258 argv[3] = NULL;
259
260 ZDEBUG(("try_uncompress_with_gzip: spawning gzip -cd %s", name));
261 exit_status = archdep_spawn("gzip", argv, &tmp_name, NULL);
262
263 lib_free(argv[0]);
264 lib_free(argv[1]);
265 lib_free(argv[2]);
266
267 if (exit_status == 0) {
268 ZDEBUG(("try_uncompress_with_gzip: OK"));
269 return tmp_name;
270 } else {
271 ZDEBUG(("try_uncompress_with_gzip: failed"));
272 ioutil_remove(tmp_name);
273 lib_free(tmp_name);
274 return NULL;
275 }
276 #endif
277 }
278
279 /* If `name' has a bzip-like extension, try to uncompress it into a temporary
280 file using bzip. If this succeeds, return the name of the temporary file;
281 return NULL otherwise. */
try_uncompress_with_bzip(const char * name)282 static char *try_uncompress_with_bzip(const char *name)
283 {
284 char *tmp_name = NULL;
285 size_t l = strlen(name);
286 int exit_status;
287 char *argv[4];
288
289 /* Check whether the name sounds like a bzipped file by checking the
290 extension. UNIX variants of bzip v2 use the extension
291 '.bz2'. bzip v1 is obsolete. */
292 if (l < 5 || strcasecmp(name + l - 4, ".bz2") != 0) {
293 return NULL;
294 }
295
296 /* `exec*()' does not want these to be constant... */
297 argv[0] = lib_strdup("bzip2");
298 argv[1] = lib_strdup("-cd");
299 argv[2] = archdep_filename_parameter(name);
300 argv[3] = NULL;
301
302 ZDEBUG(("try_uncompress_with_bzip: spawning bzip -cd %s", name));
303 exit_status = archdep_spawn("bzip2", argv, &tmp_name, NULL);
304
305 lib_free(argv[0]);
306 lib_free(argv[1]);
307 lib_free(argv[2]);
308
309 if (exit_status == 0) {
310 ZDEBUG(("try_uncompress_with_bzip: OK"));
311 return tmp_name;
312 } else {
313 ZDEBUG(("try_uncompress_with_bzip: failed"));
314 ioutil_remove(tmp_name);
315 lib_free(tmp_name);
316 return NULL;
317 }
318 }
319
try_uncompress_with_tzx(const char * name)320 static char *try_uncompress_with_tzx(const char *name)
321 {
322 char *tmp_name = NULL;
323 size_t l = strlen(name);
324 int exit_status;
325 char *argv[4];
326
327 /* Check whether the name sounds like a tzx file. */
328 if (l < 4 || strcasecmp(name + l - 4, ".tzx") != 0) {
329 return NULL;
330 }
331
332 /* `exec*()' does not want these to be constant... */
333 argv[0] = lib_strdup("64tzxtap");
334 argv[1] = archdep_filename_parameter(name);
335 argv[2] = NULL;
336
337 ZDEBUG(("try_uncompress_with_tzx: spawning 64tzxtap %s", name));
338 exit_status = archdep_spawn("64tzxtap", argv, &tmp_name, NULL);
339
340 lib_free(argv[0]);
341 lib_free(argv[1]);
342
343 if (exit_status == 0) {
344 ZDEBUG(("try_uncompress_with_tzx: OK"));
345 return tmp_name;
346 } else {
347 ZDEBUG(("try_uncompress_with_tzx: failed"));
348 ioutil_remove(tmp_name);
349 lib_free(tmp_name);
350 return NULL;
351 }
352 }
353
354 /* is the name zipcode -name? */
is_zipcode_name(char * name)355 static int is_zipcode_name(char *name)
356 {
357 if (name[0] >= '1' && name[0] <= '4' && name[1] == '!') {
358 return 1;
359 }
360 return 0;
361 }
362
363 /* Extensions we know about */
364 static const char * const extensions[] = {
365 FSDEV_EXT_SEP_STR "d64",
366 FSDEV_EXT_SEP_STR "d67",
367 FSDEV_EXT_SEP_STR "d71",
368 FSDEV_EXT_SEP_STR "d80",
369 FSDEV_EXT_SEP_STR "d81",
370 FSDEV_EXT_SEP_STR "d82",
371 FSDEV_EXT_SEP_STR "d1m",
372 FSDEV_EXT_SEP_STR "d2m",
373 FSDEV_EXT_SEP_STR "d4m",
374 FSDEV_EXT_SEP_STR "g64",
375 FSDEV_EXT_SEP_STR "p64",
376 FSDEV_EXT_SEP_STR "g41",
377 FSDEV_EXT_SEP_STR "x64",
378 FSDEV_EXT_SEP_STR "dsk",
379 FSDEV_EXT_SEP_STR "t64",
380 FSDEV_EXT_SEP_STR "p00",
381 FSDEV_EXT_SEP_STR "prg",
382 FSDEV_EXT_SEP_STR "lnx",
383 FSDEV_EXT_SEP_STR "tap",
384 NULL
385 };
386
is_valid_extension(char * end,size_t l,int nameoffset)387 static int is_valid_extension(char *end, size_t l, int nameoffset)
388 {
389 int i;
390 size_t len;
391
392 /* zipcode testing is a special case */
393 if (l > nameoffset + 2u && is_zipcode_name(end + nameoffset)) {
394 return 1;
395 }
396 /* others */
397 for (i = 0; extensions[i]; i++) {
398 len = strlen(extensions[i]);
399 if (l < nameoffset + len) {
400 continue;
401 }
402 if (!strcasecmp(extensions[i], end + l - len)) {
403 return 1;
404 }
405 }
406 return 0;
407 }
408
409
410 /* If `name' has a correct extension, try to list its contents and search for
411 the first file with a proper extension; if found, extract it. If this
412 succeeds, return the name of the temporary file; if the archive file is
413 valid but `write_mode' is non-zero, return a zero-length string; in all
414 the other cases, return NULL. */
try_uncompress_archive(const char * name,int write_mode,const char * program,const char * listopts,const char * extractopts,const char * extension,const char * search)415 static char *try_uncompress_archive(const char *name, int write_mode,
416 const char *program,
417 const char *listopts,
418 const char *extractopts,
419 const char *extension,
420 const char *search)
421 {
422 char *tmp_name = NULL;
423 size_t l = strlen(name);
424 size_t len;
425 size_t nameoffset;
426 int found = 0;
427 int exit_status;
428 char *argv[8];
429 FILE *fd;
430 char tmp[1024];
431
432 /* Do we have correct extension? */
433 len = strlen(extension);
434 if (l <= len || strcasecmp(name + l - len, extension) != 0) {
435 return NULL;
436 }
437
438 /* First run listing and search for first recognizeable extension. */
439 argv[0] = lib_strdup(program);
440 argv[1] = lib_strdup(listopts);
441 argv[2] = archdep_filename_parameter(name);
442 argv[3] = NULL;
443
444 ZDEBUG(("try_uncompress_archive: spawning `%s %s %s'",
445 program, listopts, name));
446 exit_status = archdep_spawn(program, argv, &tmp_name, NULL);
447
448 lib_free(argv[0]);
449 lib_free(argv[1]);
450 lib_free(argv[2]);
451
452 /* No luck? */
453 if (exit_status != 0) {
454 ZDEBUG(("try_uncompress_archive: `%s %s' failed.", program, listopts));
455 ioutil_remove(tmp_name);
456 lib_free(tmp_name);
457 return NULL;
458 }
459
460 ZDEBUG(("try_uncompress_archive: `%s %s' successful.", program, listopts));
461
462 fd = fopen(tmp_name, MODE_READ);
463 if (!fd) {
464 ZDEBUG(("try_uncompress_archive: cannot read `%s %s' output.",
465 program, tmp_name));
466 ioutil_remove(tmp_name);
467 lib_free(tmp_name);
468 return NULL;
469 }
470
471 ZDEBUG(("try_uncompress_archive: searching for the first valid file."));
472
473 /* Search for `search' first (if any) to see the offset where
474 filename begins, then search for first recognizeable file. */
475 nameoffset = search ? SIZE_MAX : 0;
476 len = search ? strlen(search) : 0;
477 while (!feof(fd) && !found) {
478 if (fgets(tmp, 1024, fd) == NULL) {
479 break;
480 }
481 l = strlen(tmp);
482 while (l > 0) {
483 tmp[--l] = 0;
484 if (((nameoffset == SIZE_MAX) || (nameoffset > 1024)) && l >= len
485 && strcasecmp(tmp + l - len, search) == 0) {
486 nameoffset = l - 4;
487 }
488 if (nameoffset <= 1024
489 && is_valid_extension(tmp, l, (int)nameoffset)) {
490 ZDEBUG(("try_uncompress_archive: found `%s'.",
491 tmp + nameoffset));
492 found = 1;
493 break;
494 }
495 }
496 }
497
498 fclose(fd);
499 ioutil_remove(tmp_name);
500 if (!found) {
501 ZDEBUG(("try_uncompress_archive: no valid file found."));
502 lib_free(tmp_name);
503 return NULL;
504 }
505
506 /* This would be a valid ZIP file, but we cannot handle ZIP files in
507 write mode. Return a null temporary file name to report this. */
508 if (write_mode) {
509 ZDEBUG(("try_uncompress_archive: cannot open file in write mode."));
510 lib_free(tmp_name);
511 return "";
512 }
513
514 /* And then file inside zip. If we have a zipcode extract all of them
515 to the same file. */
516 argv[0] = lib_strdup(program);
517 argv[1] = lib_strdup(extractopts);
518 argv[2] = archdep_filename_parameter(name);
519 if (is_zipcode_name(tmp + nameoffset)) {
520 argv[3] = lib_strdup(tmp + nameoffset);
521 argv[4] = lib_strdup(tmp + nameoffset);
522 argv[5] = lib_strdup(tmp + nameoffset);
523 argv[6] = lib_strdup(tmp + nameoffset);
524 argv[7] = NULL;
525 argv[3][0] = '1';
526 argv[4][0] = '2';
527 argv[5][0] = '3';
528 argv[6][0] = '4';
529 } else {
530 /* Check for info-zip's unzip
531 *
532 * Unzip needs special quoting of left brackets: [[], not \\[,
533 * see bug #1215.
534 */
535 if (strcmp(program, "unzip") == 0) {
536 argv[3] = archdep_quote_unzip(tmp + nameoffset);
537 } else {
538 argv[3] = archdep_quote_parameter(tmp + nameoffset);
539 }
540 argv[4] = NULL;
541 }
542
543 ZDEBUG(("try_uncompress_archive: spawning `%s %s %s %s'.",
544 program, extractopts, name, tmp + nameoffset));
545 exit_status = archdep_spawn(program, argv, &tmp_name, NULL);
546
547 lib_free(argv[0]);
548 lib_free(argv[1]);
549 lib_free(argv[2]);
550 lib_free(argv[3]);
551 if (is_zipcode_name(tmp + nameoffset)) {
552 lib_free(argv[4]);
553 lib_free(argv[5]);
554 lib_free(argv[6]);
555 }
556
557 if (exit_status != 0) {
558 ZDEBUG(("try_uncompress_archive: `%s %s' failed.",
559 program, extractopts));
560 ioutil_remove(tmp_name);
561 lib_free(tmp_name);
562 return NULL;
563 }
564
565 ZDEBUG(("try_uncompress_archive: `%s %s' successful.", program, tmp_name));
566 return tmp_name;
567 }
568
569 #define C1541_NAME "c1541"
570
571 /* If this file looks like a zipcode, try to extract is using c1541. We have
572 to figure this out by reading the contents of the file */
try_uncompress_zipcode(const char * name,int write_mode)573 static char *try_uncompress_zipcode(const char *name, int write_mode)
574 {
575 char *tmp_name = NULL;
576 char *argv[5];
577 int exit_status;
578
579 /* The 2nd char has to be '!'? */
580 util_fname_split(name, NULL, &tmp_name);
581 if (tmp_name == NULL) {
582 return NULL;
583 }
584 if (strlen(tmp_name) < 3 || tmp_name[1] != '!') {
585 lib_free(tmp_name);
586 return NULL;
587 }
588 lib_free(tmp_name);
589
590
591 /* don't ask for permission. just do it, if it fails, it fails. This relies
592 * on zipcode.c doing the right thing, which I doubt */
593 #if 0
594 /* Can we read this file? */
595 fd = fopen(name, MODE_READ);
596 if (fd == NULL) {
597 return NULL;
598 }
599 /* Read first track to see if this is zipcode. */
600 fseek(fd, 4, SEEK_SET);
601 for (count = 1; count < 21; count++) {
602 i = zipcode_read_sector(fd, 1, §or, tmp);
603 if (i || sector < 0 || sector > 20 || (sectors & (1 << sector))) {
604 fclose(fd);
605 return NULL;
606 }
607 sectors |= 1 << sector;
608 }
609 fclose(fd);
610 #endif
611 /* it is a zipcode. We cannot support write_mode */
612 if (write_mode) {
613 return "";
614 }
615
616 /* format image first */
617 tmp_name = archdep_tmpnam();
618
619 /* ok, now extract the zipcode */
620 argv[0] = lib_strdup(C1541_NAME);
621 argv[1] = lib_strdup("-zcreate");
622 argv[2] = lib_strdup(tmp_name);
623 argv[3] = archdep_filename_parameter(name);
624 argv[4] = NULL;
625
626 exit_status = archdep_spawn(C1541_NAME, argv, NULL, NULL);
627
628 lib_free(argv[0]);
629 lib_free(argv[1]);
630 lib_free(argv[2]);
631 lib_free(argv[3]);
632
633 if (exit_status) {
634 ioutil_remove(tmp_name);
635 lib_free(tmp_name);
636 return NULL;
637 }
638 /* everything ok */
639 return tmp_name;
640 }
641
642 /* If the file looks like a lynx image, try to extract it using c1541. We have
643 to figure this out by reading the contents of the file */
try_uncompress_lynx(const char * name,int write_mode)644 static char *try_uncompress_lynx(const char *name, int write_mode)
645 {
646 char *tmp_name;
647 size_t i;
648 int count;
649 FILE *fd;
650 char tmp[256];
651 char *argv[20];
652 int exit_status;
653
654 /* can we read this file? */
655 fd = fopen(name, MODE_READ);
656 if (fd == NULL) {
657 return NULL;
658 }
659 /* is this lynx -image? */
660 i = fread(tmp, 1, 2, fd);
661 if (i != 2 || tmp[0] != 1 || tmp[1] != 8) {
662 fclose(fd);
663 return NULL;
664 }
665 count = 0;
666 while (1) {
667 i = fread(tmp, 1, 1, fd);
668 if (i != 1) {
669 fclose(fd);
670 return NULL;
671 }
672 if (tmp[0]) {
673 count = 0;
674 } else {
675 count++;
676 }
677 if (count == 3) {
678 break;
679 }
680 }
681 i = fread(tmp, 1, 1, fd);
682 if (i != 1 || tmp[0] != 13) {
683 fclose(fd);
684 return NULL;
685 }
686 count = 0;
687 while (1) {
688 i = fread(&tmp[count], 1, 1, fd);
689 if (i != 1 || count == 254) {
690 fclose(fd);
691 return NULL;
692 }
693 if (tmp[count++] == 13) {
694 break;
695 }
696 }
697 tmp[count] = 0;
698 if (!atoi(tmp)) {
699 fclose(fd);
700 return NULL;
701 }
702 /* XXX: this is not a full check, but perhaps enough? */
703
704 fclose(fd);
705
706 /* it is a lynx image. We cannot support write_mode */
707 if (write_mode) {
708 return "";
709 }
710
711 tmp_name = archdep_tmpnam();
712
713 /* now create the image */
714 argv[0] = lib_strdup("c1541");
715 argv[1] = lib_strdup("-format");
716 argv[2] = lib_strdup("lynximage,00");
717 argv[3] = lib_strdup("x64");
718 argv[4] = lib_strdup(tmp_name);
719 argv[5] = lib_strdup("-unlynx");
720 argv[6] = archdep_filename_parameter(name);
721 argv[7] = NULL;
722
723 exit_status = archdep_spawn("c1541", argv, NULL, NULL);
724
725 lib_free(argv[0]);
726 lib_free(argv[1]);
727 lib_free(argv[2]);
728 lib_free(argv[3]);
729 lib_free(argv[4]);
730 lib_free(argv[5]);
731 lib_free(argv[6]);
732
733 if (exit_status) {
734 ioutil_remove(tmp_name);
735 lib_free(tmp_name);
736 return NULL;
737 }
738 /* everything ok */
739 return tmp_name;
740 }
741
742 struct valid_archives_s {
743 const char *program;
744 const char *listopts;
745 const char *extractopts;
746 const char *extension;
747 const char *search;
748 };
749 typedef struct valid_archives_s valid_archives_t;
750
751 static const valid_archives_t valid_archives[] = {
752 { "unzip", "-l", "-p", ".zip", "Name" },
753 { "lha", "lv", "pq", ".lzh", NULL },
754 { "lha", "lv", "pq", ".lha", NULL },
755 /* Hmmm. Did non-gnu tar have a -O -option? */
756 { "gtar", "-tf", "-xOf", ".tar", NULL },
757 { "tar", "-tf", "-xOf", ".tar", NULL },
758 { "gtar", "-ztf", "-zxOf", ".tar.gz", NULL },
759 { "tar", "-ztf", "-zxOf", ".tar.gz", NULL },
760 { "gtar", "-ztf", "-zxOf", ".tgz", NULL },
761 { "tar", "-ztf", "-zxOf", ".tgz", NULL },
762 /* this might be overkill, but adding this was sooo easy... */
763 { "zoo", "lf1q", "xpq", ".zoo", NULL },
764 { NULL, NULL, NULL, NULL, NULL }
765 };
766
767 /* Try to uncompress file `name' using the algorithms we know of. If this is
768 not possible, return `COMPR_NONE'. Otherwise, uncompress the file into a
769 temporary file, return the type of algorithm used and the name of the
770 temporary file in `tmp_name'. If `write_mode' is non-zero and the
771 returned `tmp_name' has zero length, then the file cannot be accessed in
772 write mode. */
try_uncompress(const char * name,char ** tmp_name,int write_mode)773 static enum compression_type try_uncompress(const char *name,
774 char **tmp_name,
775 int write_mode)
776 {
777 int i;
778
779 for (i = 0; valid_archives[i].program; i++) {
780 if ((*tmp_name = try_uncompress_archive(name, write_mode,
781 valid_archives[i].program,
782 valid_archives[i].listopts,
783 valid_archives[i].extractopts,
784 valid_archives[i].extension,
785 valid_archives[i].search))
786 != NULL) {
787 return COMPR_ARCHIVE;
788 }
789 }
790
791 /* need this order or .tar.gz is misunderstood */
792 if ((*tmp_name = try_uncompress_with_gzip(name)) != NULL) {
793 return COMPR_GZIP;
794 }
795
796 if ((*tmp_name = try_uncompress_with_bzip(name)) != NULL) {
797 return COMPR_BZIP;
798 }
799
800 if ((*tmp_name = try_uncompress_zipcode(name, write_mode)) != NULL) {
801 return COMPR_ZIPCODE;
802 }
803
804 if ((*tmp_name = try_uncompress_lynx(name, write_mode)) != NULL) {
805 return COMPR_LYNX;
806 }
807
808 if ((*tmp_name = try_uncompress_with_tzx(name)) != NULL) {
809 return COMPR_TZX;
810 }
811
812 return COMPR_NONE;
813 }
814
815 /* ------------------------------------------------------------------------- */
816
817 /* Compression. */
818
819 /* Compress `src' into `dest' using gzip. */
compress_with_gzip(const char * src,const char * dest)820 static int compress_with_gzip(const char *src, const char *dest)
821 {
822 #ifdef HAVE_ZLIB
823 FILE *fdsrc;
824 gzFile fddest;
825 size_t len;
826
827 fdsrc = fopen(dest, MODE_READ);
828 if (fdsrc == NULL) {
829 return -1;
830 }
831
832 fddest = gzopen(src, MODE_WRITE "9");
833 if (fddest == NULL) {
834 fclose(fdsrc);
835 return -1;
836 }
837
838 do {
839 char buf[256];
840 len = fread((void *)buf, 256, 1, fdsrc);
841 if (len > 0) {
842 gzwrite(fddest, (void *)buf, (unsigned int)len);
843 }
844 } while (len > 0);
845
846 gzclose(fddest);
847 fclose(fdsrc);
848
849 ZDEBUG(("compress with zlib: OK."));
850
851 return 0;
852 #else
853 static char *argv[4];
854 int exit_status;
855 char *mdest;
856
857 /* `exec*()' does not want these to be constant... */
858 argv[0] = lib_strdup("gzip");
859 argv[1] = lib_strdup("-c");
860 argv[2] = lib_strdup(src);
861 argv[3] = NULL;
862
863 mdest = lib_strdup(dest);
864
865 ZDEBUG(("compress_with_gzip: spawning gzip -c %s", src));
866 exit_status = archdep_spawn("gzip", argv, &mdest, NULL);
867
868 lib_free(mdest);
869
870 lib_free(argv[0]);
871 lib_free(argv[1]);
872 lib_free(argv[2]);
873
874 if (exit_status == 0) {
875 ZDEBUG(("compress_with_gzip: OK."));
876 return 0;
877 } else {
878 ZDEBUG(("compress_with_gzip: failed."));
879 return -1;
880 }
881 #endif
882 }
883
884 /* Compress `src' into `dest' using bzip. */
compress_with_bzip(const char * src,const char * dest)885 static int compress_with_bzip(const char *src, const char *dest)
886 {
887 char *argv[4];
888 int exit_status;
889 char *mdest;
890
891 /* `exec*()' does not want these to be constant... */
892 argv[0] = lib_strdup("bzip2");
893 argv[1] = lib_strdup("-c");
894 argv[2] = lib_strdup(src);
895 argv[3] = NULL;
896
897 mdest = lib_strdup(dest);
898
899 ZDEBUG(("compress_with_bzip: spawning bzip -c %s", src));
900 exit_status = archdep_spawn("bzip2", argv, &mdest, NULL);
901
902 lib_free(mdest);
903
904 lib_free(argv[0]);
905 lib_free(argv[1]);
906 lib_free(argv[2]);
907
908 if (exit_status == 0) {
909 ZDEBUG(("compress_with_bzip: OK."));
910 return 0;
911 } else {
912 ZDEBUG(("compress_with_bzip: failed."));
913 return -1;
914 }
915 }
916
917 /* Compress `src' into `dest' using algorithm `type'. */
zfile_compress(const char * src,const char * dest,enum compression_type type)918 static int zfile_compress(const char *src, const char *dest,
919 enum compression_type type)
920 {
921 char *dest_backup_name;
922 int retval;
923
924 /* This shouldn't happen */
925 if (type == COMPR_ARCHIVE) {
926 log_error(zlog, "compress: trying to compress archive-file.");
927 return -1;
928 }
929
930 /* This shouldn't happen */
931 if (type == COMPR_ZIPCODE) {
932 log_error(zlog, "compress: trying to compress zipcode-file.");
933 return -1;
934 }
935
936 /* This shouldn't happen */
937 if (type == COMPR_LYNX) {
938 log_error(zlog, "compress: trying to compress lynx-file.");
939 return -1;
940 }
941
942 /* This shouldn't happen */
943 if (type == COMPR_TZX) {
944 log_error(zlog, "compress: trying to compress tzx-file.");
945 return -1;
946 }
947
948 /* Check whether `compression_type' is a known one. */
949 if (type != COMPR_GZIP && type != COMPR_BZIP) {
950 log_error(zlog, "compress: unknown compression type");
951 return -1;
952 }
953
954 /* If we have no write permissions for `dest', give up. */
955 if (ioutil_access(dest, IOUTIL_ACCESS_W_OK) < 0) {
956 ZDEBUG(("compress: no write permissions for `%s'",
957 dest));
958 return -1;
959 }
960
961 if (ioutil_access(dest, IOUTIL_ACCESS_R_OK) < 0) {
962 ZDEBUG(("compress: no read permissions for `%s'", dest));
963 dest_backup_name = NULL;
964 } else {
965 /* If `dest' exists, make a backup first. */
966 dest_backup_name = archdep_make_backup_filename(dest);
967 if (dest_backup_name != NULL) {
968 ZDEBUG(("compress: making backup %s... ", dest_backup_name));
969 }
970 if (dest_backup_name != NULL
971 && ioutil_rename(dest, dest_backup_name) < 0) {
972 ZDEBUG(("failed."));
973 log_error(zlog, "Could not make pre-compression backup.");
974 return -1;
975 } else {
976 ZDEBUG(("OK."));
977 }
978 }
979
980 switch (type) {
981 case COMPR_GZIP:
982 retval = compress_with_gzip(src, dest);
983 break;
984 case COMPR_BZIP:
985 retval = compress_with_bzip(src, dest);
986 break;
987 default:
988 retval = -1;
989 }
990
991 if (retval == -1) {
992 /* Compression failed: restore original file. */
993 if (dest_backup_name != NULL
994 && ioutil_rename(dest_backup_name, dest) < 0) {
995 log_error(zlog,
996 "Could not restore backup file after failed compression.");
997 }
998 } else {
999 /* Compression succeeded: remove backup file. */
1000 if (dest_backup_name != NULL
1001 && ioutil_remove(dest_backup_name) < 0) {
1002 log_error(zlog, "Warning: could not remove backup file.");
1003 /* Do not return an error anyway (no data is lost). */
1004 }
1005 }
1006
1007 if (dest_backup_name) {
1008 lib_free(dest_backup_name);
1009 }
1010 return retval;
1011 }
1012
1013 /* ------------------------------------------------------------------------ */
1014
1015 /* Here we have the actual fopen and fclose wrappers.
1016
1017 These functions work exactly like the standard library versions, but
1018 handle compression and decompression automatically. When a file is
1019 opened, we check whether it looks like a compressed file of some kind.
1020 If so, we uncompress it and then actually open the uncompressed version.
1021 When a file that was opened for writing is closed, we re-compress the
1022 uncompressed version and update the original file. */
1023
1024 /* `fopen()' wrapper. */
zfile_fopen(const char * name,const char * mode)1025 FILE *zfile_fopen(const char *name, const char *mode)
1026 {
1027 char *tmp_name;
1028 FILE *stream;
1029 enum compression_type type;
1030 int write_mode = 0;
1031
1032 if (!zinit_done) {
1033 zinit();
1034 }
1035
1036 if (name == NULL || name[0] == 0) {
1037 return NULL;
1038 }
1039
1040 /* Do we want to write to this file? */
1041 if ((strchr(mode, 'w') != NULL) || (strchr(mode, '+') != NULL)) {
1042 write_mode = 1;
1043 }
1044
1045 /* Check for write permissions. */
1046 if (write_mode && ioutil_access(name, IOUTIL_ACCESS_W_OK) < 0) {
1047 return NULL;
1048 }
1049
1050 type = try_uncompress(name, &tmp_name, write_mode);
1051 if (type == COMPR_NONE) {
1052 stream = fopen(name, mode);
1053 if (stream == NULL) {
1054 return NULL;
1055 }
1056 zfile_list_add(NULL, name, type, write_mode, stream, NULL);
1057 return stream;
1058 } else if (*tmp_name == '\0') {
1059 errno = EACCES;
1060 return NULL;
1061 }
1062
1063 /* Open the uncompressed version of the file. */
1064 stream = fopen(tmp_name, mode);
1065 if (stream == NULL) {
1066 return NULL;
1067 }
1068
1069 zfile_list_add(tmp_name, name, type, write_mode, stream, NULL);
1070
1071 /* now we don't need the archdep_tmpnam allocation any more */
1072 lib_free(tmp_name);
1073
1074 return stream;
1075 }
1076
1077 /* Handle close-action of a file. `ptr' points to the zfile to close. */
handle_close_action(zfile_t * ptr)1078 static int handle_close_action(zfile_t *ptr)
1079 {
1080 if (ptr == NULL || ptr->orig_name == NULL) {
1081 return -1;
1082 }
1083
1084 switch (ptr->action) {
1085 case ZFILE_KEEP:
1086 break;
1087 case ZFILE_REQUEST:
1088 /*
1089 ui_zfile_close_request(ptr->orig_name, ptr->request_string);
1090 break;
1091 */
1092 case ZFILE_DEL:
1093 if (ioutil_remove(ptr->orig_name) < 0) {
1094 log_error(zlog, "Cannot unlink `%s': %s",
1095 ptr->orig_name, strerror(errno));
1096 }
1097 break;
1098 }
1099 return 0;
1100 }
1101
1102 /* Handle close of a (compressed file). `ptr' points to the zfile to close. */
handle_close(zfile_t * ptr)1103 static int handle_close(zfile_t *ptr)
1104 {
1105 ZDEBUG(("handle_close: closing `%s' (`%s'), write_mode = %d",
1106 ptr->tmp_name ? ptr->tmp_name : "(null)",
1107 ptr->orig_name, ptr->write_mode));
1108
1109 if (ptr->tmp_name) {
1110 /* Recompress into the original file. */
1111 if (ptr->orig_name
1112 && ptr->write_mode
1113 && zfile_compress(ptr->tmp_name, ptr->orig_name, ptr->type)) {
1114 return -1;
1115 }
1116
1117 /* Remove temporary file. */
1118 if (ioutil_remove(ptr->tmp_name) < 0) {
1119 log_error(zlog, "Cannot unlink `%s': %s", ptr->tmp_name, strerror(errno));
1120 }
1121 }
1122
1123 handle_close_action(ptr);
1124
1125 /* Remove item from list. */
1126 if (ptr->prev != NULL) {
1127 ptr->prev->next = ptr->next;
1128 } else {
1129 zfile_list = ptr->next;
1130 }
1131
1132 if (ptr->next != NULL) {
1133 ptr->next->prev = ptr->prev;
1134 }
1135
1136 if (ptr->orig_name) {
1137 lib_free(ptr->orig_name);
1138 }
1139 if (ptr->tmp_name) {
1140 lib_free(ptr->tmp_name);
1141 }
1142 if (ptr->request_string) {
1143 lib_free(ptr->request_string);
1144 }
1145
1146 lib_free(ptr);
1147
1148 return 0;
1149 }
1150
1151 /* `fclose()' wrapper. */
zfile_fclose(FILE * stream)1152 int zfile_fclose(FILE *stream)
1153 {
1154 zfile_t *ptr;
1155
1156 if (!zinit_done) {
1157 errno = EBADF;
1158 return -1;
1159 }
1160
1161 /* Search for the matching file in the list. */
1162 for (ptr = zfile_list; ptr != NULL; ptr = ptr->next) {
1163 if (ptr->stream == stream) {
1164 /* Close temporary file. */
1165 if (fclose(stream) == -1) {
1166 return -1;
1167 }
1168 if (handle_close(ptr) < 0) {
1169 errno = EBADF;
1170 return -1;
1171 }
1172
1173 return 0;
1174 }
1175 }
1176
1177 return fclose(stream);
1178 }
1179
zfile_close_action(const char * filename,zfile_action_t action,const char * request_str)1180 int zfile_close_action(const char *filename, zfile_action_t action,
1181 const char *request_str)
1182 {
1183 char *fullname = NULL;
1184 zfile_t *p = zfile_list;
1185
1186 archdep_expand_path(&fullname, filename);
1187
1188 while (p != NULL) {
1189 if (p->orig_name && !strcmp(p->orig_name, fullname)) {
1190 p->action = action;
1191 p->request_string = request_str ? lib_strdup(request_str) : NULL;
1192 lib_free(fullname);
1193 return 0;
1194 }
1195 p = p->next;
1196 }
1197
1198 lib_free(fullname);
1199 return -1;
1200 }
1201