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