freebsd-dev/gnu/usr.bin/tar/tar.c
1995-05-30 05:05:38 +00:00

1543 lines
36 KiB
C

/* 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 "gnuregex.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 ();
int nlpsfreed = 0;
#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},
{"unlink", 0, &f_unlink, 1},
{"fast-read", 0, &f_fast_read, 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\
--unlink unlink files before creating them\n\
--fast-read stop after desired names in archive have been found\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')
{
p = name_next (0);
chdir_name = p ? strdup(p) : p;
p = name_next (0);
if (!chdir_name)
{
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')
{
name = name_next (0);
chdir_name = name ? strdup(name) : name;
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;
struct name *tmpnlp;
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);
if (f_fast_read) {
if (strcmp(p, nlp->name) == 0) {
/* remove the current entry, since we found a match */
/* use brute force, this code is a mess anyway */
if (namelist->next == NULL) {
/* the list contains one element */
free(namelist);
namelist = NULL;
} else {
if (nlp == namelist) {
/* the first element is the one */
tmpnlp = namelist->next;
free(namelist);
namelist = tmpnlp;
} else {
tmpnlp = namelist;
while (tmpnlp->next != nlp) {
tmpnlp = tmpnlp->next;
}
tmpnlp->next = nlp->next;
free(nlp);
}
}
/* set a boolean to decide wether we started with a */
/* non-empty namelist, that was emptied */
nlpsfreed = 1;
}
}
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;
}