OSDN Git Service

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