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