Home | History | Annotate | Line # | Download | only in untgz
      1 /*
      2  * untgz.c -- Display contents and extract files from a gzip'd TAR file
      3  *
      4  * written by Pedro A. Aranda Gutierrez <paag (at) tid.es>
      5  * adaptation to Unix by Jean-loup Gailly <jloup (at) gzip.org>
      6  * various fixes by Cosmin Truta <cosmint (at) 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 
    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 
    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 
    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   exit(1);
    161 }
    162 
    163 
    164 /* convert octal digits to int */
    165 /* on error return -1 */
    166 
    167 int getoct (char *p,int width)
    168 {
    169   int result = 0;
    170   char c;
    171 
    172   while (width--)
    173     {
    174       c = *p++;
    175       if (c == 0)
    176         break;
    177       if (c == ' ')
    178         continue;
    179       if (c < '0' || c > '7')
    180         return -1;
    181       result = result * 8 + (c - '0');
    182     }
    183   return result;
    184 }
    185 
    186 
    187 /* convert time_t to string */
    188 /* use the "YYYY/MM/DD hh:mm:ss" format */
    189 
    190 char *strtime (time_t *t)
    191 {
    192   struct tm   *local;
    193   static char result[32];
    194 
    195   local = localtime(t);
    196   sprintf(result,"%4d/%02d/%02d %02d:%02d:%02d",
    197           local->tm_year+1900, local->tm_mon+1, local->tm_mday,
    198           local->tm_hour, local->tm_min, local->tm_sec);
    199   return result;
    200 }
    201 
    202 
    203 /* set file time */
    204 
    205 int setfiletime (char *fname,time_t ftime)
    206 {
    207 #ifdef _WIN32
    208   static int isWinNT = -1;
    209   SYSTEMTIME st;
    210   FILETIME locft, modft;
    211   struct tm *loctm;
    212   HANDLE hFile;
    213   int result;
    214 
    215   loctm = localtime(&ftime);
    216   if (loctm == NULL)
    217     return -1;
    218 
    219   st.wYear         = (WORD)loctm->tm_year + 1900;
    220   st.wMonth        = (WORD)loctm->tm_mon + 1;
    221   st.wDayOfWeek    = (WORD)loctm->tm_wday;
    222   st.wDay          = (WORD)loctm->tm_mday;
    223   st.wHour         = (WORD)loctm->tm_hour;
    224   st.wMinute       = (WORD)loctm->tm_min;
    225   st.wSecond       = (WORD)loctm->tm_sec;
    226   st.wMilliseconds = 0;
    227   if (!SystemTimeToFileTime(&st, &locft) ||
    228       !LocalFileTimeToFileTime(&locft, &modft))
    229     return -1;
    230 
    231   if (isWinNT < 0)
    232     isWinNT = (GetVersion() < 0x80000000) ? 1 : 0;
    233   hFile = CreateFile(fname, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
    234                      (isWinNT ? FILE_FLAG_BACKUP_SEMANTICS : 0),
    235                      NULL);
    236   if (hFile == INVALID_HANDLE_VALUE)
    237     return -1;
    238   result = SetFileTime(hFile, NULL, NULL, &modft) ? 0 : -1;
    239   CloseHandle(hFile);
    240   return result;
    241 #else
    242   struct utimbuf settime;
    243 
    244   settime.actime = settime.modtime = ftime;
    245   return utime(fname,&settime);
    246 #endif
    247 }
    248 
    249 
    250 /* push file attributes */
    251 
    252 void push_attr(struct attr_item **list,char *fname,int mode,time_t time)
    253 {
    254   struct attr_item *item;
    255 
    256   item = (struct attr_item *)malloc(sizeof(struct attr_item));
    257   if (item == NULL)
    258     error("Out of memory");
    259   item->fname = strdup(fname);
    260   item->mode  = mode;
    261   item->time  = time;
    262   item->next  = *list;
    263   *list       = item;
    264 }
    265 
    266 
    267 /* restore file attributes */
    268 
    269 void restore_attr(struct attr_item **list)
    270 {
    271   struct attr_item *item, *prev;
    272 
    273   for (item = *list; item != NULL; )
    274     {
    275       setfiletime(item->fname,item->time);
    276       chmod(item->fname,item->mode);
    277       prev = item;
    278       item = item->next;
    279       free(prev);
    280     }
    281   *list = NULL;
    282 }
    283 
    284 
    285 /* match regular expression */
    286 
    287 #define ISSPECIAL(c) (((c) == '*') || ((c) == '/'))
    288 
    289 int ExprMatch (char *string,char *expr)
    290 {
    291   while (1)
    292     {
    293       if (ISSPECIAL(*expr))
    294         {
    295           if (*expr == '/')
    296             {
    297               if (*string != '\\' && *string != '/')
    298                 return 0;
    299               string ++; expr++;
    300             }
    301           else if (*expr == '*')
    302             {
    303               if (*expr ++ == 0)
    304                 return 1;
    305               while (*++string != *expr)
    306                 if (*string == 0)
    307                   return 0;
    308             }
    309         }
    310       else
    311         {
    312           if (*string != *expr)
    313             return 0;
    314           if (*expr++ == 0)
    315             return 1;
    316           string++;
    317         }
    318     }
    319 }
    320 
    321 
    322 /* recursive mkdir */
    323 /* abort on ENOENT; ignore other errors like "directory already exists" */
    324 /* return 1 if OK */
    325 /*        0 on error */
    326 
    327 int makedir (char *newdir)
    328 {
    329   char *buffer = strdup(newdir);
    330   char *p;
    331   int  len = strlen(buffer);
    332 
    333   if (len <= 0) {
    334     free(buffer);
    335     return 0;
    336   }
    337   if (buffer[len-1] == '/') {
    338     buffer[len-1] = '\0';
    339   }
    340   if (mkdir(buffer, 0755) == 0)
    341     {
    342       free(buffer);
    343       return 1;
    344     }
    345 
    346   p = buffer+1;
    347   while (1)
    348     {
    349       char hold;
    350 
    351       while(*p && *p != '\\' && *p != '/')
    352         p++;
    353       hold = *p;
    354       *p = 0;
    355       if ((mkdir(buffer, 0755) == -1) && (errno == ENOENT))
    356         {
    357           fprintf(stderr,"%s: Couldn't create directory %s\n",prog,buffer);
    358           free(buffer);
    359           return 0;
    360         }
    361       if (hold == 0)
    362         break;
    363       *p++ = hold;
    364     }
    365   free(buffer);
    366   return 1;
    367 }
    368 
    369 
    370 int matchname (int arg,int argc,char **argv,char *fname)
    371 {
    372   if (arg == argc)      /* no arguments given (untgz tgzarchive) */
    373     return 1;
    374 
    375   while (arg < argc)
    376     if (ExprMatch(fname,argv[arg++]))
    377       return 1;
    378 
    379   return 0; /* ignore this for the moment being */
    380 }
    381 
    382 
    383 /* tar file list or extract */
    384 
    385 int tar (gzFile in,int action,int arg,int argc,char **argv)
    386 {
    387   union  tar_buffer buffer;
    388   int    len;
    389   int    err;
    390   int    getheader = 1;
    391   int    remaining = 0;
    392   FILE   *outfile = NULL;
    393   char   fname[BLOCKSIZE];
    394   int    tarmode;
    395   time_t tartime;
    396   struct attr_item *attributes = NULL;
    397 
    398   if (action == TGZ_LIST)
    399     printf("    date      time     size                       file\n"
    400            " ---------- -------- --------- -------------------------------------\n");
    401   while (1)
    402     {
    403       len = gzread(in, &buffer, BLOCKSIZE);
    404       if (len < 0)
    405         error(gzerror(in, &err));
    406       /*
    407        * Always expect complete blocks to process
    408        * the tar information.
    409        */
    410       if (len != BLOCKSIZE)
    411         {
    412           action = TGZ_INVALID; /* force error exit */
    413           remaining = 0;        /* force I/O cleanup */
    414         }
    415 
    416       /*
    417        * If we have to get a tar header
    418        */
    419       if (getheader >= 1)
    420         {
    421           /*
    422            * if we met the end of the tar
    423            * or the end-of-tar block,
    424            * we are done
    425            */
    426           if (len == 0 || buffer.header.name[0] == 0)
    427             break;
    428 
    429           tarmode = getoct(buffer.header.mode,8);
    430           tartime = (time_t)getoct(buffer.header.mtime,12);
    431           if (tarmode == -1 || tartime == (time_t)-1)
    432             {
    433               buffer.header.name[0] = 0;
    434               action = TGZ_INVALID;
    435             }
    436 
    437           if (getheader == 1)
    438             {
    439               strncpy(fname,buffer.header.name,SHORTNAMESIZE);
    440               if (fname[SHORTNAMESIZE-1] != 0)
    441                   fname[SHORTNAMESIZE] = 0;
    442             }
    443           else
    444             {
    445               /*
    446                * The file name is longer than SHORTNAMESIZE
    447                */
    448               if (strncmp(fname,buffer.header.name,SHORTNAMESIZE-1) != 0)
    449                   error("bad long name");
    450               getheader = 1;
    451             }
    452 
    453           /*
    454            * Act according to the type flag
    455            */
    456           switch (buffer.header.typeflag)
    457             {
    458             case DIRTYPE:
    459               if (action == TGZ_LIST)
    460                 printf(" %s     <dir> %s\n",strtime(&tartime),fname);
    461               if (action == TGZ_EXTRACT)
    462                 {
    463                   makedir(fname);
    464                   push_attr(&attributes,fname,tarmode,tartime);
    465                 }
    466               break;
    467             case REGTYPE:
    468             case AREGTYPE:
    469               remaining = getoct(buffer.header.size,12);
    470               if (remaining == -1)
    471                 {
    472                   action = TGZ_INVALID;
    473                   break;
    474                 }
    475               if (action == TGZ_LIST)
    476                 printf(" %s %9d %s\n",strtime(&tartime),remaining,fname);
    477               else if (action == TGZ_EXTRACT)
    478                 {
    479                   if (matchname(arg,argc,argv,fname))
    480                     {
    481                       outfile = fopen(fname,"wb");
    482                       if (outfile == NULL) {
    483                         /* try creating directory */
    484                         char *p = strrchr(fname, '/');
    485                         if (p != NULL) {
    486                           *p = '\0';
    487                           makedir(fname);
    488                           *p = '/';
    489                           outfile = fopen(fname,"wb");
    490                         }
    491                       }
    492                       if (outfile != NULL)
    493                         printf("Extracting %s\n",fname);
    494                       else
    495                         fprintf(stderr, "%s: Couldn't create %s",prog,fname);
    496                     }
    497                   else
    498                     outfile = NULL;
    499                 }
    500               getheader = 0;
    501               break;
    502             case GNUTYPE_LONGLINK:
    503             case GNUTYPE_LONGNAME:
    504               remaining = getoct(buffer.header.size,12);
    505               if (remaining < 0 || remaining >= BLOCKSIZE)
    506                 {
    507                   action = TGZ_INVALID;
    508                   break;
    509                 }
    510               len = gzread(in, fname, BLOCKSIZE);
    511               if (len < 0)
    512                 error(gzerror(in, &err));
    513               if (fname[BLOCKSIZE-1] != 0 || (int)strlen(fname) > remaining)
    514                 {
    515                   action = TGZ_INVALID;
    516                   break;
    517                 }
    518               getheader = 2;
    519               break;
    520             default:
    521               if (action == TGZ_LIST)
    522                 printf(" %s     <---> %s\n",strtime(&tartime),fname);
    523               break;
    524             }
    525         }
    526       else
    527         {
    528           unsigned int bytes = (remaining > BLOCKSIZE) ? BLOCKSIZE : remaining;
    529 
    530           if (outfile != NULL)
    531             {
    532               if (fwrite(&buffer,sizeof(char),bytes,outfile) != bytes)
    533                 {
    534                   fprintf(stderr,
    535                     "%s: Error writing %s -- skipping\n",prog,fname);
    536                   fclose(outfile);
    537                   outfile = NULL;
    538                   remove(fname);
    539                 }
    540             }
    541           remaining -= bytes;
    542         }
    543 
    544       if (remaining == 0)
    545         {
    546           getheader = 1;
    547           if (outfile != NULL)
    548             {
    549               fclose(outfile);
    550               outfile = NULL;
    551               if (action != TGZ_INVALID)
    552                 push_attr(&attributes,fname,tarmode,tartime);
    553             }
    554         }
    555 
    556       /*
    557        * Abandon if errors are found
    558        */
    559       if (action == TGZ_INVALID)
    560         {
    561           error("broken archive");
    562           break;
    563         }
    564     }
    565 
    566   /*
    567    * Restore file modes and time stamps
    568    */
    569   restore_attr(&attributes);
    570 
    571   if (gzclose(in) != Z_OK)
    572     error("failed gzclose");
    573 
    574   return 0;
    575 }
    576 
    577 
    578 /* ============================================================ */
    579 
    580 void help(int exitval)
    581 {
    582   printf("untgz version 0.2.1\n"
    583          "  using zlib version %s\n\n",
    584          zlibVersion());
    585   printf("Usage: untgz file.tgz            extract all files\n"
    586          "       untgz file.tgz fname ...  extract selected files\n"
    587          "       untgz -l file.tgz         list archive contents\n"
    588          "       untgz -h                  display this help\n");
    589   exit(exitval);
    590 }
    591 
    592 
    593 /* ============================================================ */
    594 
    595 #if defined(WIN32) && defined(__GNUC__)
    596 int _CRT_glob = 0;      /* disable argument globbing in MinGW */
    597 #endif
    598 
    599 int main(int argc,char **argv)
    600 {
    601     int         action = TGZ_EXTRACT;
    602     int         arg = 1;
    603     char        *TGZfile;
    604     gzFile      f;
    605 
    606     prog = strrchr(argv[0],'\\');
    607     if (prog == NULL)
    608       {
    609         prog = strrchr(argv[0],'/');
    610         if (prog == NULL)
    611           {
    612             prog = strrchr(argv[0],':');
    613             if (prog == NULL)
    614               prog = argv[0];
    615             else
    616               prog++;
    617           }
    618         else
    619           prog++;
    620       }
    621     else
    622       prog++;
    623 
    624     if (argc == 1)
    625       help(0);
    626 
    627     if (strcmp(argv[arg],"-l") == 0)
    628       {
    629         action = TGZ_LIST;
    630         if (argc == ++arg)
    631           help(0);
    632       }
    633     else if (strcmp(argv[arg],"-h") == 0)
    634       {
    635         help(0);
    636       }
    637 
    638     if ((TGZfile = TGZfname(argv[arg])) == NULL)
    639       TGZnotfound(argv[arg]);
    640 
    641     ++arg;
    642     if ((action == TGZ_LIST) && (arg != argc))
    643       help(1);
    644 
    645 /*
    646  *  Process the TGZ file
    647  */
    648     switch(action)
    649       {
    650       case TGZ_LIST:
    651       case TGZ_EXTRACT:
    652         f = gzopen(TGZfile,"rb");
    653         if (f == NULL)
    654           {
    655             fprintf(stderr,"%s: Couldn't gzopen %s\n",prog,TGZfile);
    656             return 1;
    657           }
    658         exit(tar(f, action, arg, argc, argv));
    659       break;
    660 
    661       default:
    662         error("Unknown option");
    663         exit(1);
    664       }
    665 
    666     return 0;
    667 }
    668