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