• Home
  • History
  • Annotate
  • 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  
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