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 * 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