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