home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power Programming
/
powerprogramming1994.iso
/
progtool
/
filutl
/
pdtar.arc
/
TAR.C
< prev
next >
Wrap
C/C++ Source or Header
|
1988-05-15
|
20KB
|
807 lines
/*
* 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);
}
}