home *** CD-ROM | disk | FTP | other *** search
- /*
- * A public domain tar(1) program.
- *
- * Written by John Gilmore, ihnp4!hoptoad!gnu, starting 25 Aug 85.
- * MS-DOS port 2/87 by Eric Roskos.
- * Minix port 3/88 by Eric Roskos.
- *
- * @(#)tar.c 1.21 10/29/86 Public Domain - gnu
- */
-
- #include <stdio.h>
- #include <sys/types.h> /* Needed for typedefs in tar.h */
-
- #ifdef MSDOS
- #include <conio.h>
- #include <fcntl.h>
- #endif
- #ifdef V7
- FILE *fopen();
- char *fgets();
- #endif
-
- extern char *malloc();
- extern char *
- strncpy(), *index(), *rindex();
- 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 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(). */
-
- static char *sccsid =
- "@(#)tar.c 1.21 10/29/86 Public Domain - gnu - Minix port 3/05/88 Eric Roskos (csed-1!roskos)";
-
- #ifdef MSDOS
- /*
- * see convmode, below. This list is the list of files that should be
- * opened with mode O_BINARY to prevent CR/LF conversions while they
- * are being read in. FIXME: it is my intent to eventually add an
- * option to the command line that lets you add arbitrarily many new
- * extensions to this list, so people won't have problems with the
- * list being inadequate for them. I wish there was an easier way, but
- * this one is fairly consistent if you think about it.
- */
- #define NBINEXTS 20
-
- static char *binexts[NBINEXTS] = /* extensions for O_BINARY files */
- {
- "com",
- "exe",
- "obj",
- 0 /* required */
- };
-
- #endif
-
- void describe();
-
-
- /*
- * 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 */
- #ifdef MSDOS
- physdrv = 0; /* set default drive */
- devsize = 720; /* default drive size */
- ftty = open("CON", O_RDWR); /* open console */
- #else /* !MSDOS */
- ftty = open("/dev/tty", 2);
- #endif /* !MSDOS */
- if (ftty < 0)
- {
- fprintf(stderr, "Can't open %s for I/O\n",
- #ifdef MSDOS
- "console"
- #else
- "/dev/tty"
- #endif
- );
- exit(EX_SYSTEM);
- }
-
- options(argc, argv);
-
- name_init(argc, argv);
-
- #ifdef MSDOS
- if (f_phys)
- {
- uprintf(ftty,"tar: archive on %dK drive %c\n",
- devsize/2, 'A' + physdrv);
- uprintf(ftty,"tar: insert %s disk in drive '%c' and press [Enter]: ",
- f_create? "formatted" : "first",
- 'A' + physdrv);
- while (ugetc(ftty)!='\n') ;
- }
- #endif
-
- if (f_create)
- {
- if (f_extract || f_list)
- goto dupflags;
- create_archive();
- }
- else
- if (f_extract)
- {
- if (f_list)
- goto dupflags;
- read_and(extract_archive);
- }
- else
- if (f_list)
- {
- read_and(list_archive);
- }
- else
- {
- dupflags:
- fprintf(stderr,
- "tar: you must specify exactly one of the c, t, or x options\n");
- describe();
- exit(EX_ARGSBAD);
- }
- putchar('\n');
- fflush(stdout);
- #ifndef MSDOS
- sync(); /* insure all floppy buffers are written out */
- #endif
- exit(0);
- }
-
-
- /*
- * Parse the options for tar.
- */
- int
- options(argc, argv)
- int argc;
- char **argv;
- {
- register int c; /* Option letter */
- void addbinext();
-
- /* Set default option values */
- blocking = DEFBLOCKING; /* From Makefile */
- ar_file = DEF_AR_FILE; /* From Makefile */
-
- /* Parse options */
- while ((c = getoldopt(argc, argv, "b:BcdDf:hikmopsS:tT:u:vV:xzZ")
- ) != EOF)
- {
- switch (c)
- {
-
- case 'b':
- blocking = intconv(optarg);
- break;
-
- case 'B':
- f_reblock++; /* For reading 4.2BSD pipes */
- break;
-
- case 'c':
- f_create++;
- break;
-
- case 'd':
- f_debug++; /* Debugging code */
- break; /* Yes, even with dbx */
-
- case 'D':
- f_sayblock++; /* Print block #s for debug */
- break; /* of bad tar archives */
-
- 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 */
- f_keep++;
- break;
-
- case 'm':
- f_modified++;
- break;
-
- case 'o': /* Generate old archive */
- f_oldarch++;
- break;
-
- case 'p':
- f_use_protection++;
- (void) umask(0); /* Turn off kernel "help" */
- break;
-
- case 's':
- f_sorted_names++; /* Names to extr are sorted */
- break;
- #ifdef MSDOS
- case 'S':
- devsize = atoi(optarg); /* size of DOS disk drive */
- devsize <<= 1; /* convert K to blocks */
- break;
- #endif
- case 't':
- f_list++;
- break;
-
- case 'T':
- name_file = optarg;
- f_namefile++;
- break;
- #ifdef MSDOS
- case 'u':
- addbinext(optarg);
- break;
- #endif
- case 'v':
- f_verbose++;
- break;
- #ifdef MSDOS
- case 'V':
- f_phys++;
- physdrv = toupper(*optarg) - 'A';
- if (physdrv > 4 || physdrv < 0)
- {
- fprintf(stderr, "tar: drive letter for -V must be A-D\n");
- exit(EX_ARGSBAD);
- }
- break;
- #endif /* MSDOS */
-
- case 'x':
- f_extract++;
- break;
-
- case 'z': /* Easy to type */
- case 'Z': /* Like the filename extension .Z */
- #ifndef MSDOS
- f_compress++;
- #else
- fprintf(stderr, "Running compress as a subprocess is not supported under DOS.\n");
- fprintf(stderr, "Run compress separately instead, for same effect.\n");
- #endif
- break;
-
- default:
- case '?':
- describe();
- exit(EX_ARGSBAD);
-
- }
- }
-
- blocksize = blocking * RECORDSIZE;
- }
-
-
- /* FIXME, describe tar options here */
- void
- describe()
- {
-
- fputs("tar: valid options:\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 dump record number within archive with each message\n\
- -f F read/write archive from file or device F\n", stderr);
- fputs("-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\
- -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", stderr);
- #ifdef MSDOS
- fputs("-S X device for -V option is X Kbyte drive\n", stderr);
- #endif
- fputs("-s list of names to extract is sorted to match the archive\n\
- -t list a table of contents of an archive\n\
- -T F get names to extract or create from file F\n", stderr);
- #ifdef MSDOS
- fputs("\
- -u X add X to list of file extensions to be opened in BINARY mode\n\
- (use '.' to denote 'files with no extension')\n\
- -V X use drive X (X=A..D) in multivolume mode; ignore -f if present\n",
- stderr);
- #endif
- fputs("\
- -v verbosely list what files we process\n\
- -x extract files from an archive\n", stderr);
- #ifndef MSDOS
-
- /*
- * regrettably, DOS doesn't have real pipes, just artificial shell-level
- * ones. It is better to just use those.
- */
- fputs("\
- -z or Z run the archive through compress(1)\n", stderr);
- #endif
- }
-
-
- /*
- * 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;
- }
- }
-
- /*
- * Name translation function for MS-DOS support; can also be
- * extended via #ifdef for other os's.
- *
- * Convert a name to one suitable to this OS. If this can't be done
- * automatically, let the user choose a name.
- *
- * The user prompting is done only if stdin isatty.
- *
- * It is important to understand the name translation, which occurs
- * in two steps. First, the actual name string passed in s is modified
- * in place to change case and direction of slashes, because DOS's
- * directory routines return uppercase names with backslashes, which
- * are not suitable for Unix. This changes the string into a unix-like
- * filename. Second, this string is copied into a local buffer and
- * transformed, if necessary, into a filename that is syntactically
- * acceptable to DOS. The routine returns a pointer to this string.
- * The former step fixes names that come from DOS to be Unix-compatible;
- * it will never change Unix filenames, except to make uppercase letters
- * lowercase, because they are already Unix-compatible. The latter step
- * fixes names that come from Unix to be DOS-compatible; it will never
- * change DOS filenames, because they are already DOS-compatible.
- *
- * The translation of uppercase letters to lowercase ones in unix filenames
- * that appeared in a tar file is a side-effect of the dual purpose of
- * fixname; its only effect is that listings of filenames in a tar file
- * will always be lowercase. An improvement might be to separate fixname
- * into one routine to fix DOS names, and another to fix Unix names,
- * but this is left for a future enhancement.
- *
- * Even in non-DOS environments, fixname() must at least return a pointer
- * to a *copy* of the original filename, even if it is an unmodified
- * copy. This is because this name string is used by tar at times after
- * the string which was pointed to by 's' has been overwritten by other
- * data. The present code does this properly.
- *
- * FIXME: This code is embarassingly complex and needs to be rewritten.
- */
-
- char *
- fixname(s)
- char *s;
- {
- register char *p, *q;
- char *prd;
- char *t;
- char *lsl;
- char rpy[3];
- static char buf[256]; /* where the copy of the name is stored */
-
- #ifdef MSDOS
-
- /*
- * CODE TO FIX DOS NAMES: DOS's filenames are always uppercase, though
- * DOS maps lowercase characters to uppercase in filenames automatically.
- * If we create the archive with these uppercase names, the files if
- * un-tarred under Unix all have uppercase names. So we map the names to
- * lowercase under DOS so that it works best for both. The same for \ vs
- * /: DOS takes either, Unix needs /, so we use /. This first
- * transformation occurs on the actual string we were passed, rather than
- * a copy of it.
- */
- q = s;
- strlwr(q);
- for (; *q; q++)
- if (*q == '\\')
- *q = '/';
-
- /*
- * CODE TO FIX UNIX NAMES: if more than one '.' in name, DOS won't create
- * file. Delete all but last '.', then see if name longer than DOS
- * allows. If so, prompt user for new name. (It might be better to
- * always do the length check, but in this case it is more likely to be a
- * problem since names with multiple dots often have fellow files with
- * common left substrings which the part after the dot qualifies.)
- */
- t = rindex(s, '/');
- if (t == NULL)
- t = s;
-
- if (index(t, '.') != (prd = rindex(t, '.'))) /* see if name needs
- * xlation */
- {
- for (q = s, p = buf; *q; q++)
- {
- if (q >= t && *q == '.' && q < prd)
- continue; /* del all but last dot */
- *p++ = *q;
- }
- *p = '\0';
-
- /*
- * prompt user for valid name, & check file existence, only if doing
- * an "extract" operation with stdin not redirected.
- */
- if (f_extract)
- {
- lsl = rindex(buf, '/');
- if (!lsl)
- lsl = rindex(buf, '\\');
- if (!lsl)
- lsl = buf;
- if ((q = index(lsl, '.')) - lsl > 8 || &lsl[strlen(lsl)] - q > 4)
- {
- uprintf(ftty, "tar: %s: Not a valid DOS filename.\n", buf);
- getnewname:
- uprintf(ftty, "Enter new name: ");
- ugets(buf, sizeof(buf), ftty);
- *index(buf, '\n') = '\0';
- }
- if (!access(buf, 0))
- {
- askowrite:
- uprintf(ftty, "tar: Renamed file '%s' exists. Overwrite (y,n)? ",
- buf);
- ugets(rpy, sizeof(rpy), ftty);
- if (*rpy == 'n' || *rpy == 'N')
- goto getnewname;
- if (*rpy != 'y' && *rpy != 'Y')
- goto askowrite;
- }
- }
- }
- else /* the name was not modified, so return copy
- * of unchanged name */
- #endif
-
- /*
- * ALWAYS return pointer to our local copy of the name, even if it
- * isn't modified. We can't just say "return(s)" because the
- * location of the name s points to changes in memory during the
- * extract() function.
- */
- {
- strcpy(buf, s);
- }
-
- return (buf);
- }
-
- /*
- * convmode(s) - return conversion mode bits for file with name s.
- *
- * This routine assumes filenames have the file type encoded in them
- * in a standard way (e.g., MS/DOS extensions). It examines the
- * name of the file, and returns any mode bits that need to be or'ed
- * into the mode bits for the open (O_RDWR, etc. bits) for that particular
- * file to be read such that it looks like a normal Unix file.
- *
- * Obviously, your OS has to meet all the above implied constraints, but at
- * least this is a head start, I hope...
- */
-
- int
- convmode(s)
- char *s;
- {
- char **p;
-
- #ifdef MSDOS
- while (*s)
- {
- if (*s == '.')
- break;
- s++;
- }
-
- if (*s == '\0')
- s = " ."; /* special string for "no extension" */
- /* it has space in front because of */
- s++; /* <- that increment */
-
- for (p = binexts; *p; p++)
- {
- if (strcmp(s, *p) == 0)
- {
- return (O_BINARY);
- }
- }
-
- return (O_TEXT);
- #else
- /* for a Unix-like OS, always return 0 */
- return (0);
- #endif
- }
-
- #ifdef MSDOS
- /*
- * add an extension to the "binexts" list of file extensions that
- * won't get O_BINARY translation (see convmode(), above)
- */
-
- void
- addbinext(s)
- char *s;
- {
- register char **exts;
- int n;
-
- for (exts = binexts, n = 0; *exts; exts++, n++); /* find end */
-
- if (n >= NBINEXTS - 1)
- {
- annofile(stderr, tar);
- fprintf(stderr, "%s: too many extensions added (max=%d)\n",
- s, NBINEXTS - 1);
- exit(EX_ARGSBAD);
- }
-
- /* optional "." on front unless string is just "." */
- if (s[0] == '.' && s[1] != '\0')
- s++;
-
- *exts++ = s;
- *exts = 0;
- }
-
- #endif
-
- /*
- * 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 fixname(n_argv[optind++]);
- }
- return (char *) NULL;
- }
- p = fgets(buffer, NAMSIZ + 1 /* nl */ , namef);
- if (p == NULL)
- return p; /* End of file */
- q = p + strlen(p) - 1; /* Find the newline */
- *q-- = '\0'; /* Zap the newline */
- while (*q == '/')
- *q-- = '\0'; /* Zap trailing slashes too */
- return fixname(p);
- }
-
-
- /*
- * 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 namebuff[1]; /* One-name buffer */
- struct name *namebuf = namebuff;
-
- if (f_sorted_names)
- {
- p = name_next();
- if (p)
- {
- namebuf->length = strlen(p);
- if (namebuf->length >= sizeof namebuf->name)
- {
- fprintf(stderr, "Argument name too long: %s\n",
- p);
- namebuf->length = (sizeof namebuf->name) - 1;
- }
- strncpy(namebuf->name, p, namebuf->length);
- namebuf->next = (struct name *) NULL;
- namebuf->found = 0;
- namelist = namebuf;
- namelast = namelist;
- }
- return;
- }
-
- /* Non sorted names -- read them all in */
- while (NULL != (p = name_next()))
- {
- addname(p);
- }
- }
-
- /*
- * A name from the namelist has been found.
- * If it's just a list,
-
- /*
- * 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));
- p->next = (struct name *) NULL;
- p->length = i;
- p->found = 0;
- strncpy(p->name, name, i);
- p->name[i] = '\0'; /* Null term */
- if (namelast)
- namelast->next = p;
- namelast = p;
- if (!namelist)
- namelist = p;
- }
-
-
- /*
- * Match a name from an archive, p, with a name from the namelist.
- *
- * FIXME: Allow regular expressions in the name list.
- */
- 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 (nlp->name[0] == p[0]/* First chars match */
- && 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 /* unix */
- }
- 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);
- }
- }
-