• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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