/* * A public domain tar(1) program. * * Written by John Gilmore, ihnp4!hoptoad!gnu, starting 25 Aug 85. * * @(#)tar.c 1.34 11/6/87 Public Domain - gnu */ #include #include /* Needed for typedefs in tar.h */ extern char *malloc(); extern char *getenv(); extern char *strncpy(); extern char *optarg; /* Pointer to argument */ extern int optind; /* Global argv index from getopt */ /* * The following causes "tar.h" to produce definitions of all the * global variables, rather than just "extern" declarations of them. */ #define TAR_EXTERN /**/ #include "tar.h" /* * We should use a conversion routine that does reasonable error * checking -- atoi doesn't. For now, punt. FIXME. */ #define intconv atoi extern int getoldopt(); extern void read_and(); extern void list_archive(); extern void extract_archive(); extern void diff_archive(); extern void create_archive(); static FILE *namef; /* File to read names from */ static char **n_argv; /* Argv used by name routines */ static int n_argc; /* Argc used by name routines */ /* They also use "optind" from getopt(). */ void describe(); #ifdef AMIGA extern char *_TZ; /* timezone stuff */ #endif /* * Main routine for tar. */ main(argc, argv) int argc; char **argv; { /* Uncomment this message in particularly buggy versions... fprintf(stderr, "tar: You are running an experimental PD tar, maybe use /bin/tar.\n"); */ tar = "tar"; /* Set program name */ options(argc, argv); name_init(argc, argv); if (f_create) { if (f_extract || f_list || f_diff) goto dupflags; create_archive(); } else if (f_extract) { if (f_list || f_diff) goto dupflags; extr_init(); read_and(extract_archive); } else if (f_list) { if (f_diff) goto dupflags; read_and(list_archive); } else if (f_diff) { diff_init(); read_and(diff_archive); } else { dupflags: fprintf (stderr, "tar: you must specify exactly one of the c, t, x, or d options\n"); describe(); exit(EX_ARGSBAD); } exit(0); /* NOTREACHED */ } /* * Parse the options for tar. */ int options(argc, argv) int argc; char **argv; { register int c; /* Option letter */ #ifdef AMIGA char *timezone; #endif /* Set default option values */ blocking = DEFBLOCKING; /* From Makefile */ ar_file = getenv("TAPE"); /* From environment, or */ #ifdef AMIGA timezone = getenv("TZ"); if (timezone) { _TZ = malloc(strlen(timezone) + 1); /* man page unclear about */ strcpy(_TZ, timezone); /* whether this is needed */ free(timezone); } else _TZ = "CST6CDT"; tzset(); #endif if (ar_file == 0) #ifdef AMIGA ar_file = "ram:tarfile"; /* From Makefile */ #else ar_file = DEF_AR_FILE; /* From Makefile */ #endif /* Parse options */ #ifdef AMIGA while ((c = getoldopt(argc, argv, "aAb:BcdDf:hiklmopRstT:vxzZ") #else while ((c = getoldopt(argc, argv, "b:BcdDf:hiklmopRstT:vxzZ") #endif ) != EOF) { switch (c) { #ifdef AMIGA case 'a': f_archive_set++; break; case 'A': f_archive_check++; break; #endif case 'b': blocking = intconv(optarg); break; case 'B': f_reblock++; /* For reading 4.2BSD pipes */ break; case 'c': f_create++; break; case 'd': f_diff++; /* Find difference tape/disk */ break; case 'D': f_dironly++; /* Dump dir, not contents */ break; case 'f': ar_file = optarg; break; case 'h': f_follow_links++; /* follow symbolic links */ break; case 'i': f_ignorez++; /* Ignore zero records (eofs) */ /* * This can't be the default, because Unix tar * writes two records of zeros, then pads out the * block with garbage. */ break; case 'k': /* Don't overwrite files */ #ifdef NO_OPEN3 fprintf(stderr, "tar: can't do -k option on this system\n"); exit(EX_ARGSBAD); #else f_keep++; #endif break; case 'l': f_local_filesys++; break; case 'm': f_modified++; break; case 'o': /* Generate old archive */ f_oldarch++; break; case 'p': f_use_protection++; break; case 'R': f_sayblock++; /* Print block #s for debug */ break; /* of bad tar archives */ case 's': f_sorted_names++; /* Names to extr are sorted */ break; case 't': f_list++; f_verbose++; /* "t" output == "cv" or "xv" */ break; case 'T': name_file = optarg; f_namefile++; break; case 'v': f_verbose++; break; case 'x': f_extract++; break; case 'z': /* Easy to type */ case 'Z': /* Like the filename extension .Z */ f_compress++; break; case '?': describe(); exit(EX_ARGSBAD); } } blocksize = blocking * RECORDSIZE; } /* * Print as much help as the user's gonna get. * * We have to sprinkle in the KLUDGE lines because too many compilers * cannot handle character strings longer than about 512 bytes. Yuk! * In particular, MSDOS MSC 4.0 (and 5.0) and PDP-11 V7 Unix have this * problem. */ void describe() { /* sorry, can't #ifdef AMIGA inside string */ fputs("\ tar: valid options:\n\ -a set the archived bit of each file as it's added to the archive\n\ -b N blocking factor N (block size = Nx512 bytes)\n\ -B reblock as we read (for reading 4.2BSD pipes)\n\ -c create an archive\n\ -d find differences between archive and file system\n\ -D don't dump the contents of directories, just the directory\n\ ", stderr); /* KLUDGE */ fputs("\ -f F read/write archive from file or device F (or hostname:/ForD)\n\ -h don't dump symbolic links; dump the files they point to\n\ -i ignore blocks of zeros in the archive, which normally mean EOF\n\ -k keep existing files, don't overwrite them from the archive\n\ -l stay in the local file system (like dump(8)) when creating an archive\n\ ", stderr); /* KLUDGE */ fputs("\ -m don't extract file modified time\n\ -o write an old V7 format archive, rather than ANSI [draft 6] format\n\ -p do extract all protection information\n\ -R dump record number within archive with each message\n\ -s list of names to extract is sorted to match the archive\n\ -t list a table of contents of an archive\n\ ", stderr); /* KLUDGE */ fputs("\ -T F get names to extract or create from file F\n\ -v verbosely list what files we process\n\ -x extract files from an archive\n\ -z or Z run the archive through compress(1)\n\ ", stderr); } /* * Set up to gather file names for tar. * * They can either come from stdin or from argv. */ name_init(argc, argv) int argc; char **argv; { if (f_namefile) { if (optind < argc) { fprintf(stderr, "tar: too many args with -T option\n"); exit(EX_ARGSBAD); } if (!strcmp(name_file, "-")) { namef = stdin; } else { namef = fopen(name_file, "r"); if (namef == NULL) { fprintf(stderr, "tar: "); perror(name_file); exit(EX_BADFILE); } } } else { /* Get file names from argv, after options. */ n_argc = argc; n_argv = argv; } } /* * Get the next name from argv or the name file. * * Result is in static storage and can't be relied upon across two calls. */ char * name_next() { static char buffer[NAMSIZ+2]; /* Holding pattern */ register char *p; register char *q; if (namef == NULL) { /* Names come from argv, after options */ if (optind < n_argc) return n_argv[optind++]; return (char *)NULL; } for (;;) { p = fgets(buffer, NAMSIZ+1 /*nl*/, namef); if (p == NULL) return p; /* End of file */ q = p+strlen(p)-1; /* Find the newline */ if (q <= p) continue; /* Ignore empty lines */ *q-- = '\0'; /* Zap the newline */ while (q > p && *q == '/') *q-- = '\0'; /* Zap trailing /s */ return p; } /* NOTREACHED */ } /* * Close the name file, if any. */ name_close() { if (namef != NULL && namef != stdin) fclose(namef); } /* * Gather names in a list for scanning. * Could hash them later if we really care. * * If the names are already sorted to match the archive, we just * read them one by one. name_gather reads the first one, and it * is called by name_match as appropriate to read the next ones. * At EOF, the last name read is just left in the buffer. * This option lets users of small machines extract an arbitrary * number of files by doing "tar t" and editing down the list of files. */ name_gather() { register char *p; static struct name namebuf[1]; /* One-name buffer */ if (f_sorted_names) { p = name_next(); if (p) { namebuf[0].length = strlen(p); if (namebuf[0].length >= sizeof namebuf[0].name) { fprintf(stderr, "Argument name too long: %s\n", p); namebuf[0].length = (sizeof namebuf[0].name) - 1; } strncpy(namebuf[0].name, p, namebuf[0].length); namebuf[0].name[ namebuf[0].length ] = 0; namebuf[0].next = (struct name *)NULL; namebuf[0].found = 0; namelist = namebuf; namelast = namelist; } return; } /* Non sorted names -- read them all in */ while (NULL != (p = name_next())) { addname(p); } } /* * Add a name to the namelist. */ addname(name) char *name; /* pointer to name */ { register int i; /* Length of string */ register struct name *p; /* Current struct pointer */ i = strlen(name); /*NOSTRICT*/ p = (struct name *) malloc((unsigned)(i + sizeof(struct name) - NAMSIZ)); if (!p) { fprintf(stderr,"tar: cannot allocate mem for namelist entry\n"); exit(EX_SYSTEM); } p->next = (struct name *)NULL; p->length = i; strncpy(p->name, name, i); p->name[i] = '\0'; /* Null term */ p->found = 0; p->regexp = 0; /* Assume not a regular expression */ p->firstch = 1; /* Assume first char is literal */ if (index(name, '*') || index(name, '[') || index(name, '?')) { p->regexp = 1; /* No, it's a regexp */ if (name[0] == '*' || name[0] == '[' || name[0] == '?') p->firstch = 0; /* Not even 1st char literal */ } if (namelast) namelast->next = p; namelast = p; if (!namelist) namelist = p; } /* * Match a name from an archive, p, with a name from the namelist. */ name_match(p) register char *p; { register struct name *nlp; register int len; again: if (0 == (nlp = namelist)) /* Empty namelist is easy */ return 1; len = strlen(p); for (; nlp != 0; nlp = nlp->next) { /* If first chars don't match, quick skip */ if (nlp->firstch && nlp->name[0] != p[0]) continue; /* Regular expressions */ if (nlp->regexp) { if (wildmat(p, nlp->name)) { nlp->found = 1; /* Remember it matched */ return 1; /* We got a match */ } continue; } /* Plain Old Strings */ if (nlp->length <= len /* Archive len >= specified */ && (p[nlp->length] == '\0' || p[nlp->length] == '/') /* Full match on file/dirname */ && strncmp(p, nlp->name, nlp->length) == 0) /* Name compare */ { nlp->found = 1; /* Remember it matched */ return 1; /* We got a match */ } } /* * Filename from archive not found in namelist. * If we have the whole namelist here, just return 0. * Otherwise, read the next name in and compare it. * If this was the last name, namelist->found will remain on. * If not, we loop to compare the newly read name. */ if (f_sorted_names && namelist->found) { name_gather(); /* Read one more */ if (!namelist->found) goto again; } return 0; } /* * Print the names of things in the namelist that were not matched. */ names_notfound() { register struct name *nlp; register char *p; for (nlp = namelist; nlp != 0; nlp = nlp->next) { if (!nlp->found) { fprintf(stderr, "tar: %s not found in archive\n", nlp->name); } /* * We could free() the list, but the process is about * to die anyway, so save some CPU time. Amigas and * other similarly broken software will need to waste * the time, though. */ #ifndef unix if (!f_sorted_names) free(nlp); #endif } namelist = (struct name *)NULL; namelast = (struct name *)NULL; if (f_sorted_names) { while (0 != (p = name_next())) fprintf(stderr, "tar: %s not found in archive\n", p); } }