OSDN Git Service

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