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