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, &sector, 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