cannam@89: /* cannam@89: * untgz.c -- Display contents and extract files from a gzip'd TAR file cannam@89: * cannam@89: * written by Pedro A. Aranda Gutierrez cannam@89: * adaptation to Unix by Jean-loup Gailly cannam@89: * various fixes by Cosmin Truta cannam@89: */ cannam@89: cannam@89: #include cannam@89: #include cannam@89: #include cannam@89: #include cannam@89: #include cannam@89: cannam@89: #include "zlib.h" cannam@89: cannam@89: #ifdef unix cannam@89: # include cannam@89: #else cannam@89: # include cannam@89: # include cannam@89: #endif cannam@89: cannam@89: #ifdef WIN32 cannam@89: #include cannam@89: # ifndef F_OK cannam@89: # define F_OK 0 cannam@89: # endif cannam@89: # define mkdir(dirname,mode) _mkdir(dirname) cannam@89: # ifdef _MSC_VER cannam@89: # define access(path,mode) _access(path,mode) cannam@89: # define chmod(path,mode) _chmod(path,mode) cannam@89: # define strdup(str) _strdup(str) cannam@89: # endif cannam@89: #else cannam@89: # include cannam@89: #endif cannam@89: cannam@89: cannam@89: /* values used in typeflag field */ cannam@89: cannam@89: #define REGTYPE '0' /* regular file */ cannam@89: #define AREGTYPE '\0' /* regular file */ cannam@89: #define LNKTYPE '1' /* link */ cannam@89: #define SYMTYPE '2' /* reserved */ cannam@89: #define CHRTYPE '3' /* character special */ cannam@89: #define BLKTYPE '4' /* block special */ cannam@89: #define DIRTYPE '5' /* directory */ cannam@89: #define FIFOTYPE '6' /* FIFO special */ cannam@89: #define CONTTYPE '7' /* reserved */ cannam@89: cannam@89: /* GNU tar extensions */ cannam@89: cannam@89: #define GNUTYPE_DUMPDIR 'D' /* file names from dumped directory */ cannam@89: #define GNUTYPE_LONGLINK 'K' /* long link name */ cannam@89: #define GNUTYPE_LONGNAME 'L' /* long file name */ cannam@89: #define GNUTYPE_MULTIVOL 'M' /* continuation of file from another volume */ cannam@89: #define GNUTYPE_NAMES 'N' /* file name that does not fit into main hdr */ cannam@89: #define GNUTYPE_SPARSE 'S' /* sparse file */ cannam@89: #define GNUTYPE_VOLHDR 'V' /* tape/volume header */ cannam@89: cannam@89: cannam@89: /* tar header */ cannam@89: cannam@89: #define BLOCKSIZE 512 cannam@89: #define SHORTNAMESIZE 100 cannam@89: cannam@89: struct tar_header cannam@89: { /* byte offset */ cannam@89: char name[100]; /* 0 */ cannam@89: char mode[8]; /* 100 */ cannam@89: char uid[8]; /* 108 */ cannam@89: char gid[8]; /* 116 */ cannam@89: char size[12]; /* 124 */ cannam@89: char mtime[12]; /* 136 */ cannam@89: char chksum[8]; /* 148 */ cannam@89: char typeflag; /* 156 */ cannam@89: char linkname[100]; /* 157 */ cannam@89: char magic[6]; /* 257 */ cannam@89: char version[2]; /* 263 */ cannam@89: char uname[32]; /* 265 */ cannam@89: char gname[32]; /* 297 */ cannam@89: char devmajor[8]; /* 329 */ cannam@89: char devminor[8]; /* 337 */ cannam@89: char prefix[155]; /* 345 */ cannam@89: /* 500 */ cannam@89: }; cannam@89: cannam@89: union tar_buffer cannam@89: { cannam@89: char buffer[BLOCKSIZE]; cannam@89: struct tar_header header; cannam@89: }; cannam@89: cannam@89: struct attr_item cannam@89: { cannam@89: struct attr_item *next; cannam@89: char *fname; cannam@89: int mode; cannam@89: time_t time; cannam@89: }; cannam@89: cannam@89: enum { TGZ_EXTRACT, TGZ_LIST, TGZ_INVALID }; cannam@89: cannam@89: char *TGZfname OF((const char *)); cannam@89: void TGZnotfound OF((const char *)); cannam@89: cannam@89: int getoct OF((char *, int)); cannam@89: char *strtime OF((time_t *)); cannam@89: int setfiletime OF((char *, time_t)); cannam@89: void push_attr OF((struct attr_item **, char *, int, time_t)); cannam@89: void restore_attr OF((struct attr_item **)); cannam@89: cannam@89: int ExprMatch OF((char *, char *)); cannam@89: cannam@89: int makedir OF((char *)); cannam@89: int matchname OF((int, int, char **, char *)); cannam@89: cannam@89: void error OF((const char *)); cannam@89: int tar OF((gzFile, int, int, int, char **)); cannam@89: cannam@89: void help OF((int)); cannam@89: int main OF((int, char **)); cannam@89: cannam@89: char *prog; cannam@89: cannam@89: const char *TGZsuffix[] = { "\0", ".tar", ".tar.gz", ".taz", ".tgz", NULL }; cannam@89: cannam@89: /* return the file name of the TGZ archive */ cannam@89: /* or NULL if it does not exist */ cannam@89: cannam@89: char *TGZfname (const char *arcname) cannam@89: { cannam@89: static char buffer[1024]; cannam@89: int origlen,i; cannam@89: cannam@89: strcpy(buffer,arcname); cannam@89: origlen = strlen(buffer); cannam@89: cannam@89: for (i=0; TGZsuffix[i]; i++) cannam@89: { cannam@89: strcpy(buffer+origlen,TGZsuffix[i]); cannam@89: if (access(buffer,F_OK) == 0) cannam@89: return buffer; cannam@89: } cannam@89: return NULL; cannam@89: } cannam@89: cannam@89: cannam@89: /* error message for the filename */ cannam@89: cannam@89: void TGZnotfound (const char *arcname) cannam@89: { cannam@89: int i; cannam@89: cannam@89: fprintf(stderr,"%s: Couldn't find ",prog); cannam@89: for (i=0;TGZsuffix[i];i++) cannam@89: fprintf(stderr,(TGZsuffix[i+1]) ? "%s%s, " : "or %s%s\n", cannam@89: arcname, cannam@89: TGZsuffix[i]); cannam@89: exit(1); cannam@89: } cannam@89: cannam@89: cannam@89: /* convert octal digits to int */ cannam@89: /* on error return -1 */ cannam@89: cannam@89: int getoct (char *p,int width) cannam@89: { cannam@89: int result = 0; cannam@89: char c; cannam@89: cannam@89: while (width--) cannam@89: { cannam@89: c = *p++; cannam@89: if (c == 0) cannam@89: break; cannam@89: if (c == ' ') cannam@89: continue; cannam@89: if (c < '0' || c > '7') cannam@89: return -1; cannam@89: result = result * 8 + (c - '0'); cannam@89: } cannam@89: return result; cannam@89: } cannam@89: cannam@89: cannam@89: /* convert time_t to string */ cannam@89: /* use the "YYYY/MM/DD hh:mm:ss" format */ cannam@89: cannam@89: char *strtime (time_t *t) cannam@89: { cannam@89: struct tm *local; cannam@89: static char result[32]; cannam@89: cannam@89: local = localtime(t); cannam@89: sprintf(result,"%4d/%02d/%02d %02d:%02d:%02d", cannam@89: local->tm_year+1900, local->tm_mon+1, local->tm_mday, cannam@89: local->tm_hour, local->tm_min, local->tm_sec); cannam@89: return result; cannam@89: } cannam@89: cannam@89: cannam@89: /* set file time */ cannam@89: cannam@89: int setfiletime (char *fname,time_t ftime) cannam@89: { cannam@89: #ifdef WIN32 cannam@89: static int isWinNT = -1; cannam@89: SYSTEMTIME st; cannam@89: FILETIME locft, modft; cannam@89: struct tm *loctm; cannam@89: HANDLE hFile; cannam@89: int result; cannam@89: cannam@89: loctm = localtime(&ftime); cannam@89: if (loctm == NULL) cannam@89: return -1; cannam@89: cannam@89: st.wYear = (WORD)loctm->tm_year + 1900; cannam@89: st.wMonth = (WORD)loctm->tm_mon + 1; cannam@89: st.wDayOfWeek = (WORD)loctm->tm_wday; cannam@89: st.wDay = (WORD)loctm->tm_mday; cannam@89: st.wHour = (WORD)loctm->tm_hour; cannam@89: st.wMinute = (WORD)loctm->tm_min; cannam@89: st.wSecond = (WORD)loctm->tm_sec; cannam@89: st.wMilliseconds = 0; cannam@89: if (!SystemTimeToFileTime(&st, &locft) || cannam@89: !LocalFileTimeToFileTime(&locft, &modft)) cannam@89: return -1; cannam@89: cannam@89: if (isWinNT < 0) cannam@89: isWinNT = (GetVersion() < 0x80000000) ? 1 : 0; cannam@89: hFile = CreateFile(fname, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, cannam@89: (isWinNT ? FILE_FLAG_BACKUP_SEMANTICS : 0), cannam@89: NULL); cannam@89: if (hFile == INVALID_HANDLE_VALUE) cannam@89: return -1; cannam@89: result = SetFileTime(hFile, NULL, NULL, &modft) ? 0 : -1; cannam@89: CloseHandle(hFile); cannam@89: return result; cannam@89: #else cannam@89: struct utimbuf settime; cannam@89: cannam@89: settime.actime = settime.modtime = ftime; cannam@89: return utime(fname,&settime); cannam@89: #endif cannam@89: } cannam@89: cannam@89: cannam@89: /* push file attributes */ cannam@89: cannam@89: void push_attr(struct attr_item **list,char *fname,int mode,time_t time) cannam@89: { cannam@89: struct attr_item *item; cannam@89: cannam@89: item = (struct attr_item *)malloc(sizeof(struct attr_item)); cannam@89: if (item == NULL) cannam@89: error("Out of memory"); cannam@89: item->fname = strdup(fname); cannam@89: item->mode = mode; cannam@89: item->time = time; cannam@89: item->next = *list; cannam@89: *list = item; cannam@89: } cannam@89: cannam@89: cannam@89: /* restore file attributes */ cannam@89: cannam@89: void restore_attr(struct attr_item **list) cannam@89: { cannam@89: struct attr_item *item, *prev; cannam@89: cannam@89: for (item = *list; item != NULL; ) cannam@89: { cannam@89: setfiletime(item->fname,item->time); cannam@89: chmod(item->fname,item->mode); cannam@89: prev = item; cannam@89: item = item->next; cannam@89: free(prev); cannam@89: } cannam@89: *list = NULL; cannam@89: } cannam@89: cannam@89: cannam@89: /* match regular expression */ cannam@89: cannam@89: #define ISSPECIAL(c) (((c) == '*') || ((c) == '/')) cannam@89: cannam@89: int ExprMatch (char *string,char *expr) cannam@89: { cannam@89: while (1) cannam@89: { cannam@89: if (ISSPECIAL(*expr)) cannam@89: { cannam@89: if (*expr == '/') cannam@89: { cannam@89: if (*string != '\\' && *string != '/') cannam@89: return 0; cannam@89: string ++; expr++; cannam@89: } cannam@89: else if (*expr == '*') cannam@89: { cannam@89: if (*expr ++ == 0) cannam@89: return 1; cannam@89: while (*++string != *expr) cannam@89: if (*string == 0) cannam@89: return 0; cannam@89: } cannam@89: } cannam@89: else cannam@89: { cannam@89: if (*string != *expr) cannam@89: return 0; cannam@89: if (*expr++ == 0) cannam@89: return 1; cannam@89: string++; cannam@89: } cannam@89: } cannam@89: } cannam@89: cannam@89: cannam@89: /* recursive mkdir */ cannam@89: /* abort on ENOENT; ignore other errors like "directory already exists" */ cannam@89: /* return 1 if OK */ cannam@89: /* 0 on error */ cannam@89: cannam@89: int makedir (char *newdir) cannam@89: { cannam@89: char *buffer = strdup(newdir); cannam@89: char *p; cannam@89: int len = strlen(buffer); cannam@89: cannam@89: if (len <= 0) { cannam@89: free(buffer); cannam@89: return 0; cannam@89: } cannam@89: if (buffer[len-1] == '/') { cannam@89: buffer[len-1] = '\0'; cannam@89: } cannam@89: if (mkdir(buffer, 0755) == 0) cannam@89: { cannam@89: free(buffer); cannam@89: return 1; cannam@89: } cannam@89: cannam@89: p = buffer+1; cannam@89: while (1) cannam@89: { cannam@89: char hold; cannam@89: cannam@89: while(*p && *p != '\\' && *p != '/') cannam@89: p++; cannam@89: hold = *p; cannam@89: *p = 0; cannam@89: if ((mkdir(buffer, 0755) == -1) && (errno == ENOENT)) cannam@89: { cannam@89: fprintf(stderr,"%s: Couldn't create directory %s\n",prog,buffer); cannam@89: free(buffer); cannam@89: return 0; cannam@89: } cannam@89: if (hold == 0) cannam@89: break; cannam@89: *p++ = hold; cannam@89: } cannam@89: free(buffer); cannam@89: return 1; cannam@89: } cannam@89: cannam@89: cannam@89: int matchname (int arg,int argc,char **argv,char *fname) cannam@89: { cannam@89: if (arg == argc) /* no arguments given (untgz tgzarchive) */ cannam@89: return 1; cannam@89: cannam@89: while (arg < argc) cannam@89: if (ExprMatch(fname,argv[arg++])) cannam@89: return 1; cannam@89: cannam@89: return 0; /* ignore this for the moment being */ cannam@89: } cannam@89: cannam@89: cannam@89: /* tar file list or extract */ cannam@89: cannam@89: int tar (gzFile in,int action,int arg,int argc,char **argv) cannam@89: { cannam@89: union tar_buffer buffer; cannam@89: int len; cannam@89: int err; cannam@89: int getheader = 1; cannam@89: int remaining = 0; cannam@89: FILE *outfile = NULL; cannam@89: char fname[BLOCKSIZE]; cannam@89: int tarmode; cannam@89: time_t tartime; cannam@89: struct attr_item *attributes = NULL; cannam@89: cannam@89: if (action == TGZ_LIST) cannam@89: printf(" date time size file\n" cannam@89: " ---------- -------- --------- -------------------------------------\n"); cannam@89: while (1) cannam@89: { cannam@89: len = gzread(in, &buffer, BLOCKSIZE); cannam@89: if (len < 0) cannam@89: error(gzerror(in, &err)); cannam@89: /* cannam@89: * Always expect complete blocks to process cannam@89: * the tar information. cannam@89: */ cannam@89: if (len != BLOCKSIZE) cannam@89: { cannam@89: action = TGZ_INVALID; /* force error exit */ cannam@89: remaining = 0; /* force I/O cleanup */ cannam@89: } cannam@89: cannam@89: /* cannam@89: * If we have to get a tar header cannam@89: */ cannam@89: if (getheader >= 1) cannam@89: { cannam@89: /* cannam@89: * if we met the end of the tar cannam@89: * or the end-of-tar block, cannam@89: * we are done cannam@89: */ cannam@89: if (len == 0 || buffer.header.name[0] == 0) cannam@89: break; cannam@89: cannam@89: tarmode = getoct(buffer.header.mode,8); cannam@89: tartime = (time_t)getoct(buffer.header.mtime,12); cannam@89: if (tarmode == -1 || tartime == (time_t)-1) cannam@89: { cannam@89: buffer.header.name[0] = 0; cannam@89: action = TGZ_INVALID; cannam@89: } cannam@89: cannam@89: if (getheader == 1) cannam@89: { cannam@89: strncpy(fname,buffer.header.name,SHORTNAMESIZE); cannam@89: if (fname[SHORTNAMESIZE-1] != 0) cannam@89: fname[SHORTNAMESIZE] = 0; cannam@89: } cannam@89: else cannam@89: { cannam@89: /* cannam@89: * The file name is longer than SHORTNAMESIZE cannam@89: */ cannam@89: if (strncmp(fname,buffer.header.name,SHORTNAMESIZE-1) != 0) cannam@89: error("bad long name"); cannam@89: getheader = 1; cannam@89: } cannam@89: cannam@89: /* cannam@89: * Act according to the type flag cannam@89: */ cannam@89: switch (buffer.header.typeflag) cannam@89: { cannam@89: case DIRTYPE: cannam@89: if (action == TGZ_LIST) cannam@89: printf(" %s %s\n",strtime(&tartime),fname); cannam@89: if (action == TGZ_EXTRACT) cannam@89: { cannam@89: makedir(fname); cannam@89: push_attr(&attributes,fname,tarmode,tartime); cannam@89: } cannam@89: break; cannam@89: case REGTYPE: cannam@89: case AREGTYPE: cannam@89: remaining = getoct(buffer.header.size,12); cannam@89: if (remaining == -1) cannam@89: { cannam@89: action = TGZ_INVALID; cannam@89: break; cannam@89: } cannam@89: if (action == TGZ_LIST) cannam@89: printf(" %s %9d %s\n",strtime(&tartime),remaining,fname); cannam@89: else if (action == TGZ_EXTRACT) cannam@89: { cannam@89: if (matchname(arg,argc,argv,fname)) cannam@89: { cannam@89: outfile = fopen(fname,"wb"); cannam@89: if (outfile == NULL) { cannam@89: /* try creating directory */ cannam@89: char *p = strrchr(fname, '/'); cannam@89: if (p != NULL) { cannam@89: *p = '\0'; cannam@89: makedir(fname); cannam@89: *p = '/'; cannam@89: outfile = fopen(fname,"wb"); cannam@89: } cannam@89: } cannam@89: if (outfile != NULL) cannam@89: printf("Extracting %s\n",fname); cannam@89: else cannam@89: fprintf(stderr, "%s: Couldn't create %s",prog,fname); cannam@89: } cannam@89: else cannam@89: outfile = NULL; cannam@89: } cannam@89: getheader = 0; cannam@89: break; cannam@89: case GNUTYPE_LONGLINK: cannam@89: case GNUTYPE_LONGNAME: cannam@89: remaining = getoct(buffer.header.size,12); cannam@89: if (remaining < 0 || remaining >= BLOCKSIZE) cannam@89: { cannam@89: action = TGZ_INVALID; cannam@89: break; cannam@89: } cannam@89: len = gzread(in, fname, BLOCKSIZE); cannam@89: if (len < 0) cannam@89: error(gzerror(in, &err)); cannam@89: if (fname[BLOCKSIZE-1] != 0 || (int)strlen(fname) > remaining) cannam@89: { cannam@89: action = TGZ_INVALID; cannam@89: break; cannam@89: } cannam@89: getheader = 2; cannam@89: break; cannam@89: default: cannam@89: if (action == TGZ_LIST) cannam@89: printf(" %s <---> %s\n",strtime(&tartime),fname); cannam@89: break; cannam@89: } cannam@89: } cannam@89: else cannam@89: { cannam@89: unsigned int bytes = (remaining > BLOCKSIZE) ? BLOCKSIZE : remaining; cannam@89: cannam@89: if (outfile != NULL) cannam@89: { cannam@89: if (fwrite(&buffer,sizeof(char),bytes,outfile) != bytes) cannam@89: { cannam@89: fprintf(stderr, cannam@89: "%s: Error writing %s -- skipping\n",prog,fname); cannam@89: fclose(outfile); cannam@89: outfile = NULL; cannam@89: remove(fname); cannam@89: } cannam@89: } cannam@89: remaining -= bytes; cannam@89: } cannam@89: cannam@89: if (remaining == 0) cannam@89: { cannam@89: getheader = 1; cannam@89: if (outfile != NULL) cannam@89: { cannam@89: fclose(outfile); cannam@89: outfile = NULL; cannam@89: if (action != TGZ_INVALID) cannam@89: push_attr(&attributes,fname,tarmode,tartime); cannam@89: } cannam@89: } cannam@89: cannam@89: /* cannam@89: * Abandon if errors are found cannam@89: */ cannam@89: if (action == TGZ_INVALID) cannam@89: { cannam@89: error("broken archive"); cannam@89: break; cannam@89: } cannam@89: } cannam@89: cannam@89: /* cannam@89: * Restore file modes and time stamps cannam@89: */ cannam@89: restore_attr(&attributes); cannam@89: cannam@89: if (gzclose(in) != Z_OK) cannam@89: error("failed gzclose"); cannam@89: cannam@89: return 0; cannam@89: } cannam@89: cannam@89: cannam@89: /* ============================================================ */ cannam@89: cannam@89: void help(int exitval) cannam@89: { cannam@89: printf("untgz version 0.2.1\n" cannam@89: " using zlib version %s\n\n", cannam@89: zlibVersion()); cannam@89: printf("Usage: untgz file.tgz extract all files\n" cannam@89: " untgz file.tgz fname ... extract selected files\n" cannam@89: " untgz -l file.tgz list archive contents\n" cannam@89: " untgz -h display this help\n"); cannam@89: exit(exitval); cannam@89: } cannam@89: cannam@89: void error(const char *msg) cannam@89: { cannam@89: fprintf(stderr, "%s: %s\n", prog, msg); cannam@89: exit(1); cannam@89: } cannam@89: cannam@89: cannam@89: /* ============================================================ */ cannam@89: cannam@89: #if defined(WIN32) && defined(__GNUC__) cannam@89: int _CRT_glob = 0; /* disable argument globbing in MinGW */ cannam@89: #endif cannam@89: cannam@89: int main(int argc,char **argv) cannam@89: { cannam@89: int action = TGZ_EXTRACT; cannam@89: int arg = 1; cannam@89: char *TGZfile; cannam@89: gzFile *f; cannam@89: cannam@89: prog = strrchr(argv[0],'\\'); cannam@89: if (prog == NULL) cannam@89: { cannam@89: prog = strrchr(argv[0],'/'); cannam@89: if (prog == NULL) cannam@89: { cannam@89: prog = strrchr(argv[0],':'); cannam@89: if (prog == NULL) cannam@89: prog = argv[0]; cannam@89: else cannam@89: prog++; cannam@89: } cannam@89: else cannam@89: prog++; cannam@89: } cannam@89: else cannam@89: prog++; cannam@89: cannam@89: if (argc == 1) cannam@89: help(0); cannam@89: cannam@89: if (strcmp(argv[arg],"-l") == 0) cannam@89: { cannam@89: action = TGZ_LIST; cannam@89: if (argc == ++arg) cannam@89: help(0); cannam@89: } cannam@89: else if (strcmp(argv[arg],"-h") == 0) cannam@89: { cannam@89: help(0); cannam@89: } cannam@89: cannam@89: if ((TGZfile = TGZfname(argv[arg])) == NULL) cannam@89: TGZnotfound(argv[arg]); cannam@89: cannam@89: ++arg; cannam@89: if ((action == TGZ_LIST) && (arg != argc)) cannam@89: help(1); cannam@89: cannam@89: /* cannam@89: * Process the TGZ file cannam@89: */ cannam@89: switch(action) cannam@89: { cannam@89: case TGZ_LIST: cannam@89: case TGZ_EXTRACT: cannam@89: f = gzopen(TGZfile,"rb"); cannam@89: if (f == NULL) cannam@89: { cannam@89: fprintf(stderr,"%s: Couldn't gzopen %s\n",prog,TGZfile); cannam@89: return 1; cannam@89: } cannam@89: exit(tar(f, action, arg, argc, argv)); cannam@89: break; cannam@89: cannam@89: default: cannam@89: error("Unknown option"); cannam@89: exit(1); cannam@89: } cannam@89: cannam@89: return 0; cannam@89: }