rsalz@uunet.UU.NET (Rich Salz) (11/30/87)
Submitted-by: John Gilmore <hoptoad!gnu@UUNET.UU.NET> Posting-number: Volume 12, Issue 69 Archive-name: pdtar/part02 : To unbundle, sh this file echo tar.c cat >tar.c <<'@@@ Fin de tar.c' /* * A public domain tar(1) program. * * Written by John Gilmore, ihnp4!hoptoad!gnu, starting 25 Aug 85. * * @(#)tar.c 1.34 11/6/87 Public Domain - gnu */ #include <stdio.h> #include <sys/types.h> /* Needed for typedefs in tar.h */ extern char *malloc(); extern char *getenv(); extern char *strncpy(); extern char *optarg; /* Pointer to argument */ extern int optind; /* Global argv index from getopt */ /* * The following causes "tar.h" to produce definitions of all the * global variables, rather than just "extern" declarations of them. */ #define TAR_EXTERN /**/ #include "tar.h" /* * We should use a conversion routine that does reasonable error * checking -- atoi doesn't. For now, punt. FIXME. */ #define intconv atoi extern int getoldopt(); extern void read_and(); extern void list_archive(); extern void extract_archive(); extern void diff_archive(); extern void create_archive(); static FILE *namef; /* File to read names from */ static char **n_argv; /* Argv used by name routines */ static int n_argc; /* Argc used by name routines */ /* They also use "optind" from getopt(). */ void describe(); /* * Main routine for tar. */ main(argc, argv) int argc; char **argv; { /* Uncomment this message in particularly buggy versions... fprintf(stderr, "tar: You are running an experimental PD tar, maybe use /bin/tar.\n"); */ tar = "tar"; /* Set program name */ options(argc, argv); name_init(argc, argv); if (f_create) { if (f_extract || f_list || f_diff) goto dupflags; create_archive(); } else if (f_extract) { if (f_list || f_diff) goto dupflags; extr_init(); read_and(extract_archive); } else if (f_list) { if (f_diff) goto dupflags; read_and(list_archive); } else if (f_diff) { diff_init(); read_and(diff_archive); } else { dupflags: fprintf (stderr, "tar: you must specify exactly one of the c, t, x, or d options\n"); describe(); exit(EX_ARGSBAD); } exit(0); /* NOTREACHED */ } /* * Parse the options for tar. */ int options(argc, argv) int argc; char **argv; { register int c; /* Option letter */ /* Set default option values */ blocking = DEFBLOCKING; /* From Makefile */ ar_file = getenv("TAPE"); /* From environment, or */ if (ar_file == 0) ar_file = DEF_AR_FILE; /* From Makefile */ /* Parse options */ while ((c = getoldopt(argc, argv, "b:BcdDf:hiklmopRstT:vxzZ") ) != 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_diff++; /* Find difference tape/disk */ break; case 'D': f_dironly++; /* Dump dir, not contents */ break; case 'f': ar_file = optarg; break; case 'h': f_follow_links++; /* follow symbolic links */ break; case 'i': f_ignorez++; /* Ignore zero records (eofs) */ /* * This can't be the default, because Unix tar * writes two records of zeros, then pads out the * block with garbage. */ break; case 'k': /* Don't overwrite files */ #ifdef NO_OPEN3 fprintf(stderr, "tar: can't do -k option on this system\n"); exit(EX_ARGSBAD); #else f_keep++; #endif break; case 'l': f_local_filesys++; break; case 'm': f_modified++; break; case 'o': /* Generate old archive */ f_oldarch++; break; case 'p': f_use_protection++; break; case 'R': f_sayblock++; /* Print block #s for debug */ break; /* of bad tar archives */ case 's': f_sorted_names++; /* Names to extr are sorted */ break; case 't': f_list++; f_verbose++; /* "t" output == "cv" or "xv" */ break; case 'T': name_file = optarg; f_namefile++; break; case 'v': f_verbose++; break; case 'x': f_extract++; break; case 'z': /* Easy to type */ case 'Z': /* Like the filename extension .Z */ f_compress++; break; case '?': describe(); exit(EX_ARGSBAD); } } blocksize = blocking * RECORDSIZE; } /* * Print as much help as the user's gonna get. * * We have to sprinkle in the KLUDGE lines because too many compilers * cannot handle character strings longer than about 512 bytes. Yuk! * In particular, MSDOS MSC 4.0 (and 5.0) and PDP-11 V7 Unix have this * problem. */ void describe() { 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 find differences between archive and file system\n\ -D don't dump the contents of directories, just the directory\n\ ", stderr); /* KLUDGE */ fputs("\ -f F read/write archive from file or device F (or hostname:/ForD)\n\ -h don't dump symbolic links; dump the files they point to\n\ -i ignore blocks of zeros in the archive, which normally mean EOF\n\ -k keep existing files, don't overwrite them from the archive\n\ -l stay in the local file system (like dump(8)) when creating an archive\n\ ", stderr); /* KLUDGE */ fputs("\ -m don't extract file modified time\n\ -o write an old V7 format archive, rather than ANSI [draft 6] format\n\ -p do extract all protection information\n\ -R dump record number within archive with each message\n\ -s list of names to extract is sorted to match the archive\n\ -t list a table of contents of an archive\n\ ", stderr); /* KLUDGE */ fputs("\ -T F get names to extract or create from file F\n\ -v verbosely list what files we process\n\ -x extract files from an archive\n\ -z or Z run the archive through compress(1)\n\ ", stderr); } /* * Set up to gather file names for tar. * * They can either come from stdin or from argv. */ name_init(argc, argv) int argc; char **argv; { if (f_namefile) { if (optind < argc) { fprintf(stderr, "tar: too many args with -T option\n"); exit(EX_ARGSBAD); } if (!strcmp(name_file, "-")) { namef = stdin; } else { namef = fopen(name_file, "r"); if (namef == NULL) { fprintf(stderr, "tar: "); perror(name_file); exit(EX_BADFILE); } } } else { /* Get file names from argv, after options. */ n_argc = argc; n_argv = argv; } } /* * Get the next name from argv or the name file. * * Result is in static storage and can't be relied upon across two calls. */ char * name_next() { static char buffer[NAMSIZ+2]; /* Holding pattern */ register char *p; register char *q; if (namef == NULL) { /* Names come from argv, after options */ if (optind < n_argc) return n_argv[optind++]; return (char *)NULL; } for (;;) { p = fgets(buffer, NAMSIZ+1 /*nl*/, namef); if (p == NULL) return p; /* End of file */ q = p+strlen(p)-1; /* Find the newline */ if (q <= p) continue; /* Ignore empty lines */ *q-- = '\0'; /* Zap the newline */ while (q > p && *q == '/') *q-- = '\0'; /* Zap trailing /s */ return p; } /* NOTREACHED */ } /* * Close the name file, if any. */ name_close() { if (namef != NULL && namef != stdin) fclose(namef); } /* * Gather names in a list for scanning. * Could hash them later if we really care. * * If the names are already sorted to match the archive, we just * read them one by one. name_gather reads the first one, and it * is called by name_match as appropriate to read the next ones. * At EOF, the last name read is just left in the buffer. * This option lets users of small machines extract an arbitrary * number of files by doing "tar t" and editing down the list of files. */ name_gather() { register char *p; static struct name namebuf[1]; /* One-name buffer */ if (f_sorted_names) { p = name_next(); if (p) { namebuf[0].length = strlen(p); if (namebuf[0].length >= sizeof namebuf[0].name) { fprintf(stderr, "Argument name too long: %s\n", p); namebuf[0].length = (sizeof namebuf[0].name) - 1; } strncpy(namebuf[0].name, p, namebuf[0].length); namebuf[0].name[ namebuf[0].length ] = 0; namebuf[0].next = (struct name *)NULL; namebuf[0].found = 0; namelist = namebuf; namelast = namelist; } return; } /* Non sorted names -- read them all in */ while (NULL != (p = name_next())) { addname(p); } } /* * Add a name to the namelist. */ addname(name) char *name; /* pointer to name */ { register int i; /* Length of string */ register struct name *p; /* Current struct pointer */ i = strlen(name); /*NOSTRICT*/ p = (struct name *) malloc((unsigned)(i + sizeof(struct name) - NAMSIZ)); if (!p) { fprintf(stderr,"tar: cannot allocate mem for namelist entry\n"); exit(EX_SYSTEM); } p->next = (struct name *)NULL; p->length = i; strncpy(p->name, name, i); p->name[i] = '\0'; /* Null term */ p->found = 0; p->regexp = 0; /* Assume not a regular expression */ p->firstch = 1; /* Assume first char is literal */ if (index(name, '*') || index(name, '[') || index(name, '?')) { p->regexp = 1; /* No, it's a regexp */ if (name[0] == '*' || name[0] == '[' || name[0] == '?') p->firstch = 0; /* Not even 1st char literal */ } if (namelast) namelast->next = p; namelast = p; if (!namelist) namelist = p; } /* * Match a name from an archive, p, with a name from the namelist. */ name_match(p) register char *p; { register struct name *nlp; register int len; again: if (0 == (nlp = namelist)) /* Empty namelist is easy */ return 1; len = strlen(p); for (; nlp != 0; nlp = nlp->next) { /* If first chars don't match, quick skip */ if (nlp->firstch && nlp->name[0] != p[0]) continue; /* Regular expressions */ if (nlp->regexp) { if (wildmat(p, nlp->name)) { nlp->found = 1; /* Remember it matched */ return 1; /* We got a match */ } continue; } /* Plain Old Strings */ if (nlp->length <= len /* Archive len >= specified */ && (p[nlp->length] == '\0' || p[nlp->length] == '/') /* Full match on file/dirname */ && strncmp(p, nlp->name, nlp->length) == 0) /* Name compare */ { nlp->found = 1; /* Remember it matched */ return 1; /* We got a match */ } } /* * Filename from archive not found in namelist. * If we have the whole namelist here, just return 0. * Otherwise, read the next name in and compare it. * If this was the last name, namelist->found will remain on. * If not, we loop to compare the newly read name. */ if (f_sorted_names && namelist->found) { name_gather(); /* Read one more */ if (!namelist->found) goto again; } return 0; } /* * Print the names of things in the namelist that were not matched. */ names_notfound() { register struct name *nlp; register char *p; for (nlp = namelist; nlp != 0; nlp = nlp->next) { if (!nlp->found) { fprintf(stderr, "tar: %s not found in archive\n", nlp->name); } /* * We could free() the list, but the process is about * to die anyway, so save some CPU time. Amigas and * other similarly broken software will need to waste * the time, though. */ #ifndef unix if (!f_sorted_names) free(nlp); #endif } namelist = (struct name *)NULL; namelast = (struct name *)NULL; if (f_sorted_names) { while (0 != (p = name_next())) fprintf(stderr, "tar: %s not found in archive\n", p); } } @@@ Fin de tar.c echo create.c cat >create.c <<'@@@ Fin de create.c' /* * Create a tar archive. * * Written 25 Aug 1985 by John Gilmore, ihnp4!hoptoad!gnu. * * @(#)create.c 1.36 11/6/87 Public Domain - gnu */ #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #ifndef V7 #include <fcntl.h> #endif #ifndef MSDOS #include <pwd.h> #include <grp.h> #endif #ifdef BSD42 #include <sys/dir.h> #else #ifdef MSDOS #include <sys/dir.h> #else /* * FIXME: On other systems there is no standard place for the header file * for the portable directory access routines. Change the #include line * below to bring it in from wherever it is. */ #include "ndir.h" #endif #endif #ifdef USG #include <sys/sysmacros.h> /* major() and minor() defined here */ #endif /* * V7 doesn't have a #define for this. */ #ifndef O_RDONLY #define O_RDONLY 0 #endif /* * Most people don't have a #define for this. */ #ifndef O_BINARY #define O_BINARY 0 #endif #include "tar.h" #include "port.h" extern union record *head; /* Points to current tape header */ extern struct stat hstat; /* Stat struct corresponding */ extern int head_standard; /* Tape header is in ANSI format */ /* * If there are no symbolic links, there is no lstat(). Use stat(). */ #ifndef S_IFLNK #define lstat stat #endif extern char *malloc(); extern char *strcpy(); extern char *strncpy(); extern void bzero(); extern void bcopy(); extern int errno; extern void print_header(); union record *start_header(); void finish_header(); void finduname(); void findgname(); char *name_next(); void to_oct(); void dump_file(); static nolinks; /* Gets set if we run out of RAM */ void create_archive() { register char *p; open_archive(0); /* Open for writing */ while (p = name_next()) { dump_file(p, -1); } write_eot(); close_archive(); name_close(); } /* * Dump a single file. If it's a directory, recurse. * Result is 1 for success, 0 for failure. * Sets global "hstat" to stat() output for this file. */ void dump_file(p, curdev) char *p; /* File name to dump */ int curdev; /* Device our parent dir was on */ { union record *header; char type; /* * Use stat if following (rather than dumping) 4.2BSD's * symbolic links. Otherwise, use lstat (which, on non-4.2 * systems, is #define'd to stat anyway. */ if (0 != f_follow_links? stat(p, &hstat): lstat(p, &hstat)) { badperror: perror(p); badfile: errors++; return; } /* * See if we are crossing from one file system to another, * and avoid doing so if the user only wants to dump one file system. */ if (f_local_filesys && curdev >= 0 && curdev != hstat.st_dev) { annorec(stderr, tar); fprintf(stderr, "%s: is on a different filesystem; not dumped\n", p); return; } /* * Check for multiple links. * * We maintain a list of all such files that we've written so * far. Any time we see another, we check the list and * avoid dumping the data again if we've done it once already. */ if (hstat.st_nlink > 1) switch (hstat.st_mode & S_IFMT) { register struct link *lp; case S_IFREG: /* Regular file */ #ifdef S_IFCTG case S_IFCTG: /* Contigous file */ #endif #ifdef S_IFCHR case S_IFCHR: /* Character special file */ #endif #ifdef S_IFBLK case S_IFBLK: /* Block special file */ #endif #ifdef S_IFIFO case S_IFIFO: /* Fifo special file */ #endif /* First quick and dirty. Hashing, etc later FIXME */ for (lp = linklist; lp; lp = lp->next) { if (lp->ino == hstat.st_ino && lp->dev == hstat.st_dev) { /* We found a link. */ hstat.st_size = 0; header = start_header(p, &hstat); if (header == NULL) goto badfile; strcpy(header->header.linkname, lp->name); header->header.linkflag = LF_LINK; finish_header(header); /* FIXME: Maybe remove from list after all links found? */ return; /* We dumped it */ } } /* Not found. Add it to the list of possible links. */ lp = (struct link *) malloc( (unsigned) (strlen(p) + sizeof(struct link) - NAMSIZ)); if (!lp) { if (!nolinks) { fprintf(stderr, "tar: no memory for links, they will be dumped as separate files\n"); nolinks++; } } lp->ino = hstat.st_ino; lp->dev = hstat.st_dev; strcpy(lp->name, p); lp->next = linklist; linklist = lp; } /* * This is not a link to a previously dumped file, so dump it. */ switch (hstat.st_mode & S_IFMT) { case S_IFREG: /* Regular file */ #ifdef S_IFCTG case S_IFCTG: /* Contigous file */ #endif { int f; /* File descriptor */ int bufsize, count; register long sizeleft; register union record *start; sizeleft = hstat.st_size; /* Don't bother opening empty, world readable files. */ if (sizeleft > 0 || 0444 != (0444 & hstat.st_mode)) { f = open(p, O_RDONLY|O_BINARY); if (f < 0) goto badperror; } else { f = -1; } header = start_header(p, &hstat); if (header == NULL) goto badfile; #ifdef S_IFCTG /* Mark contiguous files, if we support them */ if (f_standard && (hstat.st_mode & S_IFMT) == S_IFCTG) { header->header.linkflag = LF_CONTIG; } #endif finish_header(header); while (sizeleft > 0) { start = findrec(); bufsize = endofrecs()->charptr - start->charptr; if (sizeleft < bufsize) { /* Last read -- zero out area beyond */ bufsize = (int)sizeleft; count = bufsize % RECORDSIZE; if (count) bzero(start->charptr + sizeleft, RECORDSIZE - count); } count = read(f, start->charptr, bufsize); if (count < 0) { annorec(stderr, tar); fprintf(stderr, "read error at byte %ld, reading %d bytes, in file ", hstat.st_size - sizeleft, bufsize); perror(p); /* FIXME */ goto padit; } sizeleft -= count; /* This is nonportable (the type of userec's arg). */ userec(start+(count-1)/RECORDSIZE); if (count == bufsize) continue; annorec(stderr, tar); fprintf(stderr, "%s: file shrunk by %d bytes, padding with zeros.\n", p, sizeleft); goto padit; /* Short read */ } if (f >= 0) (void)close(f); break; /* * File shrunk or gave error, pad out tape to match * the size we specified in the header. */ padit: abort(); } #ifdef S_IFLNK case S_IFLNK: /* Symbolic link */ { int size; hstat.st_size = 0; /* Force 0 size on symlink */ header = start_header(p, &hstat); if (header == NULL) goto badfile; size = readlink(p, header->header.linkname, NAMSIZ); if (size < 0) goto badperror; if (size == NAMSIZ) { annorec(stderr, tar); fprintf(stderr, "%s: symbolic link too long\n", p); break; } header->header.linkname[size] = '\0'; header->header.linkflag = LF_SYMLINK; finish_header(header); /* Nothing more to do to it */ } break; #endif case S_IFDIR: /* Directory */ { register DIR *dirp; register struct direct *d; char namebuf[NAMSIZ+2]; register int len; int our_device = hstat.st_dev; /* Build new prototype name */ strncpy(namebuf, p, sizeof (namebuf)); len = strlen(namebuf); while (len >= 1 && '/' == namebuf[len-1]) len--; /* Delete trailing slashes */ namebuf[len++] = '/'; /* Now add exactly one back */ namebuf[len] = '\0'; /* Make sure null-terminated */ /* * Output directory header record with permissions * FIXME, do this AFTER files, to avoid R/O dir problems? * If old archive format, don't write record at all. */ if (!f_oldarch) { hstat.st_size = 0; /* Force 0 size on dir */ /* * If people could really read standard archives, * this should be: (FIXME) header = start_header(f_standard? p: namebuf, &hstat); * but since they'd interpret LF_DIR records as * regular files, we'd better put the / on the name. */ header = start_header(namebuf, &hstat); if (header == NULL) goto badfile; /* eg name too long */ if (f_standard) { header->header.linkflag = LF_DIR; } finish_header(header); /* Done with directory header */ } /* Hack to remove "./" from the front of all the file names */ if (len == 2 && namebuf[0] == '.') { len = 0; } /* Now output all the files in the directory */ if (f_dironly) break; /* Unless the user says no */ errno = 0; dirp = opendir(p); if (!dirp) { if (errno) { perror (p); } else { annorec(stderr, tar); fprintf(stderr, "%s: error opening directory", p); } break; } /* Should speed this up by cd-ing into the dir, FIXME */ while (NULL != (d=readdir(dirp))) { /* Skip . and .. */ if (d->d_name[0] == '.') { if (d->d_name[1] == '\0') continue; if (d->d_name[1] == '.') { if (d->d_name[2] == '\0') continue; } } if (d->d_namlen + len >= NAMSIZ) { annorec(stderr, tar); fprintf(stderr, "%s%s: name too long\n", namebuf, d->d_name); continue; } strcpy(namebuf+len, d->d_name); dump_file(namebuf, our_device); } closedir(dirp); } break; #ifdef S_IFCHR case S_IFCHR: /* Character special file */ type = LF_CHR; goto easy; #endif #ifdef S_IFBLK case S_IFBLK: /* Block special file */ type = LF_BLK; goto easy; #endif #ifdef S_IFIFO case S_IFIFO: /* Fifo special file */ type = LF_FIFO; #endif easy: if (!f_standard) goto unknown; hstat.st_size = 0; /* Force 0 size */ header = start_header(p, &hstat); if (header == NULL) goto badfile; /* eg name too long */ header->header.linkflag = type; if (type != LF_FIFO) { to_oct((long) major(hstat.st_rdev), 8, header->header.devmajor); to_oct((long) minor(hstat.st_rdev), 8, header->header.devminor); } finish_header(header); break; default: unknown: annorec(stderr, tar); fprintf(stderr, "%s: Unknown file type; file ignored.\n", p); break; } } /* * Make a header block for the file name whose stat info is st . * Return header pointer for success, NULL if the name is too long. */ union record * start_header(name, st) char *name; register struct stat *st; { register union record *header; header = (union record *) findrec(); bzero(header->charptr, sizeof(*header)); /* XXX speed up */ /* * Check the file name and put it in the record. */ while ('/' == *name) { static int warned_once = 0; name++; /* Force relative path */ if (!warned_once++) { annorec(stderr, tar); fprintf(stderr, "Removing leading / from absolute path names in the archive.\n"); } } strcpy(header->header.name, name); if (header->header.name[NAMSIZ-1]) { annorec(stderr, tar); fprintf(stderr, "%s: name too long\n", name); return NULL; } to_oct((long) (st->st_mode & ~S_IFMT), 8, header->header.mode); to_oct((long) st->st_uid, 8, header->header.uid); to_oct((long) st->st_gid, 8, header->header.gid); to_oct((long) st->st_size, 1+12, header->header.size); to_oct((long) st->st_mtime, 1+12, header->header.mtime); /* header->header.linkflag is left as null */ #ifndef NONAMES /* Fill in new Unix Standard fields if desired. */ if (f_standard) { header->header.linkflag = LF_NORMAL; /* New default */ strcpy(header->header.magic, TMAGIC); /* Mark as Unix Std */ finduname(header->header.uname, st->st_uid); findgname(header->header.gname, st->st_gid); } #endif return header; } /* * Finish off a filled-in header block and write it out. * We also print the file name and/or full info if verbose is on. */ void finish_header(header) register union record *header; { register int i, sum; register char *p; bcopy(CHKBLANKS, header->header.chksum, sizeof(header->header.chksum)); sum = 0; p = header->charptr; for (i = sizeof(*header); --i >= 0; ) { /* * We can't use unsigned char here because of old compilers, * e.g. V7. */ sum += 0xFF & *p++; } /* * Fill in the checksum field. It's formatted differently * from the other fields: it has [6] digits, a null, then a * space -- rather than digits, a space, then a null. * We use to_oct then write the null in over to_oct's space. * The final space is already there, from checksumming, and * to_oct doesn't modify it. * * This is a fast way to do: * (void) sprintf(header->header.chksum, "%6o", sum); */ to_oct((long) sum, 8, header->header.chksum); header->header.chksum[6] = '\0'; /* Zap the space */ userec(header); if (f_verbose) { /* These globals are parameters to print_header, sigh */ head = header; /* hstat is already set up */ head_standard = f_standard; print_header(stderr); } return; } /* * Quick and dirty octal conversion. * Converts long "value" into a "digs"-digit field at "where", * including a trailing space and room for a null. "digs"==3 means * 1 digit, a space, and room for a null. * * We assume the trailing null is already there and don't fill it in. * This fact is used by start_header and finish_header, so don't change it! * * This should be equivalent to: * (void) sprintf(where, "%*lo ", digs-2, value); * except that sprintf fills in the trailing null and we don't. */ void to_oct(value, digs, where) register long value; register int digs; register char *where; { --digs; /* Trailing null slot is left alone */ where[--digs] = ' '; /* Put in the space, though */ /* Produce the digits -- at least one */ do { where[--digs] = '0' + (char)(value & 7); /* one octal digit */ value >>= 3; } while (digs > 0 && value != 0); /* Leading spaces, if necessary */ while (digs > 0) where[--digs] = ' '; } /* * Write the EOT record(s). * We actually zero at least one record, through the end of the block. * Old tar writes garbage after two zeroed records -- and PDtar used to. */ write_eot() { union record *p; int bufsize; p = findrec(); bufsize = endofrecs()->charptr - p->charptr; bzero(p->charptr, bufsize); userec(p); } @@@ Fin de create.c echo extract.c cat >extract.c <<'@@@ Fin de extract.c' /* * Extract files from a tar archive. * * Written 19 Nov 1985 by John Gilmore, ihnp4!hoptoad!gnu. * * @(#) extract.c 1.32 87/11/11 Public Domain - gnu */ #include <stdio.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #ifdef BSD42 #include <sys/file.h> #endif #ifdef USG #include <fcntl.h> #endif #ifdef MSDOS #include <fcntl.h> #endif /* MSDOS */ /* * Some people don't have a #define for these. */ #ifndef O_BINARY #define O_BINARY 0 #endif #ifndef O_NDELAY #define O_NDELAY 0 #endif #ifdef NO_OPEN3 /* We need the #define's even though we don't use them. */ #include "open3.h" #endif #ifdef EMUL_OPEN3 /* Simulated 3-argument open for systems that don't have it */ #include "open3.h" #endif extern int errno; /* From libc.a */ extern time_t time(); /* From libc.a */ extern char *index(); /* From libc.a or port.c */ #include "tar.h" #include "port.h" extern union record *head; /* Points to current tape header */ extern struct stat hstat; /* Stat struct corresponding */ extern int head_standard; /* Tape header is in ANSI format */ extern void print_header(); extern void skip_file(); extern void pr_mkdir(); int make_dirs(); /* Makes required directories */ static time_t now = 0; /* Current time */ static we_are_root = 0; /* True if our effective uid == 0 */ static int notumask = ~0; /* Masks out bits user doesn't want */ /* * Set up to extract files. */ extr_init() { int ourmask; now = time((time_t *)0); if (geteuid() == 0) we_are_root = 1; /* * We need to know our umask. But if f_use_protection is set, * leave our kernel umask at 0, and our "notumask" at ~0. */ ourmask = umask(0); /* Read it */ if (!f_use_protection) { (void) umask (ourmask); /* Set it back how it was */ notumask = ~ourmask; /* Make umask override permissions */ } } /* * Extract a file from the archive. */ void extract_archive() { register char *data; int fd, check, namelen, written, openflag; long size; time_t acc_upd_times[2]; register int skipcrud; saverec(&head); /* Make sure it sticks around */ userec(head); /* And go past it in the archive */ decode_header(head, &hstat, &head_standard, 1); /* Snarf fields */ /* Print the record from 'head' and 'hstat' */ if (f_verbose) print_header(stdout); /* * Check for fully specified pathnames and other atrocities. * * Note, we can't just make a pointer to the new file name, * since saverec() might move the header and adjust "head". * We have to start from "head" every time we want to touch * the header record. */ skipcrud = 0; while ('/' == head->header.name[skipcrud]) { static int warned_once = 0; skipcrud++; /* Force relative path */ if (!warned_once++) { annorec(stderr, tar); fprintf(stderr, "Removing leading / from absolute path names in the archive.\n"); } } switch (head->header.linkflag) { default: annofile(stderr, tar); fprintf(stderr, "Unknown file type '%c' for %s, extracted as normal file\n", head->header.linkflag, skipcrud+head->header.name); /* FALL THRU */ case LF_OLDNORMAL: case LF_NORMAL: case LF_CONTIG: /* * Appears to be a file. * See if it's really a directory. */ namelen = strlen(skipcrud+head->header.name)-1; if (head->header.name[skipcrud+namelen] == '/') goto really_dir; /* FIXME, deal with protection issues */ again_file: openflag = f_keep? O_BINARY|O_NDELAY|O_WRONLY|O_APPEND|O_CREAT|O_EXCL: O_BINARY|O_NDELAY|O_WRONLY|O_APPEND|O_CREAT|O_TRUNC; #ifdef O_CTG /* * Contiguous files (on the Masscomp) have to specify * the size in the open call that creates them. */ if (head->header.lnkflag == LF_CONTIG) fd = open(skipcrud+head->header.name, openflag | O_CTG, hstat.st_mode, hstat.st_size); else #endif { #ifdef NO_OPEN3 /* * On raw V7 we won't let them specify -k (f_keep), but * we just bull ahead and create the files. */ fd = creat(skipcrud+head->header.name, hstat.st_mode); #else /* * With 3-arg open(), we can do this up right. */ fd = open(skipcrud+head->header.name, openflag, hstat.st_mode); #endif } if (fd < 0) { if (make_dirs(skipcrud+head->header.name)) goto again_file; annofile(stderr, tar); fprintf(stderr, "Could not make file "); perror(skipcrud+head->header.name); skip_file((long)hstat.st_size); goto quit; } for (size = hstat.st_size; size > 0; size -= written) { /* * Locate data, determine max length * writeable, write it, record that * we have used the data, then check * if the write worked. */ data = findrec()->charptr; if (data == NULL) { /* Check it... */ annorec(stderr, tar); fprintf(stderr, "Unexpected EOF on archive file\n"); break; } written = endofrecs()->charptr - data; if (written > size) written = size; errno = 0; check = write(fd, data, written); /* * The following is in violation of strict * typing, since the arg to userec * should be a struct rec *. FIXME. */ userec(data + written - 1); if (check == written) continue; /* * Error in writing to file. * Print it, skip to next file in archive. */ annofile(stderr, tar); fprintf(stderr, "Tried to write %d bytes to file, could only write %d:\n", written, check); perror(skipcrud+head->header.name); skip_file((long)(size - written)); break; /* Still do the close, mod time, chmod, etc */ } check = close(fd); if (check < 0) { annofile(stderr, tar); fprintf(stderr, "Error while closing "); perror(skipcrud+head->header.name); } set_filestat: /* * Set the modified time of the file. * * Note that we set the accessed time to "now", which * is really "the time we started extracting files". */ if (!f_modified) { acc_upd_times[0] = now; /* Accessed now */ acc_upd_times[1] = hstat.st_mtime; /* Mod'd */ if (utime(skipcrud+head->header.name, acc_upd_times) < 0) { annofile(stderr, tar); perror(skipcrud+head->header.name); } } /* * If we are root, set the owner and group of the extracted * file. This does what is wanted both on real Unix and on * System V. If we are running as a user, we extract as that * user; if running as root, we extract as the original owner. */ if (we_are_root) { if (chown(skipcrud+head->header.name, hstat.st_uid, hstat.st_gid) < 0) { annofile(stderr, tar); perror(skipcrud+head->header.name); } } /* * If '-k' is not set, open() or creat() could have saved * the permission bits from a previously created file, * ignoring the ones we specified. * Even if -k is set, if the file has abnormal * mode bits, we must chmod since writing or chown() has * probably reset them. * * If -k is set, we know *we* created this file, so the mode * bits were set by our open(). If the file is "normal", we * skip the chmod. This works because we did umask(0) if -p * is set, so umask will have left the specified mode alone. */ if ((!f_keep) || (hstat.st_mode & (S_ISUID|S_ISGID|S_ISVTX))) { if (chmod(skipcrud+head->header.name, notumask & (int)hstat.st_mode) < 0) { annofile(stderr, tar); perror(skipcrud+head->header.name); } } quit: break; case LF_LINK: again_link: check = link (head->header.linkname, skipcrud+head->header.name); if (check == 0) break; if (make_dirs(skipcrud+head->header.name)) goto again_link; annofile(stderr, tar); fprintf(stderr, "Could not link %s to ", skipcrud+head->header.name); perror(head->header.linkname); break; #ifdef S_IFLNK case LF_SYMLINK: again_symlink: check = symlink(head->header.linkname, skipcrud+head->header.name); /* FIXME, don't worry uid, gid, etc... */ if (check == 0) break; if (make_dirs(skipcrud+head->header.name)) goto again_symlink; annofile(stderr, tar); fprintf(stderr, "Could not create symlink "); perror(head->header.linkname); break; #endif #ifdef S_IFCHR case LF_CHR: hstat.st_mode |= S_IFCHR; goto make_node; #endif #ifdef S_IFBLK case LF_BLK: hstat.st_mode |= S_IFBLK; goto make_node; #endif #ifdef S_IFIFO /* If local system doesn't support FIFOs, use default case */ case LF_FIFO: hstat.st_mode |= S_IFIFO; hstat.st_rdev = 0; /* FIXME, do we need this? */ goto make_node; #endif make_node: check = mknod(skipcrud+head->header.name, (int) hstat.st_mode, (int) hstat.st_rdev); if (check != 0) { if (make_dirs(skipcrud+head->header.name)) goto make_node; annofile(stderr, tar); fprintf(stderr, "Could not make "); perror(skipcrud+head->header.name); break; }; goto set_filestat; case LF_DIR: namelen = strlen(skipcrud+head->header.name)-1; really_dir: /* Check for trailing /, and zap as many as we find. */ while (namelen && head->header.name[skipcrud+namelen] == '/') head->header.name[skipcrud+namelen--] = '\0'; again_dir: check = mkdir(skipcrud+head->header.name, 0300 | (int)hstat.st_mode); if (check != 0) { if (make_dirs(skipcrud+head->header.name)) goto again_dir; /* If we're trying to create '.', let it be. */ if (head->header.name[skipcrud+namelen] == '.' && (namelen==0 || head->header.name[skipcrud+namelen-1]=='/')) goto check_perms; annofile(stderr, tar); fprintf(stderr, "Could not make directory "); perror(skipcrud+head->header.name); break; } check_perms: if (0300 != (0300 & (int) hstat.st_mode)) { hstat.st_mode |= 0300; annofile(stderr, tar); fprintf(stderr, "Added write & execute permission to directory %s\n", skipcrud+head->header.name); } goto set_filestat; /* FIXME, Remember timestamps for after files created? */ /* FIXME, change mode after files created (if was R/O dir) */ } /* We don't need to save it any longer. */ saverec((union record **) 0); /* Unsave it */ } /* * After a file/link/symlink/dir creation has failed, see if * it's because some required directory was not present, and if * so, create all required dirs. */ int make_dirs(pathname) char *pathname; { char *p; /* Points into path */ int madeone = 0; /* Did we do anything yet? */ int save_errno = errno; /* Remember caller's errno */ int check; if (errno != ENOENT) return 0; /* Not our problem */ for (p = index(pathname, '/'); p != NULL; p = index(p+1, '/')) { /* Avoid mkdir of empty string, if leading or double '/' */ if (p == pathname || p[-1] == '/') continue; /* Avoid mkdir where last part of path is '.' */ if (p[-1] == '.' && (p == pathname+1 || p[-2] == '/')) continue; *p = 0; /* Truncate the path there */ check = mkdir (pathname, 0777); /* Try to create it as a dir */ if (check == 0) { /* Fix ownership */ if (we_are_root) { if (chown(pathname, hstat.st_uid, hstat.st_gid) < 0) { annofile(stderr, tar); perror(pathname); } } pr_mkdir(pathname, p-pathname, notumask&0777, stdout); madeone++; /* Remember if we made one */ *p = '/'; continue; } *p = '/'; if (errno == EEXIST) /* Directory already exists */ continue; /* * Some other error in the mkdir. We return to the caller. */ break; } errno = save_errno; /* Restore caller's errno */ return madeone; /* Tell them to retry if we made one */ } @@@ Fin de extract.c echo buffer.c cat >buffer.c <<'@@@ Fin de buffer.c' /* * Buffer management for public domain tar. * * Written by John Gilmore, ihnp4!hoptoad!gnu, on 25 August 1985. * * @(#) buffer.c 1.28 11/6/87 Public Domain - gnu */ #include <stdio.h> #include <errno.h> #include <sys/types.h> /* For non-Berkeley systems */ #include <sys/stat.h> #include <signal.h> #ifdef MSDOS # include <fcntl.h> #else # ifdef XENIX # include <sys/inode.h> # endif # include <sys/file.h> #endif #include "tar.h" #include "port.h" #define STDIN 0 /* Standard input file descriptor */ #define STDOUT 1 /* Standard output file descriptor */ #define PREAD 0 /* Read file descriptor from pipe() */ #define PWRITE 1 /* Write file descriptor from pipe() */ extern char *valloc(); extern char *index(), *strcat(); /* * V7 doesn't have a #define for this. */ #ifndef O_RDONLY #define O_RDONLY 0 #endif #define MAGIC_STAT 105 /* Magic status returned by child, if it can't exec. We hope compress/sh never return this status! */ /* * The record pointed to by save_rec should not be overlaid * when reading in a new tape block. Copy it to record_save_area first, and * change the pointer in *save_rec to point to record_save_area. * Saved_recno records the record number at the time of the save. * This is used by annofile() to print the record number of a file's * header record. */ static union record **save_rec; static union record record_save_area; static long saved_recno; /* * PID of child program, if f_compress or remote archive access. */ static int childpid = 0; /* * Record number of the start of this block of records */ static long baserec; /* * Error recovery stuff */ static int r_error_count; /* * Have we hit EOF yet? */ static int eof; /* * Return the location of the next available input or output record. * Return NULL for EOF. Once we have returned NULL, we just keep returning * it, to avoid accidentally going on to the next file on the "tape". */ union record * findrec() { if (ar_record == ar_last) { if (eof) return (union record *)NULL; /* EOF */ flush_archive(); if (ar_record == ar_last) { eof++; return (union record *)NULL; /* EOF */ } } return ar_record; } /* * Indicate that we have used all records up thru the argument. * (should the arg have an off-by-1? XXX FIXME) */ void userec(rec) union record *rec; { while(rec >= ar_record) ar_record++; /* * Do NOT flush the archive here. If we do, the same * argument to userec() could mean the next record (if the * input block is exactly one record long), which is not what * is intended. */ if (ar_record > ar_last) abort(); } /* * Return a pointer to the end of the current records buffer. * All the space between findrec() and endofrecs() is available * for filling with data, or taking data from. */ union record * endofrecs() { return ar_last; } /* * Duplicate a file descriptor into a certain slot. * Equivalent to BSD "dup2" with error reporting. */ void dupto(from, to, msg) int from, to; char *msg; { int err; if (from != to) { (void) close(to); err = dup(from); if (err != to) { fprintf(stderr, "tar: cannot dup "); perror(msg); exit(EX_SYSTEM); } (void) close(from); } } /* * Fork a child to deal with remote files or compression. * If rem_host is zero, we are called only for compression. */ void child_open(rem_host, rem_file) char *rem_host, *rem_file; { #ifdef MSDOS fprintf(stderr, "MSDOS %s cannot deal with compressed or remote archives\n", tar); exit(EX_ARGSBAD); #else int pipes[2]; int err; struct stat arstat; char cmdbuf[1000]; /* For big file and host names */ /* Create a pipe to talk to the child over */ err = pipe(pipes); if (err < 0) { perror ("tar: cannot create pipe to child"); exit(EX_SYSTEM); } /* Fork child process */ childpid = fork(); if (childpid < 0) { perror("tar: cannot fork"); exit(EX_SYSTEM); } /* * Parent process. Clean up. * * We always close the archive file (stdin, stdout, or opened file) * since the child will end up reading or writing that for us. * Note that this may leave standard input closed. * We close the child's end of the pipe since they will handle * that too; and we set <archive> to the other end of the pipe. * * If reading, we set f_reblock since reading pipes or network * sockets produces odd length data. */ if (childpid > 0) { (void) close (archive); if (ar_reading) { (void) close (pipes[PWRITE]); archive = pipes[PREAD]; f_reblock++; } else { (void) close (pipes[PREAD]); archive = pipes[PWRITE]; } return; } /* * Child process. */ if (ar_reading) { /* * Reading from the child... * * Close the read-side of the pipe, which our parent will use. * Move the write-side of pipe to stdout, * If local, move archive input to child's stdin, * then run the child. */ (void) close (pipes[PREAD]); dupto(pipes[PWRITE], STDOUT, "to stdout"); if (rem_host) { (void) close (STDIN); /* rsh abuses stdin */ if (STDIN != open("/dev/null")) perror("Can't open /dev/null"); sprintf(cmdbuf, "rsh '%s' dd '<%s' bs=%db", rem_host, rem_file, blocking); if (f_compress) strcat(cmdbuf, "| compress -d"); #ifdef DEBUG fprintf(stderr, "Exec-ing: %s\n", cmdbuf); #endif execlp("sh", "sh", "-c", cmdbuf, (char *)0); perror("tar: cannot exec sh"); } else { /* * If we are reading a disk file, compress is OK; * otherwise, we have to reblock the input in case it's * coming from a tape drive. This is an optimization. */ dupto(archive, STDIN, "to stdin"); err = fstat(STDIN, &arstat); if (err != 0) { perror("tar: can't fstat archive"); exit(EX_SYSTEM); } if ((arstat.st_mode & S_IFMT) == S_IFREG) { execlp("compress", "compress", "-d", (char *)0); perror("tar: cannot exec compress"); } else { /* Non-regular file needs dd before compress */ sprintf(cmdbuf, "dd bs=%db | compress -d", blocking); #ifdef DEBUG fprintf(stderr, "Exec-ing: %s\n", cmdbuf); #endif execlp("sh", "sh", "-c", cmdbuf, (char *)0); perror("tar: cannot exec sh"); } } exit(MAGIC_STAT); } else { /* * Writing archive to the child. * It would like to run either: * compress * compress | dd obs=20b * rsh 'host' dd obs=20b '>foo' * or compress | rsh 'host' dd obs=20b '>foo' * * We need the dd to reblock the output to the * user's specs, if writing to a device or over * the net. However, it produces a stupid * message about how many blocks it processed. * Because the shell on the remote end could be just * about any shell, we can't depend on it to do * redirect stderr properly for us -- the csh * doesn't use the same syntax as the Bourne shell. * On the other hand, if we just ignore stderr on * this end, we won't see errors from rsh, or from * the inability of "dd" to write its output file. * The combination of the local sh, the rsh, the * remote csh, and maybe a remote sh conspires to mess * up any possible quoting method, so grumble! we * punt and just accept the fucking "xxx blocks" * messages. The real fix would be a "dd" that * would shut up. * * Close the write-side of the pipe, which our parent will use. * Move the read-side of the pipe to stdin, * If local, move archive output to the child's stdout. * then run the child. */ (void) close (pipes[PWRITE]); dupto(pipes[PREAD], STDIN, "to stdin"); if (!rem_host) dupto(archive, STDOUT, "to stdout"); cmdbuf[0] = '\0'; if (f_compress) { if (!rem_host) { err = fstat(STDOUT, &arstat); if (err != 0) { perror("tar: can't fstat archive"); exit(EX_SYSTEM); } if ((arstat.st_mode & S_IFMT) == S_IFREG) { execlp("compress", "compress", (char *)0); perror("tar: cannot exec compress"); } } strcat(cmdbuf, "compress | "); } if (rem_host) { sprintf(cmdbuf+strlen(cmdbuf), "rsh '%s' dd obs=%db '>%s'", rem_host, blocking, rem_file); } else { sprintf(cmdbuf+strlen(cmdbuf), "dd obs=%db", blocking); } #ifdef DEBUG fprintf(stderr, "Exec-ing: %s\n", cmdbuf); #endif execlp("sh", "sh", "-c", cmdbuf, (char *)0); perror("tar: cannot exec sh"); exit(MAGIC_STAT); } #endif /* MSDOS */ } /* * Open an archive file. The argument specifies whether we are * reading or writing. */ open_archive(read) int read; { char *colon, *slash; char *rem_host = 0, *rem_file; colon = index(ar_file, ':'); if (colon) { slash = index(ar_file, '/'); if (slash && slash > colon) { /* * Remote file specified. Parse out separately, * and don't try to open it on the local system. */ rem_file = colon + 1; rem_host = ar_file; *colon = '\0'; goto gotit; } } if (ar_file[0] == '-' && ar_file[1] == '\0') { f_reblock++; /* Could be a pipe, be safe */ if (read) archive = STDIN; else archive = STDOUT; } else if (read) { archive = open(ar_file, O_RDONLY); } else { archive = creat(ar_file, 0666); } if (archive < 0) { perror(ar_file); exit(EX_BADARCH); } #ifdef MSDOS setmode(archive, O_BINARY); #endif gotit: if (blocksize == 0) { fprintf(stderr, "tar: invalid value for blocksize\n"); exit(EX_ARGSBAD); } /*NOSTRICT*/ ar_block = (union record *) valloc((unsigned)blocksize); if (!ar_block) { fprintf(stderr, "tar: could not allocate memory for blocking factor %d\n", blocking); exit(EX_ARGSBAD); } ar_record = ar_block; ar_last = ar_block + blocking; ar_reading = read; if (f_compress || rem_host) child_open(rem_host, rem_file); if (read) { ar_last = ar_block; /* Set up for 1st block = # 0 */ (void) findrec(); /* Read it in, check for EOF */ } } /* * Remember a union record * as pointing to something that we * need to keep when reading onward in the file. Only one such * thing can be remembered at once, and it only works when reading * an archive. * * We calculate "offset" then add it because some compilers end up * adding (baserec+ar_record), doing a 9-bit shift of baserec, then * subtracting ar_block from that, shifting it back, losing the top 9 bits. */ saverec(pointer) union record **pointer; { long offset; save_rec = pointer; offset = ar_record - ar_block; saved_recno = baserec + offset; } /* * Perform a write to flush the buffer. */ fl_write() { int err; err = write(archive, ar_block->charptr, blocksize); if (err == blocksize) return; /* FIXME, multi volume support on write goes here */ if (err < 0) perror(ar_file); else fprintf(stderr, "tar: %s: write failed, short %d bytes\n", ar_file, blocksize - err); exit(EX_BADARCH); } /* * Handle read errors on the archive. * * If the read should be retried, readerror() returns to the caller. */ void readerror() { # define READ_ERROR_MAX 10 read_error_flag++; /* Tell callers */ annorec(stderr, tar); fprintf(stderr, "Read error on "); perror(ar_file); if (baserec == 0) { /* First block of tape. Probably stupidity error */ exit(EX_BADARCH); } /* * Read error in mid archive. We retry up to READ_ERROR_MAX times * and then give up on reading the archive. We set read_error_flag * for our callers, so they can cope if they want. */ if (r_error_count++ > READ_ERROR_MAX) { annorec(stderr, tar); fprintf(stderr, "Too many errors, quitting.\n"); exit(EX_BADARCH); } return; } /* * Perform a read to flush the buffer. */ fl_read() { int err; /* Result from system call */ int left; /* Bytes left */ char *more; /* Pointer to next byte to read */ /* * Clear the count of errors. This only applies to a single * call to fl_read. We leave read_error_flag alone; it is * only turned off by higher level software. */ r_error_count = 0; /* Clear error count */ /* * If we are about to wipe out a record that * somebody needs to keep, copy it out to a holding * area and adjust somebody's pointer to it. */ if (save_rec && *save_rec >= ar_record && *save_rec < ar_last) { record_save_area = **save_rec; *save_rec = &record_save_area; } error_loop: err = read(archive, ar_block->charptr, blocksize); if (err == blocksize) return; if (err < 0) { readerror(); goto error_loop; /* Try again */ } more = ar_block->charptr + err; left = blocksize - err; again: if (0 == (((unsigned)left) % RECORDSIZE)) { /* FIXME, for size=0, multi vol support */ /* On the first block, warn about the problem */ if (!f_reblock && baserec == 0 && f_verbose && err > 0) { annorec(stderr, tar); fprintf(stderr, "Blocksize = %d record%s\n", err / RECORDSIZE, (err > RECORDSIZE)? "s": ""); } ar_last = ar_block + ((unsigned)(blocksize - left))/RECORDSIZE; return; } if (f_reblock) { /* * User warned us about this. Fix up. */ if (left > 0) { error2loop: err = read(archive, more, left); if (err < 0) { readerror(); goto error2loop; /* Try again */ } if (err == 0) { annorec(stderr, tar); fprintf(stderr, "%s: eof not on block boundary, strange...\n", ar_file); exit(EX_BADARCH); } left -= err; more += err; goto again; } } else { annorec(stderr, tar); fprintf(stderr, "%s: read %d bytes, strange...\n", ar_file, err); exit(EX_BADARCH); } } /* * Flush the current buffer to/from the archive. */ flush_archive() { baserec += ar_last - ar_block; /* Keep track of block #s */ ar_record = ar_block; /* Restore pointer to start */ ar_last = ar_block + blocking; /* Restore pointer to end */ if (!ar_reading) fl_write(); else fl_read(); } /* * Close the archive file. */ close_archive() { int child; int status; if (!ar_reading) flush_archive(); (void) close(archive); #ifndef MSDOS if (childpid) { /* * Loop waiting for the right child to die, or for * no more kids. */ while (((child = wait(&status)) != childpid) && child != -1) ; if (child != -1) { switch (TERM_SIGNAL(status)) { case 0: /* Child voluntarily terminated -- but why? */ if (TERM_VALUE(status) == MAGIC_STAT) { exit(EX_SYSTEM);/* Child had trouble */ } if (TERM_VALUE(status) == (SIGPIPE + 128)) { /* * /bin/sh returns this if its child * dies with SIGPIPE. 'Sok. */ break; } else if (TERM_VALUE(status)) fprintf(stderr, "tar: child returned status %d\n", TERM_VALUE(status)); case SIGPIPE: break; /* This is OK. */ default: fprintf(stderr, "tar: child died with signal %d%s\n", TERM_SIGNAL(status), TERM_COREDUMP(status)? " (core dumped)": ""); } } } #endif /* MSDOS */ } /* * Message management. * * anno writes a message prefix on stream (eg stdout, stderr). * * The specified prefix is normally output followed by a colon and a space. * However, if other command line options are set, more output can come * out, such as the record # within the archive. * * If the specified prefix is NULL, no output is produced unless the * command line option(s) are set. * * If the third argument is 1, the "saved" record # is used; if 0, the * "current" record # is used. */ void anno(stream, prefix, savedp) FILE *stream; char *prefix; int savedp; { # define MAXANNO 50 char buffer[MAXANNO]; /* Holds annorecment */ # define ANNOWIDTH 13 int space; long offset; /* Make sure previous output gets out in sequence */ if (stream == stderr) fflush(stdout); if (f_sayblock) { if (prefix) { fputs(prefix, stream); putc(' ', stream); } offset = ar_record - ar_block; sprintf(buffer, "rec %d: ", savedp? saved_recno: baserec + offset); fputs(buffer, stream); space = ANNOWIDTH - strlen(buffer); if (space > 0) { fprintf(stream, "%*s", space, ""); } } else if (prefix) { fputs(prefix, stream); fputs(": ", stream); } } @@@ Fin de buffer.c echo getoldopt.c cat >getoldopt.c <<'@@@ Fin de getoldopt.c' /* * Plug-compatible replacement for getopt() for parsing tar-like * arguments. If the first argument begins with "-", it uses getopt; * otherwise, it uses the old rules used by tar, dump, and ps. * * Written 25 August 1985 by John Gilmore (ihnp4!hoptoad!gnu) and placed * in the Pubic Domain for your edification and enjoyment. * * @(#)getoldopt.c 1.4 2/4/86 Public Domain - gnu */ #include <stdio.h> int getoldopt(argc, argv, optstring) int argc; char **argv; char *optstring; { extern char *optarg; /* Points to next arg */ extern int optind; /* Global argv index */ static char *key; /* Points to next keyletter */ static char use_getopt; /* !=0 if argv[1][0] was '-' */ extern char *index(); char c; char *place; optarg = NULL; if (key == NULL) { /* First time */ if (argc < 2) return EOF; key = argv[1]; if (*key == '-') use_getopt++; else optind = 2; } if (use_getopt) return getopt(argc, argv, optstring); c = *key++; if (c == '\0') { key--; return EOF; } place = index(optstring, c); if (place == NULL || c == ':') { fprintf(stderr, "%s: unknown option %c\n", argv[0], c); return('?'); } place++; if (*place == ':') { if (optind < argc) { optarg = argv[optind]; optind++; } else { fprintf(stderr, "%s: %c argument missing\n", argv[0], c); return('?'); } } return(c); } @@@ Fin de getoldopt.c exit 0