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 68struct 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 89union tar_buffer 90{ 91 char buffer[BLOCKSIZE]; 92 struct tar_header header; 93}; 94 95struct attr_item 96{ 97 struct attr_item *next; 98 char *fname; 99 int mode; 100 time_t time; 101}; 102 103enum { TGZ_EXTRACT, TGZ_LIST, TGZ_INVALID }; 104 105char *TGZfname OF((const char *)); 106void TGZnotfound OF((const char *)); 107 108int getoct OF((char *, int)); 109char *strtime OF((time_t *)); 110int setfiletime OF((char *, time_t)); 111void push_attr OF((struct attr_item **, char *, int, time_t)); 112void restore_attr OF((struct attr_item **)); 113 114int ExprMatch OF((char *, char *)); 115 116int makedir OF((char *)); 117int matchname OF((int, int, char **, char *)); 118 119void error OF((const char *)); 120int tar OF((gzFile, int, int, int, char **)); 121 122void help OF((int)); 123int main OF((int, char **)); 124 125char *prog; 126 127const 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 132char *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 152void 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 168int 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 191char *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 206int 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 253void 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 270void 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 290int 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 328int 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 371int 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 386int 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 581void 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 593void 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__) 603int _CRT_glob = 0; /* disable argument globbing in MinGW */ 604#endif 605 606int 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