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