OSDN Git Service

Replace fprintf() with fatal_error()
[lha/lha.git] / src / lhext.c
1 /* ------------------------------------------------------------------------ */
2 /* LHa for UNIX                                                             */
3 /*              lhext.c -- LHarc extract                                    */
4 /*                                                                          */
5 /*      Copyright (C) MCMLXXXIX Yooichi.Tagawa                              */
6 /*      Modified                Nobutaka Watazaki                           */
7 /*                                                                          */
8 /*  Ver. 0.00  Original                             1988.05.23  Y.Tagawa    */
9 /*  Ver. 1.00  Fixed                                1989.09.22  Y.Tagawa    */
10 /*  Ver. 0.03  LHa for UNIX                         1991.12.17  M.Oki       */
11 /*  Ver. 1.12  LHa for UNIX                         1993.10.01  N.Watazaki  */
12 /*  Ver. 1.13b Symbolic Link Update Bug Fix         1994.06.21  N.Watazaki  */
13 /*  Ver. 1.14  Source All chagned                   1995.01.14  N.Watazaki  */
14 /*  Ver. 1.14e bugfix                               1999.04.30  T.Okamoto   */
15 /* ------------------------------------------------------------------------ */
16 #include "lha.h"
17 /* ------------------------------------------------------------------------ */
18 static int      skip_flg = FALSE;   /* FALSE..No Skip , TRUE..Skip */
19 static char    *methods[] =
20 {
21     LZHUFF0_METHOD, LZHUFF1_METHOD, LZHUFF2_METHOD, LZHUFF3_METHOD,
22     LZHUFF4_METHOD, LZHUFF5_METHOD, LZHUFF6_METHOD, LZHUFF7_METHOD,
23     LARC_METHOD, LARC5_METHOD, LARC4_METHOD,
24     LZHDIRS_METHOD,
25     PMARC0_METHOD, PMARC2_METHOD,
26     NULL
27 };
28
29 static void add_dirinfo(char* name, LzHeader* hdr);
30 static void adjust_dirinfo();
31
32 #ifdef HAVE_LIBAPPLEFILE
33 static boolean decode_macbinary(FILE *ofp, size_t size, const char *outPath);
34 #endif
35
36 /* ------------------------------------------------------------------------ */
37 static          boolean
38 inquire_extract(name)
39     char           *name;
40 {
41     struct stat     stbuf;
42
43     skip_flg = FALSE;
44     if (stat(name, &stbuf) >= 0) {
45         if (!is_regularfile(&stbuf)) {
46             error("\"%s\" already exists (not a file)", name);
47             return FALSE;
48         }
49
50         if (noexec) {
51             printf("EXTRACT %s but file is exist.\n", name);
52             return FALSE;
53         }
54         else if (!force) {
55             if (!isatty(0)) {
56                 warning("skip to extract %s.", name);
57                 return FALSE;
58             }
59
60             switch (inquire("OverWrite ?(Yes/[No]/All/Skip)", name, "YyNnAaSs\n")) {
61             case 0:
62             case 1:/* Y/y */
63                 break;
64             case 2:
65             case 3:/* N/n */
66             case 8:/* Return */
67                 return FALSE;
68             case 4:
69             case 5:/* A/a */
70                 force = TRUE;
71                 break;
72             case 6:
73             case 7:/* S/s */
74                 skip_flg = TRUE;
75                 break;
76             }
77         }
78     }
79
80     if (noexec)
81         printf("EXTRACT %s\n", name);
82
83     return TRUE;
84 }
85
86 static boolean
87 make_name_with_pathcheck(char *name, size_t namesz, const char *dir, const char *q)
88 {
89     int offset = 0;
90     const char *p;
91     int sz;
92     struct stat stbuf;
93
94     if (extract_directory) {
95         sz = xsnprintf(name, namesz, "%s/", extract_directory);
96         if (sz == -1) {
97             return FALSE;
98         }
99         offset += sz;
100     }
101
102     while ((p = strchr(q, '/')) != NULL) {
103         if (namesz - offset < (p - q) + 2) {
104             return FALSE;
105         }
106         memcpy(name + offset, q, (p - q));
107         name[offset + (p - q)] = 0;
108
109         offset += (p - q);
110         q = p + 1;
111
112         if (lstat(name, &stbuf) < 0) {
113             name[offset++] = '/';
114             break;
115         }
116         if (is_symlink(&stbuf)) {
117             return FALSE;
118         }
119         name[offset++] = '/';
120     }
121
122     str_safe_copy(name + offset, q, namesz - offset);
123
124     return TRUE;
125 }
126
127 /* ------------------------------------------------------------------------ */
128 static          boolean
129 make_parent_path(name)
130     char           *name;
131 {
132     char            path[FILENAME_LENGTH];
133     struct stat     stbuf;
134     register char  *p;
135
136     /* make parent directory name into PATH for recursive call */
137     str_safe_copy(path, name, sizeof(path));
138     for (p = path + strlen(path); p > path; p--)
139         if (p[-1] == '/') {
140             *--p = '\0';
141             break;
142         }
143
144     if (p == path) {
145         message("invalid path name \"%s\"", name);
146         return FALSE;   /* no more parent. */
147     }
148
149     if (GETSTAT(path, &stbuf) >= 0) {
150         if (is_directory(&stbuf))
151             return TRUE;
152     }
153
154     if (verbose)
155         message("Making directory \"%s\".", path);
156
157 #if defined __MINGW32__
158     if (mkdir(path) >= 0)
159         return TRUE;
160 #else
161     if (mkdir(path, 0777) >= 0) /* try */
162         return TRUE;    /* successful done. */
163 #endif
164
165     if (!make_parent_path(path))
166         return FALSE;
167
168 #if defined __MINGW32__
169     if (mkdir(path) < 0) {      /* try again */
170         error("Cannot make directory \"%s\"", path);
171         return FALSE;
172     }
173 #else
174     if (mkdir(path, 0777) < 0) {    /* try again */
175         error("Cannot make directory \"%s\"", path);
176         return FALSE;
177     }
178 #endif
179
180     return TRUE;
181 }
182
183 /* ------------------------------------------------------------------------ */
184 static FILE    *
185 open_with_make_path(name)
186     char           *name;
187 {
188     FILE           *fp;
189
190     if ((fp = fopen(name, WRITE_BINARY)) == NULL) {
191         if (!make_parent_path(name) ||
192             (fp = fopen(name, WRITE_BINARY)) == NULL)
193             error("Cannot extract a file \"%s\"", name);
194     }
195     return fp;
196 }
197
198 /* ------------------------------------------------------------------------ */
199 static void
200 adjust_info(name, hdr)
201     char           *name;
202     LzHeader       *hdr;
203 {
204     struct utimbuf utimebuf;
205
206     /* adjust file stamp */
207     utimebuf.actime = utimebuf.modtime = hdr->unix_last_modified_stamp;
208
209     if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) != UNIX_FILE_SYMLINK)
210         utime(name, &utimebuf);
211
212     if (hdr->extend_type == EXTEND_UNIX
213         || hdr->extend_type == EXTEND_OS68K
214         || hdr->extend_type == EXTEND_XOSK) {
215
216         if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) != UNIX_FILE_SYMLINK) {
217             chmod(name, hdr->unix_mode);
218         }
219
220         if (!getuid()){
221             uid_t uid = hdr->unix_uid;
222             gid_t gid = hdr->unix_gid;
223
224 #if HAVE_GETPWNAM && HAVE_GETGRNAM
225             if (hdr->user[0]) {
226                 struct passwd *ent = getpwnam(hdr->user);
227                 if (ent) uid = ent->pw_uid;
228             }
229             if (hdr->group[0]) {
230                 struct group *ent = getgrnam(hdr->group);
231                 if (ent) gid = ent->gr_gid;
232             }
233 #endif
234
235 #if HAVE_LCHOWN
236             if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_SYMLINK)
237                 lchown(name, uid, gid);
238             else
239 #endif /* HAVE_LCHWON */
240                 chown(name, uid, gid);
241         }
242     }
243 #if __CYGWIN__
244     else {
245         /* On Cygwin, execute permission should be set for .exe or .dll. */
246         mode_t m;
247
248         umask(m = umask(0));    /* get current umask */
249         chmod(name, 0777 & ~m);
250     }
251 #endif
252 }
253
254 /* ------------------------------------------------------------------------ */
255 static off_t
256 extract_one(afp, hdr)
257     FILE           *afp;    /* archive file */
258     LzHeader       *hdr;
259 {
260     FILE           *fp; /* output file */
261 #if HAVE_LIBAPPLEFILE
262     FILE           *tfp; /* temporary output file */
263 #endif
264     struct stat     stbuf;
265     char            name[FILENAME_LENGTH];
266     unsigned int crc;
267     int             method;
268     boolean         save_quiet, save_verbose, up_flag;
269     char           *q = hdr->name, c;
270     off_t read_size = 0;
271
272     if (ignore_directory && strrchr(hdr->name, '/')) {
273         q = (char *) strrchr(hdr->name, '/') + 1;
274     }
275     else {
276         if (is_directory_traversal(q)) {
277             fatal_error("Possible directory traversal hack attempt in %s\n", q);
278         }
279
280         if (*q == '/') {
281             while (*q == '/') { q++; }
282
283             /*
284              * if OSK then strip device name
285              */
286             if (hdr->extend_type == EXTEND_OS68K
287                 || hdr->extend_type == EXTEND_XOSK) {
288                 do
289                     c = (*q++);
290                 while (c && c != '/');
291                 if (!c || !*q)
292                     q = ".";    /* if device name only */
293             }
294         }
295     }
296
297     if (!make_name_with_pathcheck(name, sizeof(name), extract_directory, q)) {
298         fatal_error("Possible symlink traversal hack attempt in %s\n", q);
299     }
300
301     /* LZHDIRS_METHODを持つヘッダをチェックする */
302     /* 1999.4.30 t.okamoto */
303     for (method = 0;; method++) {
304         if (methods[method] == NULL) {
305             error("Unknown method \"%.*s\"; \"%s\" will be skipped ...",
306                   5, hdr->method, name);
307             return read_size;
308         }
309         if (memcmp(hdr->method, methods[method], 5) == 0)
310             break;
311     }
312
313     if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_REGULAR
314         && method != LZHDIRS_METHOD_NUM) {
315     extract_regular:
316 #if 0
317         for (method = 0;; method++) {
318             if (methods[method] == NULL) {
319                 error("Unknown method \"%.*s\"; \"%s\" will be skipped ...",
320                       5, hdr->method, name);
321                 return read_size;
322             }
323             if (memcmp(hdr->method, methods[method], 5) == 0)
324                 break;
325         }
326 #endif
327
328         reading_filename = archive_name;
329         writing_filename = name;
330         if (output_to_stdout || verify_mode) {
331             /* "Icon\r" should be a resource fork file encoded in MacBinary
332                format, so that it should be skipped. */
333             if (hdr->extend_type == EXTEND_MACOS
334                 && strcmp(basename(name), "Icon\r") == 0
335                 && decode_macbinary_contents) {
336                 return read_size;
337             }
338
339             if (noexec) {
340                 printf("%s %s\n", verify_mode ? "VERIFY" : "EXTRACT", name);
341                 return read_size;
342             }
343
344             save_quiet = quiet;
345             save_verbose = verbose;
346             if (!quiet && output_to_stdout) {
347                 printf("::::::::\n%s\n::::::::\n", name);
348                 quiet = TRUE;
349                 verbose = FALSE;
350             }
351             else if (verify_mode) {
352                 quiet = FALSE;
353                 verbose = TRUE;
354             }
355
356 #if defined(__MINGW32__) || defined(__DJGPP__)
357             {
358                 int old_mode;
359                 fflush(stdout);
360                 old_mode = setmode(fileno(stdout), O_BINARY);
361 #endif
362
363 #if HAVE_LIBAPPLEFILE
364             /* On default, MacLHA encodes into MacBinary. */
365             if (hdr->extend_type == EXTEND_MACOS && !verify_mode && decode_macbinary_contents) {
366                 /* build temporary file */
367                 tfp = NULL; /* avoid compiler warnings `uninitialized' */
368                 tfp = build_temporary_file();
369
370                 crc = decode_lzhuf(afp, tfp,
371                                    hdr->original_size, hdr->packed_size,
372                                    name, method, &read_size);
373                 fclose(tfp);
374                 decode_macbinary(stdout, hdr->original_size, name);
375                 unlink(temporary_name);
376             } else {
377                 crc = decode_lzhuf(afp, stdout,
378                                    hdr->original_size, hdr->packed_size,
379                                    name, method, &read_size);
380             }
381 #else
382             crc = decode_lzhuf(afp, stdout,
383                                hdr->original_size, hdr->packed_size,
384                                name, method, &read_size);
385 #endif /* HAVE_LIBAPPLEFILE */
386 #if defined(__MINGW32__) || defined(__DJGPP__)
387                 fflush(stdout);
388                 setmode(fileno(stdout), old_mode);
389             }
390 #endif
391             quiet = save_quiet;
392             verbose = save_verbose;
393         }
394         else {
395 #ifndef __APPLE__
396             /* "Icon\r" should be a resource fork of parent folder's icon,
397                so that it can be skipped when system is not Mac OS X. */
398             if (hdr->extend_type == EXTEND_MACOS
399                 && strcmp(basename(name), "Icon\r") == 0
400                 && decode_macbinary_contents) {
401                 make_parent_path(name); /* create directory only */
402                 return read_size;
403             }
404 #endif /* __APPLE__ */
405             if (skip_flg == FALSE)  {
406                 up_flag = inquire_extract(name);
407                 if (up_flag == FALSE && force == FALSE) {
408                     return read_size;
409                 }
410             }
411
412             if (skip_flg == TRUE) { /* if skip_flg */
413                 if (stat(name, &stbuf) == 0 && force != TRUE) {
414                     if (stbuf.st_mtime >= hdr->unix_last_modified_stamp) {
415                         if (quiet != TRUE)
416                             printf("%s : Skipped...\n", name);
417                         return read_size;
418                     }
419                 }
420             }
421             if (noexec) {
422                 return read_size;
423             }
424
425             signal(SIGINT, interrupt);
426 #ifdef SIGHUP
427             signal(SIGHUP, interrupt);
428 #endif
429
430             unlink(name);
431             remove_extracting_file_when_interrupt = TRUE;
432
433             if ((fp = open_with_make_path(name)) != NULL) {
434 #if HAVE_LIBAPPLEFILE
435                 if (hdr->extend_type == EXTEND_MACOS && !verify_mode && decode_macbinary_contents) {
436                     /* build temporary file */
437                     tfp = NULL; /* avoid compiler warnings `uninitialized' */
438                     tfp = build_temporary_file();
439
440                     crc = decode_lzhuf(afp, tfp,
441                                        hdr->original_size, hdr->packed_size,
442                                        name, method, &read_size);
443                     fclose(tfp);
444                     decode_macbinary(fp, hdr->original_size, name);
445 #ifdef __APPLE__
446                     /* TODO: set resource fork */
447                     /* after processing, "Icon\r" is not needed. */
448                     if (strcmp(basename(name), "Icon\r") == 0) {
449                         unlink(name);
450                     }
451 #endif /* __APPLE__ */
452                     unlink(temporary_name);
453                 } else {
454                     crc = decode_lzhuf(afp, fp,
455                                        hdr->original_size, hdr->packed_size,
456                                        name, method, &read_size);
457                 }
458 #else /* HAVE_LIBAPPLEFILE */
459                 crc = decode_lzhuf(afp, fp,
460                                    hdr->original_size, hdr->packed_size,
461                                    name, method, &read_size);
462 #endif /* HAVE_LIBAPPLEFILE */
463                 fclose(fp);
464             }
465             remove_extracting_file_when_interrupt = FALSE;
466             signal(SIGINT, SIG_DFL);
467 #ifdef SIGHUP
468             signal(SIGHUP, SIG_DFL);
469 #endif
470             if (!fp)
471                 return read_size;
472         }
473
474         if (hdr->has_crc && crc != hdr->crc)
475             error("CRC error: \"%s\"", name);
476     }
477     else if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_DIRECTORY
478              || (hdr->unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_SYMLINK
479              || method == LZHDIRS_METHOD_NUM) {
480         /* ↑これで、Symbolic Link は、大丈夫か? */
481         if (!ignore_directory && !verify_mode) {
482             if (noexec) {
483                 if (quiet != TRUE)
484                     printf("EXTRACT %s (directory)\n", name);
485                 return read_size;
486             }
487             /* NAME has trailing SLASH '/', (^_^) */
488             if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_SYMLINK) {
489                 int             l_code;
490
491 #ifdef S_IFLNK
492                 if (skip_flg == FALSE)  {
493                     up_flag = inquire_extract(name);
494                     if (up_flag == FALSE && force == FALSE) {
495                         return read_size;
496                     }
497                 } else {
498                     if (GETSTAT(name, &stbuf) == 0 && force != TRUE) {
499                         if (stbuf.st_mtime >= hdr->unix_last_modified_stamp) {
500                             if (quiet != TRUE)
501                                 printf("%s : Skipped...\n", name);
502                             return read_size;
503                         }
504                     }
505                 }
506
507                 unlink(name);
508                 make_parent_path(name);
509                 l_code = symlink(hdr->realname, name);
510                 if (l_code < 0) {
511                     if (quiet != TRUE)
512                         warning("Can't make Symbolic Link \"%s\" -> \"%s\"",
513                                 name, hdr->realname);
514                 }
515                 if (quiet != TRUE) {
516                     message("Symbolic Link %s -> %s",
517                             name, hdr->realname);
518                 }
519 #else
520                 warning("Can't make Symbolic Link %s -> %s",
521                         name, hdr->realname);
522                 return read_size;
523 #endif
524             }
525             else { /* make directory */
526                 if (!output_to_stdout && !make_parent_path(name))
527                     return read_size;
528                 /* save directory information */
529                 add_dirinfo(name, hdr);
530             }
531         }
532     }
533     else {
534         if (force)              /* force extract */
535             goto extract_regular;
536         else
537             error("Unknown file type: \"%s\". use `f' option to force extract.", name);
538     }
539
540     if (!output_to_stdout && !verify_mode) {
541         if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) != UNIX_FILE_DIRECTORY)
542             adjust_info(name, hdr);
543     }
544
545     return read_size;
546 }
547
548 /* ------------------------------------------------------------------------ */
549 /* EXTRACT COMMAND MAIN                                                     */
550 /* ------------------------------------------------------------------------ */
551 void
552 cmd_extract()
553 {
554     LzHeader        hdr;
555     off_t           pos;
556     FILE           *afp;
557     off_t read_size;
558
559     /* open archive file */
560     if ((afp = open_old_archive()) == NULL)
561         fatal_error("Cannot open archive file \"%s\"", archive_name);
562
563     if (archive_is_msdos_sfx1(archive_name))
564         seek_lha_header(afp);
565
566     /* extract each files */
567     while (get_header(afp, &hdr)) {
568         if (need_file(hdr.name)) {
569             pos = ftello(afp);
570             read_size = extract_one(afp, &hdr);
571             if (read_size != hdr.packed_size) {
572                 /* when error occurred in extract_one(), should adjust
573                    point of file stream */
574                 if (pos != -1 && afp != stdin)
575                     fseeko(afp, pos + hdr.packed_size, SEEK_SET);
576                 else {
577                     off_t i = hdr.packed_size - read_size;
578                     while (i--) fgetc(afp);
579                 }
580             }
581         } else {
582             if (afp != stdin)
583                 fseeko(afp, hdr.packed_size, SEEK_CUR);
584             else {
585                 off_t i = hdr.packed_size;
586                 while (i--) fgetc(afp);
587             }
588         }
589     }
590
591     /* close archive file */
592     fclose(afp);
593
594     /* adjust directory information */
595     adjust_dirinfo();
596
597     return;
598 }
599
600 int
601 is_directory_traversal(char *path)
602 {
603     int state = 0;
604
605     for (; *path; path++) {
606         switch (state) {
607         case 0:
608             if (*path == '.') state = 1;
609             else state = 3;
610             break;
611         case 1:
612             if (*path == '.') state = 2;
613             else if (*path == '/') state = 0;
614             else state = 3;
615             break;
616         case 2:
617             if (*path == '/') return 1;
618             else state = 3;
619             break;
620         case 3:
621             if (*path == '/') state = 0;
622             break;
623         }
624     }
625
626     return state == 2;
627 }
628
629 /*
630  * restore directory information (timestamp, permission and uid/gid).
631  * added by A.Iriyama  2003.12.12
632  */
633
634 typedef struct LzHeaderList_t {
635     struct LzHeaderList_t *next;
636     LzHeader hdr;
637 } LzHeaderList;
638
639 static LzHeaderList *dirinfo;
640
641 static void add_dirinfo(char *name, LzHeader *hdr)
642 {
643     LzHeaderList *p, *tmp, top;
644
645     if (memcmp(hdr->method, LZHDIRS_METHOD, 5) != 0)
646         return;
647
648     p = xmalloc(sizeof(LzHeaderList));
649
650     memcpy(&p->hdr, hdr, sizeof(LzHeader));
651     strncpy(p->hdr.name, name, sizeof(p->hdr.name));
652     p->hdr.name[sizeof(p->hdr.name)-1] = 0;
653
654 #if 0
655     /* push front */
656     {
657         tmp = dirinfo;
658         dirinfo = p;
659         dirinfo->next = tmp;
660     }
661 #else
662
663     /*
664       reverse sorted by pathname order
665
666          p->hdr.name = "a"
667
668          dirinfo->hdr.name             = "a/b/d"
669          dirinfo->next->hdr.name       = "a/b/c"
670          dirinfo->next->next->hdr.name = "a/b"
671
672        result:
673
674          dirinfo->hdr.name                   = "a/b/d"
675          dirinfo->next->hdr.name             = "a/b/c"
676          dirinfo->next->next->hdr.name       = "a/b"
677          dirinfo->next->next->next->hdr.name = "a"
678     */
679
680     top.next = dirinfo;
681
682     for (tmp = &top; tmp->next; tmp = tmp->next) {
683         if (strcmp(p->hdr.name, tmp->next->hdr.name) > 0) {
684             p->next = tmp->next;
685             tmp->next = p;
686             break;
687         }
688     }
689     if (tmp->next == NULL) {
690         p->next = NULL;
691         tmp->next = p;
692     }
693
694     dirinfo = top.next;
695 #endif
696 }
697
698 static void adjust_dirinfo()
699 {
700     while (dirinfo) {
701         /* message("adjusting [%s]", dirinfo->hdr.name); */
702         adjust_info(dirinfo->hdr.name, &dirinfo->hdr);
703
704         {
705             LzHeaderList *tmp = dirinfo;
706             dirinfo = dirinfo->next;
707             free(tmp);
708         }
709     }
710 }
711
712 #if HAVE_LIBAPPLEFILE
713 static boolean
714 decode_macbinary(ofp, size, outPath)
715     FILE *ofp;
716     off_t size;
717     const char *outPath;
718 {
719     af_file_t *afp = NULL;
720     FILE *ifp = NULL;
721     unsigned char *datap;
722     off_t dlen;
723
724     if ((afp = af_open(temporary_name)) != NULL) {
725         /* fetch datafork */
726         datap = af_data(afp, &dlen);
727         fwrite(datap, sizeof(unsigned char), dlen, ofp);
728         af_close(afp);
729         return TRUE;
730     } else { /* it may be not encoded in MacBinary */
731         /* try to copy */
732         if ((ifp = fopen(temporary_name, READ_BINARY)) == NULL) {
733             error("Cannot open a temporary file \"%s\"", temporary_name);
734             return FALSE;
735         }
736         copyfile(ifp, ofp, size, 0, 0);
737         fclose(ifp);
738         return TRUE;
739     }
740
741     return FALSE;
742 }
743 #endif /* HAVE_LIBAPPLEFILE */