diff options
Diffstat (limited to 'gnu/usr.bin/tar/tar.c')
-rw-r--r-- | gnu/usr.bin/tar/tar.c | 1504 |
1 files changed, 1504 insertions, 0 deletions
diff --git a/gnu/usr.bin/tar/tar.c b/gnu/usr.bin/tar/tar.c new file mode 100644 index 0000000..9382582 --- /dev/null +++ b/gnu/usr.bin/tar/tar.c @@ -0,0 +1,1504 @@ +/* Tar -- a tape archiver. + Copyright (C) 1988, 1992, 1993 Free Software Foundation + +This file is part of GNU Tar. + +GNU Tar is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Tar is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Tar; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* + * A tar (tape archiver) program. + * + * Written by John Gilmore, ihnp4!hoptoad!gnu, starting 25 Aug 85. + */ + +#include <stdio.h> +#include <sys/types.h> /* Needed for typedefs in tar.h */ +#include "getopt.h" + +/* + * 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" + +#include "port.h" +#include "regex.h" +#include "fnmatch.h" + +/* + * We should use a conversion routine that does reasonable error + * checking -- atoi doesn't. For now, punt. FIXME. + */ +#define intconv atoi +PTR ck_malloc (); +PTR ck_realloc (); +extern int getoldopt (); +extern void read_and (); +extern void list_archive (); +extern void extract_archive (); +extern void diff_archive (); +extern void create_archive (); +extern void update_archive (); +extern void junk_archive (); +extern void init_volume_number (); +extern void closeout_volume_number (); + +/* JF */ +extern time_t get_date (); + +time_t new_time; + +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 */ +static char **n_ind; /* Store an array of names */ +static int n_indalloc; /* How big is the array? */ +static int n_indused; /* How many entries does it have? */ +static int n_indscan; /* How many of the entries have we scanned? */ + + +extern FILE *msg_file; + +int check_exclude (); +void add_exclude (); +void add_exclude_file (); +void addname (); +void describe (); +void diff_init (); +void extr_init (); +int is_regex (); +void name_add (); +void name_init (); +void options (); +char *un_quote_string (); + +#ifndef S_ISLNK +#define lstat stat +#endif + +#ifndef DEFBLOCKING +#define DEFBLOCKING 20 +#endif + +#ifndef DEF_AR_FILE +#define DEF_AR_FILE "tar.out" +#endif + +/* For long options that unconditionally set a single flag, we have getopt + do it. For the others, we share the code for the equivalent short + named option, the name of which is stored in the otherwise-unused `val' + field of the `struct option'; for long options that have no equivalent + short option, we use nongraphic characters as pseudo short option + characters, starting (for no particular reason) with character 10. */ + +struct option long_options[] = +{ + {"create", 0, 0, 'c'}, + {"append", 0, 0, 'r'}, + {"extract", 0, 0, 'x'}, + {"get", 0, 0, 'x'}, + {"list", 0, 0, 't'}, + {"update", 0, 0, 'u'}, + {"catenate", 0, 0, 'A'}, + {"concatenate", 0, 0, 'A'}, + {"compare", 0, 0, 'd'}, + {"diff", 0, 0, 'd'}, + {"delete", 0, 0, 14}, + {"help", 0, 0, 12}, + + {"null", 0, 0, 16}, + {"directory", 1, 0, 'C'}, + {"record-number", 0, &f_sayblock, 1}, + {"files-from", 1, 0, 'T'}, + {"label", 1, 0, 'V'}, + {"exclude-from", 1, 0, 'X'}, + {"exclude", 1, 0, 15}, + {"file", 1, 0, 'f'}, + {"block-size", 1, 0, 'b'}, + {"version", 0, 0, 11}, + {"verbose", 0, 0, 'v'}, + {"totals", 0, &f_totals, 1}, + + {"read-full-blocks", 0, &f_reblock, 1}, + {"starting-file", 1, 0, 'K'}, + {"to-stdout", 0, &f_exstdout, 1}, + {"ignore-zeros", 0, &f_ignorez, 1}, + {"keep-old-files", 0, 0, 'k'}, + {"same-permissions", 0, &f_use_protection, 1}, + {"preserve-permissions", 0, &f_use_protection, 1}, + {"modification-time", 0, &f_modified, 1}, + {"preserve", 0, 0, 10}, + {"same-order", 0, &f_sorted_names, 1}, + {"same-owner", 0, &f_do_chown, 1}, + {"preserve-order", 0, &f_sorted_names, 1}, + + {"newer", 1, 0, 'N'}, + {"after-date", 1, 0, 'N'}, + {"newer-mtime", 1, 0, 13}, + {"incremental", 0, 0, 'G'}, + {"listed-incremental", 1, 0, 'g'}, + {"multi-volume", 0, &f_multivol, 1}, + {"info-script", 1, 0, 'F'}, + {"new-volume-script", 1, 0, 'F'}, + {"absolute-paths", 0, &f_absolute_paths, 1}, + {"interactive", 0, &f_confirm, 1}, + {"confirmation", 0, &f_confirm, 1}, + + {"verify", 0, &f_verify, 1}, + {"dereference", 0, &f_follow_links, 1}, + {"one-file-system", 0, &f_local_filesys, 1}, + {"old-archive", 0, 0, 'o'}, + {"portability", 0, 0, 'o'}, + {"compress", 0, 0, 'Z'}, + {"uncompress", 0, 0, 'Z'}, + {"block-compress", 0, &f_compress_block, 1}, + {"gzip", 0, 0, 'z'}, + {"ungzip", 0, 0, 'z'}, + {"use-compress-program", 1, 0, 18}, + + + {"same-permissions", 0, &f_use_protection, 1}, + {"sparse", 0, &f_sparse_files, 1}, + {"tape-length", 1, 0, 'L'}, + {"remove-files", 0, &f_remove_files, 1}, + {"ignore-failed-read", 0, &f_ignore_failed_read, 1}, + {"checkpoint", 0, &f_checkpoint, 1}, + {"show-omitted-dirs", 0, &f_show_omitted_dirs, 1}, + {"volno-file", 1, 0, 17}, + {"force-local", 0, &f_force_local, 1}, + {"atime-preserve", 0, &f_atime_preserve, 1}, + + {0, 0, 0, 0} +}; + +/* + * Main routine for tar. + */ +void +main (argc, argv) + int argc; + char **argv; +{ + extern char version_string[]; + + tar = argv[0]; /* JF: was "tar" Set program name */ + filename_terminator = '\n'; + errors = 0; + + options (argc, argv); + + if (!n_argv) + name_init (argc, argv); + + if (f_volno_file) + init_volume_number (); + + switch (cmd_mode) + { + case CMD_CAT: + case CMD_UPDATE: + case CMD_APPEND: + update_archive (); + break; + case CMD_DELETE: + junk_archive (); + break; + case CMD_CREATE: + create_archive (); + if (f_totals) + fprintf (stderr, "Total bytes written: %d\n", tot_written); + break; + case CMD_EXTRACT: + if (f_volhdr) + { + const char *err; + label_pattern = (struct re_pattern_buffer *) + ck_malloc (sizeof *label_pattern); + err = re_compile_pattern (f_volhdr, strlen (f_volhdr), + label_pattern); + if (err) + { + fprintf (stderr, "Bad regular expression: %s\n", + err); + errors++; + break; + } + + } + extr_init (); + read_and (extract_archive); + break; + case CMD_LIST: + if (f_volhdr) + { + const char *err; + label_pattern = (struct re_pattern_buffer *) + ck_malloc (sizeof *label_pattern); + err = re_compile_pattern (f_volhdr, strlen (f_volhdr), + label_pattern); + if (err) + { + fprintf (stderr, "Bad regular expression: %s\n", + err); + errors++; + break; + } + } + read_and (list_archive); +#if 0 + if (!errors) + errors = different; +#endif + break; + case CMD_DIFF: + diff_init (); + read_and (diff_archive); + break; + case CMD_VERSION: + fprintf (stderr, "%s\n", version_string); + break; + case CMD_NONE: + msg ("you must specify exactly one of the r, c, t, x, or d options\n"); + fprintf (stderr, "For more information, type ``%s --help''.\n", tar); + exit (EX_ARGSBAD); + } + if (f_volno_file) + closeout_volume_number (); + exit (errors); + /* NOTREACHED */ +} + + +/* + * Parse the options for tar. + */ +void +options (argc, argv) + int argc; + char **argv; +{ + register int c; /* Option letter */ + int ind = -1; + + /* Set default option values */ + blocking = DEFBLOCKING; /* From Makefile */ + ar_files = (char **) ck_malloc (sizeof (char *) * 10); + ar_files_len = 10; + n_ar_files = 0; + cur_ar_file = 0; + + /* Parse options */ + while ((c = getoldopt (argc, argv, + "-01234567Ab:BcC:df:F:g:GhikK:lL:mMN:oOpPrRsStT:uvV:wWxX:zZ", + long_options, &ind)) != EOF) + { + switch (c) + { + case 0: /* long options that set a single flag */ + break; + case 1: + /* File name or non-parsed option */ + name_add (optarg); + break; + case 'C': + name_add ("-C"); + name_add (optarg); + break; + case 10: /* preserve */ + f_use_protection = f_sorted_names = 1; + break; + case 11: + if (cmd_mode != CMD_NONE) + goto badopt; + cmd_mode = CMD_VERSION; + break; + case 12: /* help */ + printf ("This is GNU tar, the tape archiving program.\n"); + describe (); + exit (1); + case 13: + f_new_files++; + goto get_newer; + + case 14: /* Delete in the archive */ + if (cmd_mode != CMD_NONE) + goto badopt; + cmd_mode = CMD_DELETE; + break; + + case 15: + f_exclude++; + add_exclude (optarg); + break; + + case 16: /* -T reads null terminated filenames. */ + filename_terminator = '\0'; + break; + + case 17: + f_volno_file = optarg; + break; + + case 18: + if (f_compressprog) + { + msg ("Only one compression option permitted\n"); + exit (EX_ARGSBAD); + } + f_compressprog = optarg; + break; + + case 'g': /* We are making a GNU dump; save + directories at the beginning of + the archive, and include in each + directory its contents */ + if (f_oldarch) + goto badopt; + f_gnudump++; + gnu_dumpfile = optarg; + break; + + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + { + /* JF this'll have to be modified for other + systems, of course! */ + int d, add; + static char buf[50]; + + d = getoldopt (argc, argv, "lmh"); +#ifdef MAYBEDEF + sprintf (buf, "/dev/rmt/%d%c", c, d); +#else +#ifndef LOW_NUM +#define LOW_NUM 0 +#define MID_NUM 8 +#define HGH_NUM 16 +#endif + if (d == 'l') + add = LOW_NUM; + else if (d == 'm') + add = MID_NUM; + else if (d == 'h') + add = HGH_NUM; + else + goto badopt; + + sprintf (buf, "/dev/rmt%d", add + c - '0'); +#endif + if (n_ar_files == ar_files_len) + ar_files + = (char **) + ck_malloc (sizeof (char *) + * (ar_files_len *= 2)); + ar_files[n_ar_files++] = buf; + } + break; + + case 'A': /* Arguments are tar files, + just cat them onto the end + of the archive. */ + if (cmd_mode != CMD_NONE) + goto badopt; + cmd_mode = CMD_CAT; + break; + + case 'b': /* Set blocking factor */ + blocking = intconv (optarg); + break; + + case 'B': /* Try to reblock input */ + f_reblock++; /* For reading 4.2BSD pipes */ + break; + + case 'c': /* Create an archive */ + if (cmd_mode != CMD_NONE) + goto badopt; + cmd_mode = CMD_CREATE; + break; + +#if 0 + case 'C': + if (chdir (optarg) < 0) + msg_perror ("Can't change directory to %d", optarg); + break; +#endif + + case 'd': /* Find difference tape/disk */ + if (cmd_mode != CMD_NONE) + goto badopt; + cmd_mode = CMD_DIFF; + break; + + case 'f': /* Use ar_file for the archive */ + if (n_ar_files == ar_files_len) + ar_files + = (char **) ck_malloc (sizeof (char *) + * (ar_files_len *= 2)); + + ar_files[n_ar_files++] = optarg; + break; + + case 'F': + /* Since -F is only useful with -M , make it implied */ + f_run_script_at_end++;/* run this script at the end */ + info_script = optarg; /* of each tape */ + f_multivol++; + break; + + case 'G': /* We are making a GNU dump; save + directories at the beginning of + the archive, and include in each + directory its contents */ + if (f_oldarch) + goto badopt; + f_gnudump++; + gnu_dumpfile = 0; + 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 + msg ("can't keep old files on this system"); + exit (EX_ARGSBAD); +#else + f_keep++; +#endif + break; + + case 'K': + f_startfile++; + addname (optarg); + break; + + case 'l': /* When dumping directories, don't + dump files/subdirectories that are + on other filesystems. */ + f_local_filesys++; + break; + + case 'L': + tape_length = intconv (optarg); + f_multivol++; + break; + case 'm': + f_modified++; + break; + + case 'M': /* Make Multivolume archive: + When we can't write any more + into the archive, re-open it, + and continue writing */ + f_multivol++; + break; + + case 'N': /* Only write files newer than X */ + get_newer: + f_new_files++; + new_time = get_date (optarg, (PTR) 0); + if (new_time == (time_t) - 1) + { + msg ("invalid date format `%s'", optarg); + exit (EX_ARGSBAD); + } + break; + + case 'o': /* Generate old archive */ + if (f_gnudump /* || f_dironly */ ) + goto badopt; + f_oldarch++; + break; + + case 'O': + f_exstdout++; + break; + + case 'p': + f_use_protection++; + break; + + case 'P': + f_absolute_paths++; + break; + + case 'r': /* Append files to the archive */ + if (cmd_mode != CMD_NONE) + goto badopt; + cmd_mode = CMD_APPEND; + 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 'S': /* deal with sparse files */ + f_sparse_files++; + break; + case 't': + if (cmd_mode != CMD_NONE) + goto badopt; + cmd_mode = CMD_LIST; + f_verbose++; /* "t" output == "cv" or "xv" */ + break; + + case 'T': + name_file = optarg; + f_namefile++; + break; + + case 'u': /* Append files to the archive that + aren't there, or are newer than the + copy in the archive */ + if (cmd_mode != CMD_NONE) + goto badopt; + cmd_mode = CMD_UPDATE; + break; + + case 'v': + f_verbose++; + break; + + case 'V': + f_volhdr = optarg; + break; + + case 'w': + f_confirm++; + break; + + case 'W': + f_verify++; + break; + + case 'x': /* Extract files from the archive */ + if (cmd_mode != CMD_NONE) + goto badopt; + cmd_mode = CMD_EXTRACT; + break; + + case 'X': + f_exclude++; + add_exclude_file (optarg); + break; + + case 'z': + if (f_compressprog) + { + msg ("Only one compression option permitted\n"); + exit (EX_ARGSBAD); + } + f_compressprog = "gzip"; + break; + + case 'Z': + if (f_compressprog) + { + msg ("Only one compression option permitted\n"); + exit (EX_ARGSBAD); + } + f_compressprog = "compress"; + break; + + case '?': + badopt: + msg ("Unknown option. Use '%s --help' for a complete list of options.", tar); + exit (EX_ARGSBAD); + + } + } + + blocksize = blocking * RECORDSIZE; + if (n_ar_files == 0) + { + n_ar_files = 1; + ar_files[0] = getenv ("TAPE"); /* From environment, or */ + if (ar_files[0] == 0) + ar_files[0] = DEF_AR_FILE; /* From Makefile */ + } + if (n_ar_files > 1 && !f_multivol) + { + msg ("Multiple archive files requires --multi-volume\n"); + exit (EX_ARGSBAD); + } + if (f_compress_block && !f_compressprog) + { + msg ("You must use a compression option (--gzip, --compress\n\ +or --use-compress-program) with --block-compress.\n"); + exit (EX_ARGSBAD); + } +} + + +/* + * 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, MS-DOS and Xenix MSC and PDP-11 V7 Unix have this + * problem. + */ +void +describe () +{ + puts ("choose one of the following:"); + fputs ("\ +-A, --catenate,\n\ + --concatenate append tar files to an archive\n\ +-c, --create create a new archive\n\ +-d, --diff,\n\ + --compare find differences between archive and file system\n\ +--delete delete from the archive (not for use on mag tapes!)\n\ +-r, --append append files to the end of an archive\n\ +-t, --list list the contents of an archive\n\ +-u, --update only append files that are newer than copy in archive\n\ +-x, --extract,\n\ + --get extract files from an archive\n", stdout); + + fprintf (stdout, "\ +Other options:\n\ +--atime-preserve don't change access times on dumped files\n\ +-b, --block-size N block size of Nx512 bytes (default N=%d)\n", DEFBLOCKING); + fputs ("\ +-B, --read-full-blocks reblock as we read (for reading 4.2BSD pipes)\n\ +-C, --directory DIR change to directory DIR\n\ +--checkpoint print directory names while reading the archive\n\ +", stdout); /* KLUDGE */ + fprintf (stdout, "\ +-f, --file [HOSTNAME:]F use archive file or device F (default %s)\n", + DEF_AR_FILE); + fputs ("\ +--force-local archive file is local even if has a colon\n\ +-F, --info-script F\n\ + --new-volume-script F run script at end of each tape (implies -M)\n\ +-G, --incremental create/list/extract old GNU-format incremental backup\n\ +-g, --listed-incremental F create/list/extract new GNU-format incremental backup\n\ +-h, --dereference don't dump symlinks; dump the files they point to\n\ +-i, --ignore-zeros ignore blocks of zeros in archive (normally mean EOF)\n\ +--ignore-failed-read don't exit with non-zero status on unreadable files\n\ +-k, --keep-old-files keep existing files; don't overwrite them from archive\n\ +-K, --starting-file F begin at file F in the archive\n\ +-l, --one-file-system stay in local file system when creating an archive\n\ +-L, --tape-length N change tapes after writing N*1024 bytes\n\ +", stdout); /* KLUDGE */ + fputs ("\ +-m, --modification-time don't extract file modified time\n\ +-M, --multi-volume create/list/extract multi-volume archive\n\ +-N, --after-date DATE,\n\ + --newer DATE only store files newer than DATE\n\ +-o, --old-archive,\n\ + --portability write a V7 format archive, rather than ANSI format\n\ +-O, --to-stdout extract files to standard output\n\ +-p, --same-permissions,\n\ + --preserve-permissions extract all protection information\n\ +-P, --absolute-paths don't strip leading `/'s from file names\n\ +--preserve like -p -s\n\ +", stdout); /* KLUDGE */ + fputs ("\ +-R, --record-number show record number within archive with each message\n\ +--remove-files remove files after adding them to the archive\n\ +-s, --same-order,\n\ + --preserve-order list of names to extract is sorted to match archive\n\ +--same-owner create extracted files with the same ownership \n\ +-S, --sparse handle sparse files efficiently\n\ +-T, --files-from F get names to extract or create from file F\n\ +--null -T reads null-terminated names, disable -C\n\ +--totals print total bytes written with --create\n\ +-v, --verbose verbosely list files processed\n\ +-V, --label NAME create archive with volume name NAME\n\ +--version print tar program version number\n\ +-w, --interactive,\n\ + --confirmation ask for confirmation for every action\n\ +", stdout); /* KLUDGE */ + fputs ("\ +-W, --verify attempt to verify the archive after writing it\n\ +--exclude FILE exclude file FILE\n\ +-X, --exclude-from FILE exclude files listed in FILE\n\ +-Z, --compress,\n\ + --uncompress filter the archive through compress\n\ +-z, --gzip,\n\ + --ungzip filter the archive through gzip\n\ +--use-compress-program PROG\n\ + filter the archive through PROG (which must accept -d)\n\ +--block-compress block the output of compression program for tapes\n\ +-[0-7][lmh] specify drive and density\n\ +", stdout); +} + +void +name_add (name) + char *name; +{ + if (n_indalloc == n_indused) + { + n_indalloc += 10; + n_ind = (char **) (n_indused ? ck_realloc (n_ind, n_indalloc * sizeof (char *)): ck_malloc (n_indalloc * sizeof (char *))); + } + n_ind[n_indused++] = name; +} + +/* + * Set up to gather file names for tar. + * + * They can either come from stdin or from argv. + */ +void +name_init (argc, argv) + int argc; + char **argv; +{ + + if (f_namefile) + { + if (optind < argc) + { + msg ("too many args with -T option"); + exit (EX_ARGSBAD); + } + if (!strcmp (name_file, "-")) + { + namef = stdin; + } + else + { + namef = fopen (name_file, "r"); + if (namef == NULL) + { + msg_perror ("can't open file %s", name_file); + exit (EX_BADFILE); + } + } + } + else + { + /* Get file names from argv, after options. */ + n_argc = argc; + n_argv = argv; + } +} + +/* Read the next filename read from STREAM and null-terminate it. + Put it into BUFFER, reallocating and adjusting *PBUFFER_SIZE if necessary. + Return the new value for BUFFER, or NULL at end of file. */ + +char * +read_name_from_file (buffer, pbuffer_size, stream) + char *buffer; + size_t *pbuffer_size; + FILE *stream; +{ + register int c; + register int indx = 0; + register size_t buffer_size = *pbuffer_size; + + while ((c = getc (stream)) != EOF && c != filename_terminator) + { + if (indx == buffer_size) + { + buffer_size += NAMSIZ; + buffer = ck_realloc (buffer, buffer_size + 2); + } + buffer[indx++] = c; + } + if (indx == 0 && c == EOF) + return NULL; + if (indx == buffer_size) + { + buffer_size += NAMSIZ; + buffer = ck_realloc (buffer, buffer_size + 2); + } + buffer[indx] = '\0'; + *pbuffer_size = buffer_size; + return buffer; +} + +/* + * Get the next name from argv or the name file. + * + * Result is in static storage and can't be relied upon across two calls. + * + * If CHANGE_DIRS is non-zero, treat a filename of the form "-C" as + * meaning that the next filename is the name of a directory to change to. + * If `filename_terminator' is '\0', CHANGE_DIRS is effectively always 0. + */ + +char * +name_next (change_dirs) + int change_dirs; +{ + static char *buffer; /* Holding pattern */ + static int buffer_siz; + register char *p; + register char *q = 0; + register int next_name_is_dir = 0; + extern char *un_quote_string (); + + if (buffer_siz == 0) + { + buffer = ck_malloc (NAMSIZ + 2); + buffer_siz = NAMSIZ; + } + if (filename_terminator == '\0') + change_dirs = 0; +tryagain: + if (namef == NULL) + { + if (n_indscan < n_indused) + p = n_ind[n_indscan++]; + else if (optind < n_argc) + /* Names come from argv, after options */ + p = n_argv[optind++]; + else + { + if (q) + msg ("Missing filename after -C"); + return NULL; + } + + /* JF trivial support for -C option. I don't know if + chdir'ing at this point is dangerous or not. + It seems to work, which is all I ask. */ + if (change_dirs && !q && p[0] == '-' && p[1] == 'C' && p[2] == '\0') + { + q = p; + goto tryagain; + } + if (q) + { + if (chdir (p) < 0) + msg_perror ("Can't chdir to %s", p); + q = 0; + goto tryagain; + } + /* End of JF quick -C hack */ + +#if 0 + if (f_exclude && check_exclude (p)) + goto tryagain; +#endif + return un_quote_string (p); + } + while (p = read_name_from_file (buffer, &buffer_siz, namef)) + { + buffer = p; + if (*p == '\0') + continue; /* Ignore empty lines. */ + q = p + strlen (p) - 1; + while (q > p && *q == '/')/* Zap trailing "/"s. */ + *q-- = '\0'; + if (change_dirs && next_name_is_dir == 0 + && p[0] == '-' && p[1] == 'C' && p[2] == '\0') + { + next_name_is_dir = 1; + goto tryagain; + } + if (next_name_is_dir) + { + if (chdir (p) < 0) + msg_perror ("Can't change to directory %s", p); + next_name_is_dir = 0; + goto tryagain; + } +#if 0 + if (f_exclude && check_exclude (p)) + goto tryagain; +#endif + return un_quote_string (p); + } + return NULL; +} + + +/* + * Close the name file, if any. + */ +void +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. + */ +void +name_gather () +{ + register char *p; + static struct name *namebuf; /* One-name buffer */ + static namelen; + static char *chdir_name; + + if (f_sorted_names) + { + if (!namelen) + { + namelen = NAMSIZ; + namebuf = (struct name *) ck_malloc (sizeof (struct name) + NAMSIZ); + } + p = name_next (0); + if (p) + { + if (*p == '-' && p[1] == 'C' && p[2] == '\0') + { + chdir_name = name_next (0); + p = name_next (0); + if (!p) + { + msg ("Missing file name after -C"); + exit (EX_ARGSBAD); + } + namebuf->change_dir = chdir_name; + } + namebuf->length = strlen (p); + if (namebuf->length >= namelen) + { + namebuf = (struct name *) ck_realloc (namebuf, sizeof (struct name) + namebuf->length); + namelen = namebuf->length; + } + strncpy (namebuf->name, p, namebuf->length); + namebuf->name[namebuf->length] = 0; + namebuf->next = (struct name *) NULL; + namebuf->found = 0; + namelist = namebuf; + namelast = namelist; + } + return; + } + + /* Non sorted names -- read them all in */ + while (p = name_next (0)) + addname (p); +} + +/* + * Add a name to the namelist. + */ +void +addname (name) + char *name; /* pointer to name */ +{ + register int i; /* Length of string */ + register struct name *p; /* Current struct pointer */ + static char *chdir_name; + char *new_name (); + + if (name[0] == '-' && name[1] == 'C' && name[2] == '\0') + { + chdir_name = name_next (0); + name = name_next (0); + if (!chdir_name) + { + msg ("Missing file name after -C"); + exit (EX_ARGSBAD); + } + if (chdir_name[0] != '/') + { + char *path = ck_malloc (PATH_MAX); +#if defined(__MSDOS__) || defined(HAVE_GETCWD) || defined(_POSIX_VERSION) + if (!getcwd (path, PATH_MAX)) + { + msg ("Couldn't get current directory."); + exit (EX_SYSTEM); + } +#else + char *getwd (); + + if (!getwd (path)) + { + msg ("Couldn't get current directory: %s", path); + exit (EX_SYSTEM); + } +#endif + chdir_name = new_name (path, chdir_name); + free (path); + } + } + + if (name) + { + i = strlen (name); + /*NOSTRICT*/ + p = (struct name *) malloc ((unsigned) (sizeof (struct name) + i)); + } + else + p = (struct name *) malloc ((unsigned) (sizeof (struct name))); + if (!p) + { + if (name) + msg ("cannot allocate mem for name '%s'.", name); + else + msg ("cannot allocate mem for chdir record."); + exit (EX_SYSTEM); + } + p->next = (struct name *) NULL; + if (name) + { + p->fake = 0; + p->length = i; + strncpy (p->name, name, i); + p->name[i] = '\0'; /* Null term */ + } + else + p->fake = 1; + p->found = 0; + p->regexp = 0; /* Assume not a regular expression */ + p->firstch = 1; /* Assume first char is literal */ + p->change_dir = chdir_name; + p->dir_contents = 0; /* JF */ + if (name) + { + 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; +} + +/* + * Return nonzero if name P (from an archive) matches any name from + * the namelist, zero if not. + */ +int +name_match (p) + register char *p; +{ + register struct name *nlp; + register int len; + +again: + if (0 == (nlp = namelist)) /* Empty namelist is easy */ + return 1; + if (nlp->fake) + { + if (nlp->change_dir && chdir (nlp->change_dir)) + msg_perror ("Can't change to directory %d", nlp->change_dir); + namelist = 0; + 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 (shell globbing, actually). */ + if (nlp->regexp) + { + if (fnmatch (nlp->name, p, FNM_LEADING_DIR) == 0) + { + nlp->found = 1; /* Remember it matched */ + if (f_startfile) + { + free ((void *) namelist); + namelist = 0; + } + if (nlp->change_dir && chdir (nlp->change_dir)) + msg_perror ("Can't change to directory %s", nlp->change_dir); + 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 */ + if (f_startfile) + { + free ((void *) namelist); + namelist = 0; + } + if (nlp->change_dir && chdir (nlp->change_dir)) + msg_perror ("Can't change to directory %s", nlp->change_dir); + 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. + */ +void +names_notfound () +{ + register struct name *nlp, *next; + register char *p; + + for (nlp = namelist; nlp != 0; nlp = next) + { + next = nlp->next; + if (!nlp->found) + msg ("%s not found in archive", 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. + */ +#ifdef amiga + if (!f_sorted_names) + free (nlp); +#endif + } + namelist = (struct name *) NULL; + namelast = (struct name *) NULL; + + if (f_sorted_names) + { + while (0 != (p = name_next (1))) + msg ("%s not found in archive", p); + } +} + +/* These next routines were created by JF */ + +void +name_expand () +{ + ; +} + +/* This is like name_match(), except that it returns a pointer to the name + it matched, and doesn't set ->found The caller will have to do that + if it wants to. Oh, and if the namelist is empty, it returns 0, unlike + name_match(), which returns TRUE */ + +struct name * +name_scan (p) + register char *p; +{ + register struct name *nlp; + register int len; + +again: + if (0 == (nlp = namelist)) /* Empty namelist is easy */ + return 0; + 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 (fnmatch (nlp->name, p, FNM_LEADING_DIR) == 0) + return nlp; /* 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 */ + return nlp; /* 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 (struct name *) 0; +} + +/* This returns a name from the namelist which doesn't have ->found set. + It sets ->found before returning, so successive calls will find and return + all the non-found names in the namelist */ + +struct name *gnu_list_name; + +char * +name_from_list () +{ + if (!gnu_list_name) + gnu_list_name = namelist; + while (gnu_list_name && gnu_list_name->found) + gnu_list_name = gnu_list_name->next; + if (gnu_list_name) + { + gnu_list_name->found++; + if (gnu_list_name->change_dir) + if (chdir (gnu_list_name->change_dir) < 0) + msg_perror ("can't chdir to %s", gnu_list_name->change_dir); + return gnu_list_name->name; + } + return (char *) 0; +} + +void +blank_name_list () +{ + struct name *n; + + gnu_list_name = 0; + for (n = namelist; n; n = n->next) + n->found = 0; +} + +char * +new_name (path, name) + char *path, *name; +{ + char *path_buf; + + path_buf = (char *) malloc (strlen (path) + strlen (name) + 2); + if (path_buf == 0) + { + msg ("Can't allocate memory for name '%s/%s", path, name); + exit (EX_SYSTEM); + } + (void) sprintf (path_buf, "%s/%s", path, name); + return path_buf; +} + +/* returns non-zero if the luser typed 'y' or 'Y', zero otherwise. */ + +int +confirm (action, file) + char *action, *file; +{ + int c, nl; + static FILE *confirm_file = 0; + extern FILE *msg_file; + extern char TTY_NAME[]; + + fprintf (msg_file, "%s %s?", action, file); + fflush (msg_file); + if (!confirm_file) + { + confirm_file = (archive == 0) ? fopen (TTY_NAME, "r") : stdin; + if (!confirm_file) + { + msg ("Can't read confirmation from user"); + exit (EX_SYSTEM); + } + } + c = getc (confirm_file); + for (nl = c; nl != '\n' && nl != EOF; nl = getc (confirm_file)) + ; + return (c == 'y' || c == 'Y'); +} + +char *x_buffer = 0; +int size_x_buffer; +int free_x_buffer; + +char **exclude = 0; +int size_exclude = 0; +int free_exclude = 0; + +char **re_exclude = 0; +int size_re_exclude = 0; +int free_re_exclude = 0; + +void +add_exclude (name) + char *name; +{ + /* char *rname;*/ + /* char **tmp_ptr;*/ + int size_buf; + + un_quote_string (name); + size_buf = strlen (name); + + if (x_buffer == 0) + { + x_buffer = (char *) ck_malloc (size_buf + 1024); + free_x_buffer = 1024; + } + else if (free_x_buffer <= size_buf) + { + char *old_x_buffer; + char **tmp_ptr; + + old_x_buffer = x_buffer; + x_buffer = (char *) ck_realloc (x_buffer, size_x_buffer + 1024); + free_x_buffer = 1024; + for (tmp_ptr = exclude; tmp_ptr < exclude + size_exclude; tmp_ptr++) + *tmp_ptr = x_buffer + ((*tmp_ptr) - old_x_buffer); + for (tmp_ptr = re_exclude; tmp_ptr < re_exclude + size_re_exclude; tmp_ptr++) + *tmp_ptr = x_buffer + ((*tmp_ptr) - old_x_buffer); + } + + if (is_regex (name)) + { + if (free_re_exclude == 0) + { + re_exclude = (char **) (re_exclude ? ck_realloc (re_exclude, (size_re_exclude + 32) * sizeof (char *)): ck_malloc (sizeof (char *) * 32)); + free_re_exclude += 32; + } + re_exclude[size_re_exclude] = x_buffer + size_x_buffer; + size_re_exclude++; + free_re_exclude--; + } + else + { + if (free_exclude == 0) + { + exclude = (char **) (exclude ? ck_realloc (exclude, (size_exclude + 32) * sizeof (char *)): ck_malloc (sizeof (char *) * 32)); + free_exclude += 32; + } + exclude[size_exclude] = x_buffer + size_x_buffer; + size_exclude++; + free_exclude--; + } + strcpy (x_buffer + size_x_buffer, name); + size_x_buffer += size_buf + 1; + free_x_buffer -= size_buf + 1; +} + +void +add_exclude_file (file) + char *file; +{ + FILE *fp; + char buf[1024]; + + if (strcmp (file, "-")) + fp = fopen (file, "r"); + else + /* Let's hope the person knows what they're doing. */ + /* Using -X - -T - -f - will get you *REALLY* strange + results. . . */ + fp = stdin; + + if (!fp) + { + msg_perror ("can't open %s", file); + exit (2); + } + while (fgets (buf, 1024, fp)) + { + /* int size_buf;*/ + char *end_str; + + end_str = rindex (buf, '\n'); + if (end_str) + *end_str = '\0'; + add_exclude (buf); + + } + fclose (fp); +} + +int +is_regex (str) + char *str; +{ + return index (str, '*') || index (str, '[') || index (str, '?'); +} + +/* Returns non-zero if the file 'name' should not be added/extracted */ +int +check_exclude (name) + char *name; +{ + int n; + char *str; + extern char *strstr (); + + for (n = 0; n < size_re_exclude; n++) + { + if (fnmatch (re_exclude[n], name, FNM_LEADING_DIR) == 0) + return 1; + } + for (n = 0; n < size_exclude; n++) + { + /* Accept the output from strstr only if it is the last + part of the string. There is certainly a faster way to + do this. . . */ + if ((str = strstr (name, exclude[n])) + && (str == name || str[-1] == '/') + && str[strlen (exclude[n])] == '\0') + return 1; + } + return 0; +} |