1 /*
2 * untgz.c -- Display contents and extract files from a gzip'd TAR file
3 *
4 * written by Pedro A. Aranda Gutierrez <paag@tid.es>
5 * adaptation to Unix by Jean-loup Gailly <jloup@gzip.org>
6 * various fixes by Cosmin Truta <cosmint@cs.ubbcluj.ro>
7 *
8 * This software is provided 'as-is', without any express or implied
9 * warranty. In no event will the authors be held liable for any damages
10 * arising from the use of this software.
11 *
12 * Permission is granted to anyone to use this software for any purpose,
13 * including commercial applications, and to alter it and redistribute it
14 * freely, subject to the following restrictions:
15 *
16 * 1. The origin of this software must not be misrepresented; you must not
17 * claim that you wrote the original software. If you use this software
18 * in a product, an acknowledgment in the product documentation would be
19 * appreciated but is not required.
20 * 2. Altered source versions must be plainly marked as such, and must not be
21 * misrepresented as being the original software.
22 * 3. This notice may not be removed or altered from any source distribution.
23 */
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <time.h>
29 #include <errno.h>
30
31 #include "zlib.h"
32
33 #ifdef _WIN32
34 # include <direct.h>
35 # include <io.h>
36 # include <windows.h>
37 # ifndef F_OK
38 # define F_OK 0
39 # endif
40 # define mkdir(dirname,mode) _mkdir(dirname)
41 # ifdef _MSC_VER
42 # define access(path,mode) _access(path,mode)
43 # define chmod(path,mode) _chmod(path,mode)
44 # define strdup(str) _strdup(str)
45 # endif
46 #else
47 # include <sys/stat.h>
48 # include <unistd.h>
49 # include <utime.h>
50 #endif
51
52
53 /* values used in typeflag field */
54
55 #define REGTYPE '0' /* regular file */
56 #define AREGTYPE '\0' /* regular file */
57 #define LNKTYPE '1' /* link */
58 #define SYMTYPE '2' /* reserved */
59 #define CHRTYPE '3' /* character special */
60 #define BLKTYPE '4' /* block special */
61 #define DIRTYPE '5' /* directory */
62 #define FIFOTYPE '6' /* FIFO special */
63 #define CONTTYPE '7' /* reserved */
64
65 /* GNU tar extensions */
66
67 #define GNUTYPE_DUMPDIR 'D' /* file names from dumped directory */
68 #define GNUTYPE_LONGLINK 'K' /* long link name */
69 #define GNUTYPE_LONGNAME 'L' /* long file name */
70 #define GNUTYPE_MULTIVOL 'M' /* continuation of file from another volume */
71 #define GNUTYPE_NAMES 'N' /* file name that does not fit into main hdr */
72 #define GNUTYPE_SPARSE 'S' /* sparse file */
73 #define GNUTYPE_VOLHDR 'V' /* tape/volume header */
74
75
76 /* tar header */
77
78 #define BLOCKSIZE 512
79 #define SHORTNAMESIZE 100
80
81 struct tar_header
82 { /* byte offset */
83 char name[100]; /* 0 */
84 char mode[8]; /* 100 */
85 char uid[8]; /* 108 */
86 char gid[8]; /* 116 */
87 char size[12]; /* 124 */
88 char mtime[12]; /* 136 */
89 char chksum[8]; /* 148 */
90 char typeflag; /* 156 */
91 char linkname[100]; /* 157 */
92 char magic[6]; /* 257 */
93 char version[2]; /* 263 */
94 char uname[32]; /* 265 */
95 char gname[32]; /* 297 */
96 char devmajor[8]; /* 329 */
97 char devminor[8]; /* 337 */
98 char prefix[155]; /* 345 */
99 /* 500 */
100 };
101
102 union tar_buffer
103 {
104 char buffer[BLOCKSIZE];
105 struct tar_header header;
106 };
107
108 struct attr_item
109 {
110 struct attr_item *next;
111 char *fname;
112 int mode;
113 time_t time;
114 };
115
116 enum { TGZ_EXTRACT, TGZ_LIST, TGZ_INVALID };
117
118 char *prog;
119
error(const char * msg)120 void error(const char *msg)
121 {
122 fprintf(stderr, "%s: %s\n", prog, msg);
123 exit(1);
124 }
125
126 const char *TGZsuffix[] = { "\0", ".tar", ".tar.gz", ".taz", ".tgz", NULL };
127
128 /* return the file name of the TGZ archive */
129 /* or NULL if it does not exist */
130
TGZfname(const char * arcname)131 char *TGZfname (const char *arcname)
132 {
133 static char buffer[1024];
134 int origlen,i;
135
136 strcpy(buffer,arcname);
137 origlen = strlen(buffer);
138
139 for (i=0; TGZsuffix[i]; i++)
140 {
141 strcpy(buffer+origlen,TGZsuffix[i]);
142 if (access(buffer,F_OK) == 0)
143 return buffer;
144 }
145 return NULL;
146 }
147
148
149 /* error message for the filename */
150
TGZnotfound(const char * arcname)151 void TGZnotfound (const char *arcname)
152 {
153 int i;
154
155 fprintf(stderr,"%s: Couldn't find ",prog);
156 for (i = 0; TGZsuffix[i]; i++){
157 fprintf(stderr,(TGZsuffix[i + 1]) ? "%s%s, " : "or %s%s\n",
158 arcname,
159 TGZsuffix[i]);
160 }
161 exit(1);
162 }
163
164
165 /* convert octal digits to int */
166 /* on error return -1 */
167
getoct(char * p,int width)168 int getoct (char *p,int width)
169 {
170 int result = 0;
171 char c;
172
173 while (width--)
174 {
175 c = *p++;
176 if (c == 0)
177 {
178 break;
179 }
180 if (c == ' ')
181 {
182 continue;
183 }
184 if (c < '0' || c > '7')
185 {
186 return -1;
187 }
188 result = result * 8 + (c - '0');
189 }
190 return result;
191 }
192
193
194 /* convert time_t to string */
195 /* use the "YYYY/MM/DD hh:mm:ss" format */
196
strtime(time_t * t)197 char *strtime (time_t *t)
198 {
199 struct tm *local;
200 static char result[32];
201
202 local = localtime(t);
203 sprintf(result,"%4d/%02d/%02d %02d:%02d:%02d",
204 local->tm_year + 1900, local->tm_mon + 1, local->tm_mday,
205 local->tm_hour, local->tm_min, local->tm_sec);
206 return result;
207 }
208
209
210 /* set file time */
211
setfiletime(char * fname,time_t ftime)212 int setfiletime (char *fname,time_t ftime)
213 {
214 #ifdef _WIN32
215 static int isWinNT = -1;
216 SYSTEMTIME st;
217 FILETIME locft, modft;
218 struct tm *loctm;
219 HANDLE hFile;
220 int result;
221
222 loctm = localtime(&ftime);
223 if (loctm == NULL)
224 {
225 return -1;
226 }
227
228 st.wYear = (WORD)loctm->tm_year + 1900;
229 st.wMonth = (WORD)loctm->tm_mon + 1;
230 st.wDayOfWeek = (WORD)loctm->tm_wday;
231 st.wDay = (WORD)loctm->tm_mday;
232 st.wHour = (WORD)loctm->tm_hour;
233 st.wMinute = (WORD)loctm->tm_min;
234 st.wSecond = (WORD)loctm->tm_sec;
235 st.wMilliseconds = 0;
236 if (!SystemTimeToFileTime(&st, &locft) ||
237 !LocalFileTimeToFileTime(&locft, &modft))
238 {
239 return -1;
240 }
241
242 if (isWinNT < 0)
243 {
244 isWinNT = (GetVersion() < 0x80000000) ? 1 : 0;
245 }
246 hFile = CreateFile(fname, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
247 (isWinNT ? FILE_FLAG_BACKUP_SEMANTICS : 0),
248 NULL);
249 if (hFile == INVALID_HANDLE_VALUE)
250 {
251 return -1;
252 }
253 result = SetFileTime(hFile, NULL, NULL, &modft) ? 0 : -1;
254 CloseHandle(hFile);
255 return result;
256 #else
257 struct utimbuf settime;
258
259 settime.actime = settime.modtime = ftime;
260 return utime(fname,&settime);
261 #endif
262 }
263
264
265 /* push file attributes */
266
push_attr(struct attr_item ** list,char * fname,int mode,time_t time)267 void push_attr(struct attr_item **list,char *fname,int mode,time_t time)
268 {
269 struct attr_item *item;
270
271 item = (struct attr_item *)malloc(sizeof(struct attr_item));
272 if (item == NULL)
273 {
274 error("Out of memory");
275 }
276 item->fname = strdup(fname);
277 item->mode = mode;
278 item->time = time;
279 item->next = *list;
280 *list = item;
281 }
282
283
284 /* restore file attributes */
285
restore_attr(struct attr_item ** list)286 void restore_attr(struct attr_item **list)
287 {
288 struct attr_item *item, *prev;
289
290 for (item = *list; item != NULL; )
291 {
292 setfiletime(item->fname,item->time);
293 chmod(item->fname,item->mode);
294 prev = item;
295 item = item->next;
296 free(prev);
297 }
298 *list = NULL;
299 }
300
301
302 /* match regular expression */
303
304 #define ISSPECIAL(c) (((c) == '*') || ((c) == '/'))
305
ExprMatch(char * string,char * expr)306 int ExprMatch (char *string,char *expr)
307 {
308 while (1)
309 {
310 if (ISSPECIAL(*expr))
311 {
312 if (*expr == '/')
313 {
314 if (*string != '\\' && *string != '/')
315 {
316 return 0;
317 }
318 string ++; expr++;
319 }
320 else if (*expr == '*')
321 {
322 if (*expr ++ == 0)
323 {
324 return 1;
325 }
326 while (*++string != *expr){
327 if (*string == 0)
328 {
329 return 0;
330 }
331 }
332 }
333 }
334 else
335 {
336 if (*string != *expr)
337 {
338 return 0;
339 }
340 if (*expr++ == 0)
341 {
342 return 1;
343 }
344 string++;
345 }
346 }
347 }
348
349
350 /* recursive mkdir */
351 /* abort on ENOENT; ignore other errors like "directory already exists" */
352 /* return 1 if OK */
353 /* 0 on error */
354
makedir(char * newdir)355 int makedir (char *newdir)
356 {
357 char *buffer = strdup(newdir);
358 char *p;
359 int len = strlen(buffer);
360
361 if (len <= 0)
362 {
363 free(buffer);
364 return 0;
365 }
366 if (buffer[len-1] == '/')
367 {
368 buffer[len-1] = '\0';
369 }
370 if (mkdir(buffer, 0755) == 0)
371 {
372 free(buffer);
373 return 1;
374 }
375
376 p = buffer+1;
377 while (1)
378 {
379 char hold;
380
381 while(*p && *p != '\\' && *p != '/'){
382 p++;
383 }
384 hold = *p;
385 *p = 0;
386 if ((mkdir(buffer, 0755) == -1) && (errno == ENOENT))
387 {
388 fprintf(stderr,"%s: Couldn't create directory %s\n",prog,buffer);
389 free(buffer);
390 return 0;
391 }
392 if (hold == 0)
393 {
394 break;
395 }
396 *p++ = hold;
397 }
398 free(buffer);
399 return 1;
400 }
401
402
matchname(int arg,int argc,char ** argv,char * fname)403 int matchname (int arg,int argc,char **argv,char *fname)
404 {
405 if (arg == argc) /* no arguments given (untgz tgzarchive) */
406 {
407 return 1;
408 }
409
410 while (arg < argc){
411 if (ExprMatch(fname,argv[arg++]))
412 {
413 return 1;
414 }
415 }
416
417 return 0; /* ignore this for the moment being */
418 }
419
420
421 /* tar file list or extract */
422
tar(gzFile in,int action,int arg,int argc,char ** argv)423 int tar (gzFile in,int action,int arg,int argc,char **argv)
424 {
425 union tar_buffer buffer;
426 int len;
427 int err;
428 int getheader = 1;
429 int remaining = 0;
430 FILE *outfile = NULL;
431 char fname[BLOCKSIZE];
432 int tarmode;
433 time_t tartime;
434 struct attr_item *attributes = NULL;
435
436 if (action == TGZ_LIST)
437 {
438 printf(" date time size file\n"
439 " ---------- -------- --------- -------------------------------------\n");
440 }
441 while (1)
442 {
443 len = gzread(in, &buffer, BLOCKSIZE);
444 if (len < 0)
445 {
446 error(gzerror(in, &err));
447 }
448 /*
449 * Always expect complete blocks to process
450 * the tar information.
451 */
452 if (len != BLOCKSIZE)
453 {
454 action = TGZ_INVALID; /* force error exit */
455 remaining = 0; /* force I/O cleanup */
456 }
457
458 /*
459 * If we have to get a tar header
460 */
461 if (getheader >= 1)
462 {
463 /*
464 * if we met the end of the tar
465 * or the end-of-tar block,
466 * we are done
467 */
468 if (len == 0 || buffer.header.name[0] == 0)
469 {
470 break;
471 }
472
473 tarmode = getoct(buffer.header.mode,8);
474 tartime = (time_t)getoct(buffer.header.mtime,12);
475 if (tarmode == -1 || tartime == (time_t)-1)
476 {
477 buffer.header.name[0] = 0;
478 action = TGZ_INVALID;
479 }
480
481 if (getheader == 1)
482 {
483 strncpy(fname,buffer.header.name,SHORTNAMESIZE);
484 if (fname[SHORTNAMESIZE-1] != 0)
485 {
486 fname[SHORTNAMESIZE] = 0;
487 }
488 }
489 else
490 {
491 /*
492 * The file name is longer than SHORTNAMESIZE
493 */
494 if (strncmp(fname,buffer.header.name,SHORTNAMESIZE - 1) != 0)
495 {
496 error("bad long name");
497 }
498 getheader = 1;
499 }
500
501 /*
502 * Act according to the type flag
503 */
504 switch (buffer.header.typeflag)
505 {
506 case DIRTYPE:
507 if (action == TGZ_LIST)
508 {
509 printf(" %s <dir> %s\n",strtime(&tartime),fname);
510 }
511 if (action == TGZ_EXTRACT)
512 {
513 makedir(fname);
514 push_attr(&attributes,fname,tarmode,tartime);
515 }
516 break;
517 case REGTYPE:
518 case AREGTYPE:
519 remaining = getoct(buffer.header.size,12);
520 if (remaining == -1)
521 {
522 action = TGZ_INVALID;
523 break;
524 }
525 if (action == TGZ_LIST)
526 {
527 printf(" %s %9d %s\n",strtime(&tartime),remaining,fname);
528 }
529 else if (action == TGZ_EXTRACT)
530 {
531 if (matchname(arg,argc,argv,fname))
532 {
533 outfile = fopen(fname,"wb");
534 if (outfile == NULL)
535 {
536 /* try creating directory */
537 char *p = strrchr(fname, '/');
538 if (p != NULL) {
539 *p = '\0';
540 makedir(fname);
541 *p = '/';
542 outfile = fopen(fname,"wb");
543 }
544 }
545 if (outfile != NULL)
546 {
547 printf("Extracting %s\n",fname);
548 }
549 else
550 {
551 fprintf(stderr, "%s: Couldn't create %s",prog,fname);
552 }
553 }
554 else
555 {
556 outfile = NULL;
557 }
558 }
559 getheader = 0;
560 break;
561 case GNUTYPE_LONGLINK:
562 case GNUTYPE_LONGNAME:
563 remaining = getoct(buffer.header.size,12);
564 if (remaining < 0 || remaining >= BLOCKSIZE)
565 {
566 action = TGZ_INVALID;
567 break;
568 }
569 len = gzread(in, fname, BLOCKSIZE);
570 if (len < 0)
571 {
572 error(gzerror(in, &err));
573 }
574 if (fname[BLOCKSIZE - 1] != 0 || (int)strlen(fname) > remaining)
575 {
576 action = TGZ_INVALID;
577 break;
578 }
579 getheader = 2;
580 break;
581 default:
582 if (action == TGZ_LIST)
583 {
584 printf(" %s <---> %s\n",strtime(&tartime),fname);
585 }
586 break;
587 }
588 }
589 else
590 {
591 unsigned int bytes = (remaining > BLOCKSIZE) ? BLOCKSIZE : remaining;
592
593 if (outfile != NULL)
594 {
595 if (fwrite(&buffer,sizeof(char),bytes,outfile) != bytes)
596 {
597 fprintf(stderr,
598 "%s: Error writing %s -- skipping\n",prog,fname);
599 fclose(outfile);
600 outfile = NULL;
601 remove(fname);
602 }
603 }
604 remaining -= bytes;
605 }
606
607 if (remaining == 0)
608 {
609 getheader = 1;
610 if (outfile != NULL)
611 {
612 fclose(outfile);
613 outfile = NULL;
614 if (action != TGZ_INVALID)
615 {
616 push_attr(&attributes,fname,tarmode,tartime);
617 }
618 }
619 }
620
621 /*
622 * Abandon if errors are found
623 */
624 if (action == TGZ_INVALID)
625 {
626 error("broken archive");
627 break;
628 }
629 }
630
631 /*
632 * Restore file modes and time stamps
633 */
634 restore_attr(&attributes);
635
636 if (gzclose(in) != Z_OK)
637 {
638 error("failed gzclose");
639 }
640
641 return 0;
642 }
643
644
645 /* ============================================================ */
646
help(int exitval)647 void help(int exitval)
648 {
649 printf("untgz version 0.2.1\n"
650 " using zlib version %s\n\n",
651 zlibVersion());
652 printf("Usage: untgz file.tgz extract all files\n"
653 " untgz file.tgz fname ... extract selected files\n"
654 " untgz -l file.tgz list archive contents\n"
655 " untgz -h display this help\n");
656 exit(exitval);
657 }
658
659
660 /* ============================================================ */
661
662 #if defined(WIN32) && defined(__GNUC__)
663 int _CRT_glob = 0; /* disable argument globbing in MinGW */
664 #endif
665
main(int argc,char ** argv)666 int main(int argc,char **argv)
667 {
668 int action = TGZ_EXTRACT;
669 int arg = 1;
670 char *TGZfile;
671 gzFile f;
672
673 prog = strrchr(argv[0],'\\');
674 if (prog == NULL)
675 {
676 prog = strrchr(argv[0],'/');
677 if (prog == NULL)
678 {
679 prog = strrchr(argv[0],':');
680 if (prog == NULL)
681 {
682 prog = argv[0];
683 }
684 else
685 {
686 prog++;
687 }
688 }
689 else
690 {
691 prog++;
692 }
693 }
694 else
695 {
696 prog++;
697 }
698
699 if (argc == 1)
700 {
701 help(0);
702 }
703
704 if (strcmp(argv[arg],"-l") == 0)
705 {
706 action = TGZ_LIST;
707 if (argc == ++arg)
708 {
709 help(0);
710 }
711 }
712 else if (strcmp(argv[arg],"-h") == 0)
713 {
714 help(0);
715 }
716
717 if ((TGZfile = TGZfname(argv[arg])) == NULL)
718 {
719 TGZnotfound(argv[arg]);
720 }
721
722 ++arg;
723 if ((action == TGZ_LIST) && (arg != argc))
724 {
725 help(1);
726 }
727
728 /*
729 * Process the TGZ file
730 */
731 switch(action)
732 {
733 case TGZ_LIST:
734 case TGZ_EXTRACT:
735 f = gzopen(TGZfile,"rb");
736 if (f == NULL)
737 {
738 fprintf(stderr,"%s: Couldn't gzopen %s\n",prog,TGZfile);
739 return 1;
740 }
741 exit(tar(f, action, arg, argc, argv));
742 break;
743
744 default:
745 error("Unknown option");
746 exit(1);
747 }
748
749 return 0;
750 }
751