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