/* This file is in the public domain. */ /* notes to myself: W tar -static -V -g -DROOT_UNLINK_BUG standards: gwoods sez IEEE Std 1003.1-1990, POSIX 1003.2b (-2001?) extensions. */ /* * Compile-time options: * * NO_PROGNAME * If defined, provides its own __progname; if __progname * turns up undefined in the link, define this. * * NO_STRNCASECMP * If defined, provides its own strncasecmp(); if strncasecmp * turns up undefined in the link, define this. * * NO_FIFO * Assume the system doesn't have FIFOs, even if S_IFIFO is * defined by the system include files. If mkfifo turns up * undefined in the link, try defining this. * * NO_DEVICES * Don't attempt to do anything with device special files. * If you have trouble with makedev, major, minor, this is * probably the quickest fix, though it will mean that the * resulting executable can't extract or archive device * special files. It will also mean you can't use i-file. * * NO_SYNTHETIC * Completely disable the use of -S to generate synthetic * archive entries. (I can't see the point of this - it's * not as if generating arbitrary archives is hard - but * someone wanted it and it's easy to do.) * * DECLARE_ERRNO * Declare "extern int errno;". Normally errno will be * declared by , and with some implementations an * additional declaration can be a problem. This is * provided for compatability with implementations that * don't declare it in . * * USE_RMT * Use the remote-tape library. This requires rmt_open, * rmt_read, rmt_write, and rmt_ioctl from the remote tape * library. * * ROOT_UNLINK_BUG * Define this if root calling unlink() on a directory * results in orphaning the directory. (While this is * true on almost all systems, I feel it is definitely a * bug, so this is not the default.) * * NO_STRERROR * The system doesn't provide strerror(), so provide a * workable replacement version. * * DIRENT_FIXUP * Do "#define direct dirent", provided for systems whose * provides "struct dirent" (rather than * "struct direct") as the return type for readdir(). * * LINUX_NAMLEN * Linux, gratuitously incompatible with the rest of the * known universe as usual, does not have d_namlen. * Define this and tar will attempt to compensate. * * USE_GNUC_ATTRIBUTES * NO_GNUC_ATTRIBUTES * Use, or don't use, GNU C's __attribute__ extension to * get better compile-time checking of a few things, and * sometimes better optimization. If neither is defined, * tries to decide whether gcc is in use, and if so, * whether it's a version that supports __attribute__. * * TAR_MAP_SUN * TAR_MAP_NEXT * TAR_MAP_NETBSD * TAR_MAP_OSF1 * TAR_MAP_NONE * Describe what sort of map support should be used. * TAR_MAP_SUN uses Sun-style mmap/munmap; TAR_MAP_NEXT * uses NeXT-style vm_allocate/map_fd/vm_deallocate; * TAR_MAP_NETBSD uses NetBSD-style mmap/munmap; * TAR_MAP_OSF1 uses OSF/1-style mmap/munmap; and * TAR_MAP_NONE uses plain read() rather than mapping at * all. This affects only how files are read to determine * sparsity when the S keyletter is used to create an * archive; reading files for actual dumping is always * done with read(). If none is defined, a guess is made * based on preprocessor-defined identification symbols. * * TAR_INODE_SUN * TAR_INODE_NEXT * TAR_INODE_NETBSD * TAR_INODE_ALPHA_OSF * TAR_INODE_ULTRIX * TAR_INODE_INTERNAL * TAR_INODE_NONE * (These used to be called INODE_xxx; the names were * changed due to potential conflicts with system * headers.) * * Describe what sort of raw-inode-reading code should be * compiled in (support for the i keyletter). * TAR_INODE_NONE specifies that reading files by inodes * is to be completely disabled (useful if you want to use * tar for normal use on a new system without having to * port the inode-reading code). The others all specify * that the 4.2 "fast file system" format is to be read; * they differ in that they control what actions are to be * taken to get the necessary definitions. TAR_INODE_SUN, * TAR_INODE_NEXT, TAR_INODE_NETBSD, TAR_INODE_ALPHA_OSF, * and TAR_INODE_ULTRIX specify, respectively, SunOS * 4.1.x, NeXT (at least release 2.1), NetBSD, DEC Alpha * running OSF/1, and Ultrix; TAR_INODE_INTERNAL specifies * that tar is to completely ignore system header files * and structure definitions and provide its own, blindly * assuming that the on-disk filesystem layout matches the * Berkeley `ffs' one. Note that if you define * TAR_INODE_INTERNAL and the filesystem *isn't* the 4.2 * one, you may get run-time errors or you may silently * get garbage (if you're lucky, it'll be obvious * garbage). * * If none of these are defined, a guess is made based on * preprocessor-predefined symbols. * * Note that there is a bug in the include files for at * least one NeXT release; you may get complaints about * "vm_pager_null redefined". I know of no way to make * these go away except to edit the offending files. * * NO_FASTLINK * Suppresses the "fast symlink" code. Normally, when * reading filesystems for i-full, tar is prepared to deal * with "fast" symlinks, where the link-to string is * overlaid on the block pointers. However, this code can * mistake a non-fast symlink for a fast symlink, * especially for short link-to strings on a little-endian * machine. Defining this means that fast links will not * even be considered; if a fast link is encountered, it * will almost certainly be mistaken for a corrupted * filesystem. * * TAR_MOUNT_GETMNTENT * TAR_MOUNT_GETFSSTAT * TAR_MOUNT_GETMNT * TAR_MOUNT_NONE * (These used to be called MOUNT_xxx; the names were * changed due to conflicts with system headers on at * least one system.) * * Specifies how the mount table is to be read. (This is * not used if TAR_INODE_NONE is defined, because then there * is no need to read the mount table.) * TAR_MOUNT_GETMNTENT says to use getmntent(); * TAR_MOUNT_GETFSSTAT says to use getfsstat(); * TAR_MOUNT_GETMNT says to use getmnt(); and * TAR_MOUNT_NONE says that no reading of the mount table * is to be attempted (and then the i option will not be * usable without the U option). * * If none of these are defined, a guess is made based on * which TAR_INODE_xxx symbol is defined (perhaps * according to the default as determined from * predefinitions). At present, this defaulting is as * follows: TAR_INODE_SUN and TAR_INODE_NEXT use * TAR_MOUNT_GETMNTENT, TAR_INODE_ALPHA_OSF and * TAR_INODE_NETBSD use TAR_MOUNT_GETFSSTAT, * TAR_INODE_ULTRIX uses TAR_MOUNT_GETMNT, * TAR_INODE_INTERNAL uses TAR_MOUNT_NONE, and * TAR_INODE_NONE completely ignores the TAR_MOUNT_xxx * symbols. * * SYMLINK_CHOWN_NONE * SYMLINK_CHOWN_CHOWN * SYMLINK_CHOWN_LCHOWN * These control how tar should set the ownership of * newly-created symbolic links. SYMLINK_CHOWN_NONE says * that this should not be attempted at all; * SYMLINK_CHOWN_CHOWN says that chown(2) should be used; * and SYMLINK_CHOWN_LCHOWN says that lchown(2) should be * used. If none are defined, a guess is made based on * preprocessor symbols. * * Additions (see man page for details): * * all the long keys (see long_keys() below) * C modifier: comparison mode. * E modifier: how to handle files that didn't dump right. * F modifier: relaxes header block checking. * I modifier: take names for a c operation from stdin. * L modifier: don't handle too-long pathnames with a private header type. * M modifier: read multiple canisters. * O modifier: extract files to stdout. * P modifier: filter a canister. * R modifier: aids in extracting tars written with absolute paths. * S modifier: deal with sparse files. * U modifier: specify disk device to use for i. * V modifier: specify where log-style output (eg, due to v) goes. * W modifier: arguments are wildcards (for t, x, C). * X modifier: reverse sense of arglist tests (for t, x, C). * d modifier: don't leave mount point of argument. * e modifier: stat() again to see if file changed. * i modifier: read disk device(s) directly. * j modifier: evil-archive paranoia when extracting. * k modifier: create a kill archive. * r modifier: sync() and reread inodes when contents are doubtful. * s modifier: archive only things modified since a given date. * u modifier: use raw (`uncooked') disk devices for i. * -Q flag among arguments: next arg is file even if it looks like flag. * for c: * -C flag among arguments: chdir before appending further arguments. * -X flag among arguments: exclude that path * -I flag among arguments: override later -X * -S flag among arguments: synthetic entries * for t, x, C: * -H flag among arguments: arg is head. * -T flag among arguments: arg is tail. * -W flag among arguments: toggle W for just next argument. * -X flag among arguments: toggle X for just next argument. * * Credit: most of this program is my work. Credit for the initial * implementation of the s, e, i, r, u, and E keyletters belongs to * Steven Winikoff, at the time smw@alcor.concordia.ca, though I have * since rewritten it. * * I am.... * * der Mouse * * mouse@rodents.montreal.qc.ca * 7D C8 61 52 5D E7 2D 39 4E F1 31 3E E8 B3 27 4B */ #include /* #include grrr... */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* If none of the TAR_MAP_* symbols are defined, guess */ #if !defined(TAR_MAP_SUN) && \ !defined(TAR_MAP_NEXT) && \ !defined(TAR_MAP_NETBSD) && \ !defined(TAR_MAP_OSF1) && \ !defined(TAR_MAP_NONE) #ifdef sun #define TAR_MAP_SUN #endif #ifdef NeXT #define TAR_MAP_NEXT #endif #ifdef __NetBSD__ #define TAR_MAP_NETBSD #endif #if defined(__osf__) #define TAR_MAP_OSF1 #endif #endif /* If other than exactly one of the TAR_MAP_* symbols is defined, something weird is going on, so fall back on TAR_MAP_NONE */ #if defined(TAR_MAP_SUN) + \ defined(TAR_MAP_NEXT) + \ defined(TAR_MAP_NETBSD) + \ defined(TAR_MAP_OSF1) + \ defined(TAR_MAP_NONE) != 1 #undef TAR_MAP_SUN #undef TAR_MAP_NEXT #undef TAR_MAP_NETBSD #undef TAR_MAP_OSF1 #undef TAR_MAP_NONE #define TAR_MAP_NONE #endif /* Include files appropriate to the TAR_MAP_* variants */ #ifdef TAR_MAP_SUN #include #include #ifndef _MAP_NEW extern char *valloc(); #endif #endif #ifdef TAR_MAP_NEXT #include #include #endif #if defined(TAR_MAP_NETBSD) || defined(TAR_MAP_OSF1) #include #include #ifndef MAP_FILE #define MAP_FILE 0 #endif #endif /* If none of the TAR_INODE_* symbols are defined, guess */ #if !defined(TAR_INODE_SUN) && \ !defined(TAR_INODE_NEXT) && \ !defined(TAR_INODE_NETBSD) && \ !defined(TAR_INODE_ALPHA_OSF) && \ !defined(TAR_INODE_ULTRIX) && \ !defined(TAR_INODE_INTERNAL) && \ !defined(TAR_INODE_NONE) #ifdef sun #define TAR_INODE_SUN #endif #ifdef NeXT #define TAR_INODE_NEXT #endif #ifdef __NetBSD__ #define TAR_INODE_NETBSD #endif #if defined(__osf__) && defined(__alpha) #define TAR_INODE_ALPHA_OSF #endif #if defined(ultrix) #define TAR_INODE_ULTRIX #endif #endif /* If other than exactly one of the TAR_INODE_* symbols is defined, something weird is going on, so fall back on TAR_INODE_NONE */ #if defined(TAR_INODE_SUN) + \ defined(TAR_INODE_NEXT) + \ defined(TAR_INODE_NETBSD) + \ defined(TAR_INODE_ALPHA_OSF) + \ defined(TAR_INODE_ULTRIX) + \ defined(TAR_INODE_INTERNAL) + \ defined(TAR_INODE_NONE) != 1 #undef TAR_INODE_SUN #undef TAR_INODE_NEXT #undef TAR_INODE_NETBSD #undef TAR_INODE_ALPHA_OSF #undef TAR_INODE_ULTRIX #undef TAR_INODE_INTERNAL #undef TAR_INODE_NONE #define TAR_INODE_NONE #endif /* If none of the SYMLINK_CHOWN_* symbols are defined, guess */ #if !defined(SYMLINK_CHOWN_NONE) && \ !defined(SYMLINK_CHOWN_CHOWN) && \ !defined(SYMLINK_CHOWN_LCHOWN) #define SYMLINK_CHOWN_CHOWN #if defined(__osf__) || defined(__NetBSD__) #undef SYMLINK_CHOWN_CHOWN #define SYMLINK_CHOWN_LCHOWN #endif #endif /* If other than exactly one of the SYMLINK_CHOWN_* symbols is defined, something weird is going on, so fall back on SYMLINK_CHOWN_NONE */ #if defined(SYMLINK_CHOWN_NONE) + \ defined(SYMLINK_CHOWN_CHOWN) + \ defined(SYMLINK_CHOWN_LCHOWN) != 1 #undef SYMLINK_CHOWN_NONE #undef SYMLINK_CHOWN_CHOWN #undef SYMLINK_CHOWN_LCHOWN #define SYMLINK_CHOWN_NONE #endif /* If none of the TAR_MOUNT_* symbols are defined, guess */ #if !defined(TAR_MOUNT_GETMNTENT) && \ !defined(TAR_MOUNT_GETFSSTAT) && \ !defined(TAR_MOUNT_GETMNT) && \ !defined(TAR_MOUNT_NONE) #if defined(TAR_INODE_SUN) || defined(TAR_INODE_NEXT) #define TAR_MOUNT_GETMNTENT #endif #if defined(TAR_INODE_ALPHA_OSF) || defined(TAR_INODE_NETBSD) #define TAR_MOUNT_GETFSSTAT #endif #ifdef TAR_INODE_ULTRIX #define TAR_MOUNT_GETMNT #endif #endif /* If other than exactly one of the TAR_MOUNT_* symbols is defined, something weird is going on, so fall back on TAR_MOUNT_NONE. Exception: if TAR_INODE_NONE, we force TAR_MOUNT_NONE. */ #if defined(TAR_INODE_NONE) || \ ( defined(TAR_MOUNT_GETMNTENT) + \ defined(TAR_MOUNT_GETFSSTAT) + \ defined(TAR_MOUNT_GETMNT) + \ defined(TAR_MOUNT_NONE) != 1 ) #undef TAR_MOUNT_GETMNTENT #undef TAR_MOUNT_GETFSSTAT #undef TAR_MOUNT_GETMNT #undef TAR_MOUNT_NONE #define TAR_MOUNT_NONE #endif /* At least one system defines struct direct one way in and another, incompatible, way in its disk include files. So we use rawi_direct for the disk one, with #defines in the system-specific sections below to make this work. We also have trouble with di_?time being timeval, timespec, or simple long int, and printing big values. */ /* Default versions, overridable in system-specific sections */ #define tar_di_atime di_atime #define tar_di_mtime di_mtime #define tar_di_ctime di_ctime #define isfastlink(di) \ ( ((di)->di_size <= ((NDADDR+NIADDR)*sizeof(daddr_t))) && \ ((di)->di_size == strlen((char *)&(di)->di_db[0])) ) #define SBFIXUP(sb) do{}while(0) #define SBBUFSIZE SBSIZE #define SBBUFALIGN(ptr) (ptr) #define BIGFMT "%ld" #define BIGTYPE long int #define BIGUFMT "%lu" #define BIGUTYPE unsigned long int /* Sun: mostly pretty boring. But some releases declare opendir() and friends, including the _dirdesc struct and the DIR type, in as well as - and the two are incompatible! */ #ifdef TAR_INODE_SUN #include #include #include #include #include #define direct rawi_direct #define DIR tarxx_DIR #define _dirdesc tarxx__dirdesc #define opendir tarxx_opendir #define readdir tarxx_readdir #define telldir tarxx_telldir #define seekdir tarxx_seekdir #define closedir tarxx_closedir #include #undef direct #undef DIR #undef _dirdesc #undef opendir #undef readdir #undef telldir #undef seekdir #undef closedir #undef SBBUFSIZE #undef SBBUFALIGN #define SBBUFSIZE (SBSIZE+8) #define SBBUFALIGN(ptr) ((char *)((((int)(ptr))+7)&~7)) #endif /* NeXT: they seem to use d_fileno instead of d_ino. */ #ifdef TAR_INODE_NEXT #include #include #include #include #define rawi_direct direct #define d_ino d_fileno #endif /* NetBSD: fix up itoo/itod and the fs_q?mask fields, and BIG* */ #ifdef TAR_INODE_NETBSD #include #include #include #define rawi_direct direct #define itoo(sb,i) ino_to_fsbo(sb,i) #define itod(sb,i) ino_to_fsba(sb,i) #undef SBFIXUP #undef SBBUFSIZE #undef SBBUFALIGN #define SBFIXUP(sb) do{ \ (sb)->fs_qbmask=~(quad_t)(sb)->fs_bmask; \ (sb)->fs_qfmask=~(quad_t)(sb)->fs_fmask; \ }while(0) #define SBBUFSIZE (SBSIZE+8) #define SBBUFALIGN(ptr) ((char *)((((unsigned long int)(ptr))+7)&~7)) #undef BIGFMT #undef BIGTYPE #undef BIGUFMT #undef BIGUTYPE #define BIGFMT "%qd" #define BIGTYPE int64_t #define BIGUFMT "%qu" #define BIGUTYPE u_int64_t #endif /* OSF/1 on the Alpha: nothing particularly noteworthy. */ #ifdef TAR_INODE_ALPHA_OSF #include #include #include #define rawi_direct dirent #endif /* Ultrix: [amc]time fields are struct timevals, not longs */ /* Also, they use di_gennum instead of di_gen. */ #ifdef TAR_INODE_ULTRIX #include #include #include #define rawi_direct direct #define di_gen di_gennum #undef tar_di_atime #undef tar_di_mtime #undef tar_di_ctime #define tar_di_atime di_atime.tv_sec #define tar_di_mtime di_mtime.tv_sec #define tar_di_ctime di_ctime.tv_sec #endif /* Internal: provide our own version. You may need to fix the [US]{16,32} typedefs if you're porting. */ #ifdef TAR_INODE_INTERNAL /* U = unsigned, S = signed; number is # of bits */ #undef btodb typedef unsigned short int U16; typedef signed short int S16; typedef unsigned long int U32; typedef signed long int S32; #undef daddr_t #define daddr_t U32 #undef DEV_BSIZE #define DEV_BSIZE 512 #define SBLOCK 16 #define SBSIZE 8192 #define MAXBSIZE 8192 #define FS_MAGIC 0x00011954 #define FS_MAGIC_SW 0x54190100 #define NDADDR 12 #define NIADDR 3 #define ROOTINO 2 #define fsbtodb(sb,b) ((b) << (sb)->fs_fsbtodb) #define dbtofsb(sb,b) ((b) >> (sb)->fs_fsbtodb) #define btodb(n) ((n) >> 9) #define NINDIR(sb) ((sb)->fs_nindir) #define INOPB(sb) ((sb)->fs_inopb) #define fragroundup(sb,n) round_up(n,(sb)->fs_fsize) #define itoo(sb,i) ((i) % INOPB(sb)) #define itog(sb,i) ((i) / (sb)->fs_ipg) #define itod(sb,i) ((daddr_t)(cgimin(sb,itog(sb,i))+((((i)%(sb)->fs_ipg)/INOPB(sb))<<(sb)->fs_fragshift))) #define cgbase(sb,c) ((daddr_t)((sb)->fs_fpg*(c))) #define cgstart(sb,c) (cgbase(sb,c)+((sb)->fs_cgoffset*((c)&~(sb)->fs_cgmask))) #define cgimin(sb,c) (cgstart(sb,c)+(sb)->fs_iblkno) #define blkoff(sb,x) ((x) & ~(sb)->fs_bmask) #define lblkno(sb,x) ((x) >> (sb)->fs_bshift) #define dblksize(sb,di,bno) \ ( ( ((bno) >= NDADDR) || \ ((di)->di_size >= ((bno)+1)<<(sb)->fs_bshift) ) \ ? (sb)->fs_bsize \ : fragroundup((sb),blkoff((sb),(di)->di_size)) ) #define fragnum(sb,x) ((x) & ((sb)->fs_frag-1)) /* beware, adding more non-padding fields to this (or more precisely, changing some of the padding to real fields) means updating sb__swap_bytes! */ struct fs { char pad1[16]; S32 fs_iblkno; char pad2[4]; S32 fs_cgoffset; S32 fs_cgmask; char pad3[4]; S32 fs_size; char pad4[4]; S32 fs_ncg; S32 fs_bsize; S32 fs_fsize; S32 fs_frag; char pad5[12]; S32 fs_bmask; char pad6[4]; S32 fs_bshift; char pad7[12]; S32 fs_fragshift; S32 fs_fsbtodb; char pad8[12]; S32 fs_nindir; S32 fs_inopb; char pad9[60]; S32 fs_ipg; S32 fs_fpg; char pad10[1180]; U32 fs_magic; } ; struct dinode { union { struct { U16 mode; #define di_mode u.s.mode S16 nlink; #define di_nlink u.s.nlink U16 uid; #define di_uid u.s.uid U16 gid; #define di_gid u.s.gid U32 size_high; U32 size; #define di_size u.s.size U32 atime; #define di_atime u.s.atime char pad1[4]; U32 mtime; #define di_mtime u.s.mtime char pad2[4]; U32 ctime; #define di_ctime u.s.ctime char pad3[4]; U32 db[NDADDR]; #define di_db u.s.db U32 ib[NIADDR]; #define di_ib u.s.ib } s; char pad[128]; } u; } ; #define di_rdev di_db[0] struct rawi_direct { U32 d_ino; U16 d_reclen; U16 d_namlen; char d_name[256]; } ; #endif /* If NO_FASTLINK, make isfastlink() a trivial version */ #ifdef NO_FASTLINK #undef isfastlink #define isfastlink(di) 0 #endif /* Include files for the various TAR_MOUNT_* variants */ #ifdef TAR_MOUNT_GETMNTENT #include #endif #ifdef TAR_MOUNT_GETFSSTAT #include #endif #ifdef TAR_MOUNT_GETMNT #include #endif /* Implement DIRENT_FIXUP */ #ifdef DIRENT_FIXUP #define direct dirent #endif /* We'd like to use howmany() and roundup() from system include files, but they may not be present or functional everywhere. */ #define how_many(amount,unit) (((amount)+(unit)-1)/(unit)) #define round_up(amount,unit) (how_many((amount),(unit))*(unit)) #ifdef DECLARE_ERRNO extern int errno; #endif /* Common malloc idioms */ #define NEW(t) ((t *)malloc(sizeof(t))) #define OLD(x) free(x) #ifdef NO_ARGVEC #error NO_ARGVEC is gone, but check out NO_PROGNAME #endif extern const char *__progname; #ifdef NO_PROGNAME const char *__progname; int main(int, char **); int main_(int, char **); int main(int ac, char **av) { __progname = av[0]; return(main_(ac,av)); } #define main main_ #endif #ifdef NO_STRNCASECMP static int strncasecmp(const char *s1, const char *s2, int n) { unsigned char c1; unsigned char c2; for (;n>0;n--,s1++,s2++) { c1 = tolower(*(const unsigned char *)s1); c2 = tolower(*(const unsigned char *)s2); if (c1 != c2) return(c1-c2); if (! c1) return(0); } return(0); } #endif /* This is to ^ as && is to &, or || is to |...except for the short-circuit semantics, which of course are not possible for an xor operation. */ #define XOR(a,b) ((a)?!(b):(b)) /* Implement NO_FIFO: ignore S_IFIFO if it's defined. */ #ifdef NO_FIFO #undef S_IFIFO #endif /* We use CRCs for hashing; this is the polynomial. */ #define CRC_POLY 0xedb88320 /* TBLOCK is a fundamental constant of the tar archive format. It's the unit tapes are read or written in. NBLOCK is not used for much; it's the blocking factor used when the B flag is enabled. MAXBLOCK is the maximum blocking factor available. NAMSIZ is a fundamental constant of the archive format. It's the size of the space reserved in the header for names. TULEN and TGLEN are the space reseved for the user and group names in archives with the "ustar " magic string. */ #define TBLOCK 512 #define NBLOCK 20 #define MAXBLOCK 2048 /* must be at least twice NBLOCK */ #define NAMSIZ 100 #define TULEN 32 #define TGLEN 32 /* Typedefs for internal structures. HEADER is an archive member header block. TOCENTRY is used to represent a table-of-contents entry. This holds much the same information as a HEADER, but it holds it in different form; for example, long headers are expanded. LINK is the hash-table entry used to keep track of multiply-linked files. EXTDESC is used to map file extents, the portions of a file that aren't holes or potential holes. STRCHAIN is just a linked list of strings; this is used, for example, for argument strings to the E flag and for filenames that must be retried when using i and the later= option to E. STRINTCHAIN is a linked list of pairs; this is used, for example, for arguments to the K flag. MTABENT represents one entry in the mount table. DISKDEV represents a raw disk device we might read files from. */ typedef struct header HEADER; typedef struct tocentry TOCENTRY; typedef struct link LINK; typedef struct extdesc EXTDESC; typedef struct strchain STRCHAIN; typedef struct strintchain STRINTCHAIN; #ifndef TAR_MOUNT_NONE typedef struct mtabent MTABENT; #endif #ifndef TAR_INODE_NONE typedef struct diskdev DISKDEV; #endif /* * An archive header block. The implementation-specific header block * formats LF_MOUSE_LONG, LF_MOUSE_SPARSE, LF_MOUSE_KILL, and * LF_MOUSE_IDLE are documented here. * * LONG is used to handle names or link-to strings longer than NAMSIZ, * by pushing the actual string(s) into the following TBLOCK(s) in the * archive. linkflag gets set to LF_MOUSE_LONG; the real linkflag is * moved to the first byte of name[]. The second byte of name[] is * '1' or '2' according as the header needs 1 or 2 names; the name * length(s) follow(s) in format "%o" or "%o %o" as appropriate, * name[] first and then, if applicable, linkname[]. The names are * concatenated, if necessary, and then stored in the minimum number * of TBLOCK-size blocks possible for the resulting number of bytes; * these blocks appear immediately after the "real" header (the one * with LF_MOUSE_LONG in its linkflag). Note that for two-name * headers (LINK and SYMLINK), even if one name would fit in its * field, if the other won't, both names get pushed out into followup * blocks. * * SPARSE is used to store sparse files relatively compactly. A SPARSE * header is used only for plain files. The linkname[] array holds * two numbers: the number of segments used for the file and the * number of TBLOCK-size blocks of data needed for those segments; * they are stored in that order in format "%o %o". Following this * header are the segment descriptors, then the data blocks. The * segment descriptors occupy 32 bytes each; they are packed into * TBLOCK-size blocks, with the last block padded if necessary to a * TBLOCK boundary. Each segment descriptor describes one non-hole * fragment of the file; it consists of the starting byte offset and * length of the segment, in that order, in format "%015o %015o" (the * \0 terminator makes up the 32nd byte). These descriptors must * appear in order of increasing offset; all but the last one * must/will be a multiple of TBLOCK in length. The data blocks * follow the last TBLOCK of descriptors; the segments appear in the * same order in the data portion as they do in the segment * descriptors. * * KILL is used to support the k keyletter. When tar sees a KILL * header, it saves the name string for later; when it later sees * another header with the same name[], it silently discards the later * archive entry in toto (as well as the saved name). See the manpage * description of the k flag for more. * * IDLE headers are used to pad an output archive for the align key; * they are silently ignored very early on read. */ struct header { char name[NAMSIZ]; /* name of archive entry */ char mode[8]; /* permission bits */ char uid[8]; /* owning uid */ char gid[8]; /* owning gid */ char size[12]; /* size */ char mtime[12]; /* modification timestamp */ char chksum[8]; /* header checksum */ char linkflag; /* what sort of entry is this? */ /* We don't actually implement anything special for LF_CONTIG, but we do keep track of it, and will pass it through when using P */ #define LF_OLDNORMAL '\0' /* plain file, old version */ #define LF_NORMAL '0' /* plain file, modern version */ #define LF_LINK '1' /* hard link to previously-archived file */ #define LF_SYMLINK '2' /* symlink */ #define LF_CHR '3' /* character special device */ #define LF_BLK '4' /* block special device */ #define LF_DIR '5' /* directory */ #define LF_FIFO '6' /* FIFO (named pipe) */ #define LF_CONTIG '7' /* contiguous file */ /* Private flags. See comment block at the beginning of this struct. Arguably _SPARSE should be 'S', but when these were first chosen, GNU tar had taken that, and we wanted to avoid conflicts. It now seems that they've also taken L, and for compatability we can't switch away from L, so attempts to avoid conflicts with GNU tar have been dropped. */ #define LF_MOUSE_LONG 'L' #define LF_MOUSE_SPARSE 'P' #define LF_MOUSE_KILL 'K' #define LF_MOUSE_IDLE 'I' /* GNU letter flags, for convert_fromgnu and convert_tognu support. Unfortunately I have been able to find no documentation on the GNU tar private header formats except the GNU tar source, so I'm not sure I've got them right. If you have reason to think I haven't, please tell me! */ #define LF_GNU_DUMPDIR 'D' #define LF_GNU_LONGLINK 'K' #define LF_GNU_LONGNAME 'L' #define LF_GNU_MULTIVOL 'M' #define LF_GNU_NAMES 'N' #define LF_GNU_SPARSE 'S' #define LF_GNU_VOLHDR 'V' char linkname[NAMSIZ]; /* link-to name, for LF_LINK and LF_SYMLINK */ char magic[8]; /* "ustar \0" if uname/gname valid */ char uname[TULEN]; /* user name */ char gname[TGLEN]; /* group name */ char devmajor[8]; /* major device number for LF_BLK/LF_CHR */ char devminor[8]; /* minor device number for LF_BLK/LF_CHR */ /* gnu* fields are used only for GNU tar compatability */ char gnupad[12+12+12+4+1]; /* here just to position following fields */ char gnusparse[12*2*4]; char gnuext; char gnurealsize[12]; } ; /* This is deduced from observation; I've never seen it specced anywhere. */ char tmagic[/*8*/] = "ustar "; /* Flags that indicate that we're reading or writing a GNU-style archive, not one of our own. */ static int gnu_read; static int gnu_write; /* Much like a HEADER, except that long headers are unpacked, and some numbers of internal use are computed and stored (eg, tblocks, mapsize). Fields mostly correspond to similarly-named fields in a HEADER. Exceptions: xname corresponds to name[] in the header; tblocks is computed based on various header fields (it's the number of TBLOCK-size blocks occupied by the archive member, not counting the header or long-header extension blocks); mapsize is used only for SPARSE headers, and contains the number of segment descriptors. */ struct tocentry { char *xname; int mode; int uid; int gid; int size; int tblocks; time_t mtime; int type; #define TYPE_NORMAL 1 #define TYPE_DIRECTORY 2 #define TYPE_HARDLINK 3 #define TYPE_SOFTLINK 4 #define TYPE_CSPECIAL 5 #define TYPE_BSPECIAL 6 #define TYPE_FIFO 7 #define TYPE_SPARSE 8 #define TYPE_KILL 9 #define TYPE_CONTIG 10 #define TYPE_SKIP 11 char *xlinkname; char devmajor[8+1]; char devminor[8+1]; int mapsize; } ; /* When we see a multiply-linked file, we add one of these to the hash table so as to recognize it as a link when we see it again. count holds the number of links we haven't yet found. One could argue we should remove the entry from the hash table when we find the last link; we presently don't do so. */ struct link { ino_t inum; dev_t devnum; int count; char *xpathname; } ; /* These next three structures are self-explanatory, given the functional descriptions above the typedefs earlier. */ struct extdesc { EXTDESC *link; int start; int length; } ; struct strchain { STRCHAIN *link; char *s; } ; struct strintchain { STRINTCHAIN *link; char *s; int i; } ; /* A mount table entry. Used to search for the disk device to read a file from, when using i or i-full. Note the DISKDEV pointer. */ #ifndef TAR_MOUNT_NONE struct mtabent { MTABENT *link; dev_t dev; DISKDEV *disk; } ; #endif /* A disk device. name is used for messages. fd is the fd we have the raw device open on. sbbuf holds the superblock; sb points to it. bsifsb is just a cached copy of dbtofsb(...->sb,btodb(...->sb->fs_bsize)). If we're using TAR_INODE_INTERNAL, we have fixdi() to deal with filesystems of the other byte sex. (This is perhaps overkill...) */ #ifndef TAR_INODE_NONE struct diskdev { DISKDEV *link; const char *name; int fd; char sbbuf[SBBUFSIZE]; struct fs *sb; int bsifsb; #ifdef TAR_INODE_INTERNAL void (*fixdi)(struct dinode *); #endif } ; #endif /* This mess is because we want GNU-style attributes, for the improved compile-time checking they allow, but we don't want to render tar uncompilable by other compilers. So we check version numbers (unless of course overridden by compile-time defines). We ultimately define an attribute() macro that's either nothing or __attribute__(). */ #if defined(NO_GNUC_ATTRIBUTES) #define GNUC_ATTRIBUTES 0 #endif #if !defined(GNUC_ATTRIBUTES) && defined(USE_GNUC_ATTRIBUTES) #define GNUC_ATTRIBUTES 1 #endif #if !defined(GNUC_ATTRIBUTES) #if defined(__GNUC__) && \ ( (__GNUC__ > 2) || \ ( (__GNUC__ == 2) && \ defined(__GNUC_MINOR__) && \ (__GNUC_MINOR__ >= 5) ) ) #define GNUC_ATTRIBUTES 1 #else #define GNUC_ATTRIBUTES 0 #endif #endif #if GNUC_ATTRIBUTES #define attribute(x) __attribute__(x) #if defined(__GNUC__) && \ ( (__GNUC__ > 2) || \ ( (__GNUC__ == 2) && \ defined(__GNUC_MINOR__) && \ (__GNUC_MINOR__ >= 7) ) ) #define UNUSED(x) x attribute((__unused__)) #endif #else #define attribute(x) /*nil*/ #endif #undef GNUC_ATTRIBUTES #ifndef UNUSED #define UNUSED(x) x #endif /* Compile-time defines, for the config long keyletter. */ static const char *config[] = { #ifdef NEED_UNISTD_H "NEED_UNISTD_H", #else "!NEED_UNISTD_H", #endif #ifdef NEED_SYSCALLS_H "NEED_SYSCALLS_H", #else "!NEED_SYSCALLS_H", #endif #ifdef TAR_MAP_SUN "TAR_MAP_SUN", #ifdef _MAP_NEW "_MAP_NEW", #else "!_MAP_NEW", #endif #else "!TAR_MAP_SUN", #endif #ifdef TAR_MAP_NEXT "TAR_MAP_NEXT", #else "!TAR_MAP_NEXT", #endif #ifdef TAR_MAP_NETBSD "TAR_MAP_NETBSD", #else "!TAR_MAP_NETBSD", #endif #ifdef TAR_MAP_OSF1 "TAR_MAP_OSF1", #else "!TAR_MAP_OSF1", #endif #ifdef TAR_MAP_NONE "TAR_MAP_NONE", #else "!TAR_MAP_NONE", #endif #ifdef TAR_INODE_SUN "TAR_INODE_SUN", #else "!TAR_INODE_SUN", #endif #ifdef TAR_INODE_NEXT "TAR_INODE_NEXT", #else "!TAR_INODE_NEXT", #endif #ifdef TAR_INODE_NETBSD "TAR_INODE_NETBSD", #else "!TAR_INODE_NETBSD", #endif #ifdef TAR_INODE_ALPHA_OSF "TAR_INODE_ALPHA_OSF", #else "!TAR_INODE_ALPHA_OSF", #endif #ifdef TAR_INODE_ULTRIX "TAR_INODE_ULTRIX", #else "!TAR_INODE_ULTRIX", #endif #ifdef TAR_INODE_INTERNAL "TAR_INODE_INTERNAL", #else "!TAR_INODE_INTERNAL", #endif #ifdef TAR_INODE_NONE "TAR_INODE_NONE", #else "!TAR_INODE_NONE", #endif #ifdef TAR_MOUNT_GETMNTENT "TAR_MOUNT_GETMNTENT", #else "!TAR_MOUNT_GETMNTENT", #endif #ifdef TAR_MOUNT_GETFSSTAT "TAR_MOUNT_GETFSSTAT", #else "!TAR_MOUNT_GETFSSTAT", #endif #ifdef TAR_MOUNT_GETMNT "TAR_MOUNT_GETMNT", #else "!TAR_MOUNT_GETMNT", #endif #ifdef TAR_MOUNT_NONE "TAR_MOUNT_NONE", #else "!TAR_MOUNT_NONE", #endif #ifdef NO_FASTLINK "NO_FASTLINK", #else "!NO_FASTLINK", #endif #ifdef DIRENT_FIXUP "DIRENT_FIXUP", #else "!DIRENT_FIXUP", #endif #ifdef NO_PROGNAME "NO_PROGNAME", #else "!NO_PROGNAME", #endif #ifdef NO_FIFO "NO_FIFO", #else "!NO_FIFO", #endif #ifdef CONCAT_REISER "CONCAT_REISER", #else "!CONCAT_REISER", #endif #ifdef CONCAT_ANSI "CONCAT_ANSI", #else "!CONCAT_ANSI", #endif #ifdef CONCAT "supplied CONCAT", #else "no supplied CONCAT", #endif #ifdef USE_RMT "USE_RMT", #else "!USE_RMT", #endif #ifdef NO_GNUC_ATTRIBUTES "NO_GNUC_ATTRIBUTES", #else "!NO_GNUC_ATTRIBUTES", #endif #ifdef USE_GNUC_ATTRIBUTES "USE_GNUC_ATTRIBUTES", #else "!USE_GNUC_ATTRIBUTES", #endif #ifdef NO_STRERROR "NO_STRERROR", #else "!NO_STRERROR", #endif #ifdef NO_STRNCASECMP "NO_STRNCASECMP", #else "!NO_STRNCASECMP", #endif #ifdef NO_DEVICES "NO_DEVICES", #else "!NO_DEVICES", #endif #ifdef NO_SYNTHETIC "NO_SYNTHETIC", #else "!NO_SYNTHETIC", #endif 0, }; /* We want a large integer type to store byte counts in. We could use BIGTYPE from above, but that's really for the inode stuff. We could also use long int, but that falls over at 32 bits, or long long int, which isn't yet widely enough supported. So we do it ourselves. We don't use it for much and hence don't need high efficiency and lots of operations... We use an array of four unsigned shorts, and the support code is careful to avoid assuming that unsigned short is exactly 16 bits. We assume it is _at least_ 16 bits, and don't worry about the wastage if it's larger - these aren't used heavily. We typedef to a struct rather than an array so that the things can be passed to and received from functions by value. */ typedef struct bignum BIGNUM; struct bignum { unsigned short int limb[4]; /* [3] most significant, [0] least */ } ; static const BIGNUM bigzero = { { 0, 0, 0, 0 } }; /* BIGNUM_TEXTSIZE is a buffer length for buffers holding a BIGNUM converted to decimal text. 2^64 is 18446744073709551616, which is 20 digits long; plus a NUL and some padding/slop, 24. */ #define BIGNUM_TEXTSIZE 24 /* This BIGNUM holds the byte offset of the current block in the archive, for error messages. */ static BIGNUM archive_offset; /* The hash table of hardlinked files. */ static char *linkht; /* Boolean and simple integer flags. */ /* Available one-letter flags: GHJNQYZagnqwyz */ static int Aflag = 0; /* suppress killing */ static int Cflag = 0; /* comparison mode */ static int Fflag = 0; /* allow certain header format violations */ static int Lflag = 0; /* don't do private headers for too-long names */ static int Mflag = 0; /* process multiple canisters */ static int Sflag = 0; /* sparse file handling */ static int Wflag = 0; /* wildcards handled in arglist */ static int Xflag = 0; /* reverse arglist default */ static int cflag = 0; /* write files to archive */ static int dflag = 0; /* device restriction mode */ static int eflag = 0; /* stat() again to see if files change while dumping */ static int hflag = 0; /* follow symlinks */ static int jflag = 0; /* evil-archive paranoia on extract */ static int kflag = 0; /* make a kill archive */ static int lflag = 0; /* complain if unable to resolve all hard links */ static int mflag = 0; /* don't utimes() when extracting */ static int oflag = 0; /* don't put directory info in archive */ static int pflag = 0; /* restore all mode bits, as far as possible */ static int tflag = 0; /* give table-of-contents of tape */ static int vflag = 0; /* verbose */ static int xflag = 0; /* extract */ /* Long-named simple flags */ static int sortflag = 0; /* sort when reading directories */ static int unlinkflag = 0; /* unlink things when added */ /* B, as it applies to input and output */ static int Bflag_i = 0; static int Bflag_o = 0; #define BF_AUTO 0x01 #define BF_KEY 0x02 /* For checking to see if you're overloading stdin or stdout */ static const char *stdin_busy = 0; static const char *stdout_busy = 0; /* For O-style extraction */ static const char *Odest = 0; static int Ofd; /* For P-style operation: where to send the output */ static const char *Pdest = 0; /* List of I input sources */ static STRINTCHAIN *Isource = 0; /* List of K flags */ static STRINTCHAIN *Kflag = 0; #define K_ACTION_DEFAULT '@' #define K_ACTION_NONE '=' #define K_ACTION_START '+' #define K_ACTION_STOP '-' /* True iff the current K state is such that we should not skip members */ static int Kon; /* A bunch of stuff for reading stuff from raw disk devices. */ #ifndef TAR_INODE_NONE static DISKDEV *Udisk = 0; /* DISKDEV to use if U given */ #define I_NONE 0 /* normal filesystem operation */ #define I_FULL 1 /* i-full style: everything via disk */ #ifndef NO_DEVICES #define I_FILE 2 /* i-file style: files via disk but others via filesystem */ #endif static int iflag = I_NONE; /* default mode */ static int i_fs; /* current filesystem handle */ static int fs_cwd; /* current directory for i-full mode */ static int rflag = 0; /* r value: number of rereads of inode */ static int uflag = 0; /* iff u flag given: use raw disk */ static const char *diskfn; /* name of disk currently in use */ static DISKDEV *disk; /* DISKDEV of disk currently in use */ static DISKDEV *alldisks = 0; /* Linked list of all DISKDEVs */ static volatile int rvalue; /* Number of times we've retried */ static jmp_buf rjmp; /* longjmp() here on filesystem error */ static char inobuf[MAXBSIZE]; /* Holds current inode */ static struct dinode *di; /* Pointer to current inode */ static char fileblk[MAXBSIZE]; /* Holds block of file */ static char indirblk1[MAXBSIZE];/* Holds first-level indirect block */ static char indirblk2[MAXBSIZE];/* Holds second-level indirect block */ static char indirblk3[MAXBSIZE];/* Holds third-level indirect block */ /* indirblks[] just selects one of indirblk{1,2,3} based on an int */ static char *indirblks[3] = { &indirblk1[0], &indirblk2[0], &indirblk3[0] }; /* Indirect blocks as arrays of daddr_t */ static daddr_t *dbvecs[3] = { (daddr_t *) &indirblk1[0], (daddr_t *) &indirblk2[0], (daddr_t *) &indirblk3[0] }; /* Block numbers currently in indirblk* buffers */ static int curindirs[3] = { -1, -1, -1 }; #endif #ifndef TAR_MOUNT_NONE static MTABENT *mounts; /* Linked list of mount points */ #endif /* Stuff supporting the E keyletter. */ static STRCHAIN *E_options = 0; /* List of options */ static int E_failed; /* Flag, set if attempt failed */ static int E_failhard; /* Flag, set if failure can't be retried */ static int E_retry_now = 0; /* Number of times to retry immediately */ static int E_retry_later = 0; /* Number of times to retry later */ static int E_listfd = -1; /* fd to send failure messages to */ static int E_appfile = /* Should we fake an archive member? */ #define E_AF_NONE 0 /* No */ #define E_AF_EMPTY 1 /* Yes, an empty one */ #define E_AF_MSGFILE 2 /* Yes, one containing a message */ E_AF_NONE; /* Default */ static FILE *E_logf; /* Where to send E errors */ static STRCHAIN *mustretry = 0; /* Things we want to retry later */ /* Support for the k keyletter. */ static STRCHAIN *kill_list = 0; /* List of saved-up kill entries */ static int killed; /* True if current entry was killed */ /* * Support for debugging output. The `debugging' variable is a * bitmask; the bits are the DEBUG_* defines. debugf, if non-nil, is * where to send the output. The debugkeys[] array is used to look up * keywords passed to the D key. */ static unsigned long int debugging = 0; static FILE *debugf; #define DEBUG_ARGS 0x00000001 #define DEBUG_WILDCARD 0x00000002 #define DEBUG_ARGLIST 0x00000004 #define DEBUG_DIRSTACK 0x00000008 #define DEBUG_MODETIME 0x00000010 #define DEBUG_INPUT 0x00000020 #define DEBUG_SINCE 0x00000040 #define DEBUG_SPARSE 0x00000080 #define DEBUG_DISKS 0x00000100 #define DEBUG_ERRORS 0x00000200 #define DEBUG_KFILTER 0x00000400 #define DEBUG_KILL 0x00000800 #define DEBUG_DNLC 0x00001000 #define DEBUG_FTRUNCATE 0x00002000 #define DEBUG_J 0x00004000 #define DEBUG_ALL 0x00007fff static struct { const char *name; unsigned long int value; } debugkeys[] = { { "all", DEBUG_ALL }, { "args", DEBUG_ARGS }, { "wildcard", DEBUG_WILDCARD }, { "arglist", DEBUG_ARGLIST }, { "dirstack", DEBUG_DIRSTACK }, { "modetime", DEBUG_MODETIME }, { "input", DEBUG_INPUT }, { "since", DEBUG_SINCE }, { "sparse", DEBUG_SPARSE }, { "disks", DEBUG_DISKS }, { "errors", DEBUG_ERRORS }, { "kfilter", DEBUG_KFILTER }, { "kill", DEBUG_KILL }, { "dnlc", DEBUG_DNLC }, { "ftruncate", DEBUG_FTRUNCATE }, { "j", DEBUG_J }, { 0, 0 } }; /* * Determine how to do token concatenation. This tries the * empty-comment trick to see if it works, and if not goes to using * ##. You can define CONCAT_REISER or CONCAT_ANSI to force its * choice; if necessary, you can figure out something that works * (based on the Reiser and ANSI samples) and define DEBUG to override * all. If necessary, you can define DEBUG as 0, and you will lose * all debugging output, but it should let you get tar to compile. * This code works on real Reiser and ANSI preprocessors; at least one * other preprocessor is known to get upset over it. Sigh. */ #if !defined(CONCAT_REISER) && !defined(CONCAT_ANSI) && !defined(DEBUG) #define FOOA 1+ #define FOOB 1 #define FOOAFOOB 100 #define CONCAT(a,b) a/**/b #if (CONCAT(FOOA,FOOB)) > 10 /* either FOOA FOOB = 1+1 or FOOAFOOB = 100 */ #define CONCAT_REISER #else #define CONCAT_ANSI #endif #undef FOOA #undef FOOB #undef FOOAFOOB #endif #if defined(CONCAT_REISER) && !defined(DEBUG) #define DEBUG(what) (debugging & (DEBUG_/**/what)) #endif #if defined(CONCAT_ANSI) && !defined(DEBUG) #define DEBUG(what) (debugging & DEBUG_##what) #endif #ifndef DEBUG #error Fix this for your system! #endif static int imt; /* Input archive file descriptor. */ static int omt; /* Output archive file descriptor. */ static int ismt; /* Archive is connected to a real tape device */ #ifdef USE_RMT static int isrmt; /* Tape device is rmt-remote */ #endif static int isfile; /* Is a plain file (ie, is seekable) */ static int bfactor; /* Blocking factor (TBLOCKs per read/write) */ static int setbfactor; /* bfactor explicitly set by arglist */ static int firsti; /* Haven't done first read() yet */ static int firsto; /* Haven't done first write() yet */ static int align_output;/* Set if align keyword used */ static int align_room; /* Number of blocks left before alignment mark */ /* Input and output archive names, for printing */ static char *imtn = 0; static char *omtn = 0; /* Return value from goodheader(). */ enum goodh_rv { GH_NIL = 0, /* not used - error catcher */ GH_GOOD, /* header is fine */ GH_NOGOOD, /* header is bad */ GH_LONG, /* header is a LONG header */ GH_IDLE, /* header is an IDLE header */ GH_EOF /* header is an end-of-archive header */ } ; typedef enum goodh_rv GOODH_RV; /* vfp/vfn are for table-of-contents messages; ifp/ifn are for information. These correspond to the V-toc and V-info keywords. */ static FILE *vfp; static char *vfn = 0; static FILE *ifp; static char ifn_magic; #define COPY_FROM_VFP (&ifn_magic) static char *ifn = COPY_FROM_VFP; /* tapenames is a list of f keyletter arguments. */ static STRCHAIN *tapenames = 0; /* Handling the s keyletter. */ static char *since = 0; static time_t sincetime; /* Handling the R keyletter. */ static char *rootname = 0; static int rootnamelen; static int isrooted; /* Input and output tape buffers. */ static char itapebuf[(MAXBLOCK*TBLOCK)+1]; static char otapebuf[MAXBLOCK*TBLOCK]; /* Fill point for itapebuf, when doing B-style input. */ static int itbfill; /* Pointers into itapebuf and otapebuf. */ static int curiblk; static int curoblk; /* Byte offset of the beginning of itapebuf in the archive. */ static BIGNUM itboff; /* Byte offset of the most-recently-returned block in the archive. */ static BIGNUM archive_offset; /* How this system's ftruncate() behaves - see x_aux_e() for more. */ #define FTRUNC_UNKNOWN 1 #define FTRUNC_CAN_GROW 2 #define FTRUNC_SHRINK_ONLY 3 #define FTRUNC_PARANOID 4 static int ftruncate_behavior = FTRUNC_UNKNOWN; static int interested; /* True iff we're interested in this member */ static int oldumask; /* umask() when we started up */ static int filen; /* Number of data blocks left in member */ static TOCENTRY xtoce; /* Current entry's TOCENTRY */ static int xphase; /* When doing a sparse extract... */ #define XPH_MAP 1 /* ...extracting segment blocks */ #define XPH_DATA 2 /* ...extracting data blocks */ static EXTDESC *xmap = 0; /* Segment map of file */ static EXTDESC **xmaptail; /* tconc tail pointer for xmap */ static int xsizeleft; /* Size of file yet to extract */ static int xzblocks; /* Number of all-NUL blocks seen (for S) */ static int desparsify_P; /* Should we fill out sparse files under P */ static int des_left; /* Data-left value for desparsify_P */ static int ffd; /* fd on file being dumped/extracted */ static jmp_buf xabort; /* Jump here to abort extract */ static jmp_buf goodh_throw; /* For goodheader() */ static int goodh_complain; /* Should goodheader() complain? */ static char zblk[TBLOCK]; /* A TBLOCK of all-0 bytes */ static char ihblk[TBLOCK]; /* Input header block buffer */ #define iheader ((HEADER *)&ihblk[0]) /* ...as a HEADER */ static char ohblk[TBLOCK]; /* Output header block buffer */ #define oheader ((HEADER *)&ohblk[0]) /* ...as a HEADER */ static struct stat filestat; /* stat() result on a file of interest */ static dev_t d_device; /* Device to restrict to, for d keyletter */ static TOCENTRY tocent; /* TOCENTRY of entry being created */ /* Directory stack buffer - XXX should be dynamically allocated. */ static char dirss[65536]; /* Stack of directories to fix the mode bits on: */ static struct { char *end; /* End of name in dirss */ unsigned int info : 1; /* Do we have mode info for this dir? */ unsigned int made : 1; /* Did we create this dir? */ int setit; /* Set this dir's mode? */ #define DONT 0 /* No */ #define ROOTED 1 /* Yes - but use R value */ #define NORMAL 2 /* Yes */ int mode; /* Bits to set it to */ time_t mtime; /* Modify time to set */ int uid; /* uid to chown it to */ int gid; /* gid to chown it to */ } modes[32768]; /* at most one per two chars in dirss */ static int dirsp; /* How full dirss is */ /* Buffer to hold prefix for C messages - XXX should be dynamic. */ static char Cbuf[65536]; /* ARG is an argument filename; ARGFLAG records flags attached (eg, -H). */ typedef struct arg ARG; typedef struct argflag ARGFLAG; /* If you add something to here, make sure you update: af_default() scanargs() */ struct argflag { unsigned int how : 2; /* -N, -H, -T, -F */ #define HOW_NORM 0 #define HOW_HEAD 1 #define HOW_TAIL 2 #define HOW_FULL 3 unsigned int I : 1; /* -I */ unsigned int X : 1; /* -X */ unsigned int W : 1; /* -W */ unsigned int C : 1; /* -C */ unsigned int S : 1; /* -S */ unsigned int Q : 1; /* -Q */ } ; struct arg { char *str; ARGFLAG flags; } ; static ARG *args; /* Arguments from the command line */ static int nargs; /* Count of 'em */ /* If we're supposed to use librmt, declare its routines. */ #ifdef USE_RMT int rmt_open(const char *, const char *, int); int rmt_read(int, void *, int); int rmt_write(int, const void *, int); int rmt_ioctl(int, int, void *); #endif /* Return a malloc()ed copy of a string */ static char *copyofstr(const char *s) { char *t; t = malloc(strlen(s)+1); if (t) strcpy(t,s); return(t); } /* strncpy into malloc()ed storage */ static char *copynofstr(const char *s, int n) { char *t; t = malloc(n+1); if (t) { strncpy(t,s,n); t[n] = '\0'; } return(t); } /* * Hash-table library, imported inline to eliminate dependency on * external include files and libraries. The hash table uses chaining * for collision resolution, and resizes the table automatically when * it starts getting full. The client of this package is responsible * for providing hash and comparision functions. */ typedef struct htable HTABLE; /* A hash table */ typedef struct entry ENTRY; /* An entry in a hash table */ struct entry { ENTRY *link; /* Bucket chain */ char *data; /* Data pointer */ } ; struct htable { int entries; /* Current # of entries */ int maxentries; /* Max # entries before we grow */ int tblsize; /* Current table size */ int (*sizefxn)(int); /* Function to pick a new size when growing */ int (*hfxn)(void *, int); /* Function to hash an entry */ int (*cmpfxn)(void *, void *);/* Function to compare two entries */ ENTRY **table; /* The table itself */ } ; /* This is the only thing that ever goes in an HTABLE's sizefxn */ static int hsizefxn(int old) { if (old <= 0) { return(15); } return(old+old+1); } /* Return the bucket number of an entry in a table */ static int gethash(HTABLE *t, char *e) { unsigned int i; int s; s = t->tblsize; i = (*t->hfxn)(e,s); return((int)(i%s)); } /* Grow a table, rehashing all entries */ static void grow_table(HTABLE *t) { int newsize; int oldsize; register ENTRY **newtbl; register ENTRY *e; register ENTRY *e2; register int h; register int i; register ENTRY **oldtbl; oldsize = t->tblsize; oldtbl = t->table; newsize = (*t->sizefxn)(oldsize); if (newsize <= oldsize) newsize = hsizefxn(oldsize); newtbl = (ENTRY **) malloc(newsize*sizeof(ENTRY *)); t->tblsize = newsize; t->table = newtbl; t->maxentries = 2 * newsize; for (i=newsize-1;i>=0;i--) newtbl[i] = 0; for (i=oldsize-1;i>=0;i--) { for (e=oldtbl[i];e;e=e2) { e2 = e->link; h = gethash(t,e->data); e->link = newtbl[h]; newtbl[h] = e; } } free(oldtbl); } /* Create a new table, given hash and comparison functions. */ static void *new_htable(int (*hfxn)(void *, int), int (*cmpfxn)(void *, void *)) { HTABLE *h; h = NEW(HTABLE); h->entries = 0; h->maxentries = 0; h->tblsize = 0; h->sizefxn = hsizefxn; h->hfxn = hfxn; h->cmpfxn = cmpfxn; h->table = 0; grow_table(h); return(h); } /* Do bookkeeping for having just added a new entry to an HTABLE. */ static int new_entry(HTABLE *t) { t->entries ++; if (t->entries > t->maxentries) { grow_table(t); return(1); } else { return(0); } } /* Abbreviation for interface functions. They take Tbl as an opaque argument, but for convenience in the code, we de-opaquify it. */ #define tbl ((HTABLE *)Tbl) /* Find an entry matching this one, if any */ static void *find_hentry(void *Tbl, void *entry) { ENTRY *e; for (e=tbl->table[gethash(tbl,entry)];e;e=e->link) { if ((*tbl->cmpfxn)(e->data,entry) == 0) { return(e->data); } } return(0); } /* Add an entry, blindly assuming it's not already there */ static void add_new_hentry(void *Tbl, void *entry) { int h; register ENTRY *e; new_entry(tbl); h = gethash(tbl,entry); e = NEW(ENTRY); e->link = tbl->table[h]; tbl->table[h] = e; e->data = entry; } /* Call a function for each entry in a table */ static void map_htable(void *Tbl, void (*fxn)(void *)) { int i; ENTRY *e; for (i=tbl->tblsize-1;i>=0;i--) { for (e=tbl->table[i];e;e=e->link) { (*fxn)(e->data); } } } /* The full version has more functions, but tar doesn't use 'em */ #undef tbl /* If we were told to supply strerror(), do so. */ #ifdef NO_STRERROR static const char *strerror(int e) { extern const char *sys_errlist[]; extern int sys_nerr; static char bad[64]; if ((e < 0) || (e >= sys_nerr)) { sprintf(&bad[0],"Error %d",e); return(&bad[0]); } return(sys_errlist[e]); } #endif /* Compute a CRC, for the directory name lookup cache */ #ifndef TAR_INODE_NONE static unsigned long int crc(unsigned long int init, const void *buf, int nb) { const unsigned char *bp; static unsigned long int table[256]; unsigned long int t; if (table[128] == 0) { int i; int n; for (i=0;i<256;i++) { t = i; for (n=0;n<8;n++) t = (t & 1) ? ((t >> 1) ^ CRC_POLY) : (t >> 1); table[i] = t; } } t = init; for (bp=buf;nb>0;nb--,bp++) { t = table[(t^*bp)&0xff] ^ (t >> 8); } return(t); } #endif #ifndef TAR_INODE_NONE /* Like L_SET, L_INCR, L_XTND, for fd_lseek */ enum fs_whence { FS_SET, FS_INCR, FS_XTND }; /* The thing returned by fs_readdir() */ struct fs_direct { int d_ino; int d_namlen; char d_name[513]; } ; /* FS is an open filesystem. F is an open file on a filesystem. MAPSTATE is state for the code that creates a blocks-used map of a file (see fs_spacemap()). DNLC is a directory name lookup cache, DNLCENT is an entry in a DNLC. */ typedef struct openfs FS; typedef struct openf F; typedef struct mapstate MAPSTATE; typedef struct dnlc DNLC; typedef struct dnlcent DNLCENT; struct mapstate { long int (*map)[2]; /* [0] is start, [1] is size, both in bytes */ int nmap; /* # entries allocated to map */ int rv; /* # entries filled in map */ long int factor; /* size of each block, in bytes */ int start; /* first block of segment */ int last; /* last block of segment */ } ; struct openfs { int fsx; /* index in fsv[], below */ int fd; /* file descriptor to filesystem */ const char *dev; /* path to filesystem */ char sbbuf[SBBUFSIZE];/* superblock buffer */ struct fs *sb; /* sbbuf[] as a superblock struct */ int maxino; /* highest inode number */ char *bufs; /* block buffers */ int inoblk; /* number of block in inobuf */ char *inobuf; /* inode-holding block buffer */ DNLC *dnlc; /* directory name lookup cache */ int indirblk[NIADDR]; /* indirect block numbers */ char *indirbuf[NIADDR];/*indirect block buffers */ #ifdef TAR_INODE_INTERNAL void (*fixdi)(struct dinode *); /* fix a dinode */ void (*fixdirect)(struct rawi_direct *); /* fix a rawi_direct */ daddr_t (*fixblkno)(daddr_t); /* fix a block number */ char *inoswap; #endif F *of; /* files open on this filesystem */ } ; struct openf { F *flink; /* forward link of files open on filesystem */ F *blink; /* backward ditto */ FS *fs; /* filesystem this file is on */ int ofx; /* index in ofv[], below */ int ino; /* inode number */ struct dinode di; /* dinode for this inode */ struct fs_direct fsd; /* the fs_direct returned by fs_readdir */ off_t off; /* lseek pointer */ off_t bufoff; /* offset of beginning of buf */ int bufsiz; /* size of buf */ char *buf; /* data buffer */ } ; struct dnlc { int hashindex; /* index into dnlc_hashsizes[], below */ int hashsize; /* hash table size */ int capacity; /* cache capacity - start reclaiming at this point */ int fill; /* current fill count */ DNLCENT **hashvec; /* hash table vector */ DNLCENT **ageheap; /* heap of entries sorted by age */ FS *fs; /* filesystem this dnlc is for */ } ; struct dnlcent { int heapindex; /* index in the ageheap */ int hash; /* hash value */ int lastuse; /* last-use time */ int ino; /* inumber of directory */ int namelen; /* name length */ char *name; /* name string */ int toino; /* inumber name maps to */ } ; /* dnlc_hashsizes is reasonable hash table sizes for dnlcs */ static int dnlc_hashsizes[] = { 17, 31, 61, 127, 191, 283, 431, 647, 971, 1459, 0 }; /* current timestamp - incremented each time it's used */ static int dnlc_lastuse = 0; static int nfs = 0; /* number of open filesystems */ static FS **fsv = 0; /* vector of open filesystems */ static int nof = 0; /* number of open files */ static F **ofv = 0; /* vector of open files */ /* Error messages get built in malloced memory here */ static char *fs_errstr; /* Used in case we can't malloc for fs_errstr */ static char fs_mallfail[] = "(malloc failed)"; /* For vsprintf. Yes, very long strings in user input can overrun this buffer...once stdio is fixed so we can vsmallocprintf(), I will happily fix this. */ static char fs_errbuf[1024]; #endif #ifdef LINUX_NAMLEN /* Yeah, it's a little inefficient to call this every time we want the */ /* namlen. If you don't like it, either don't use Linux or beat on */ /* the Linux people to be a little more compatible. */ #define D_NAMLEN(d) (strlen(&(d)->d_name[0])) #else #define D_NAMLEN(d) ((d)->d_namlen) #endif #ifndef TAR_INODE_NONE static void set_errmsg(const char *fmt, ...) { va_list ap; va_start(ap,fmt); vsprintf(&fs_errbuf[0],fmt,ap); va_end(ap); free(fs_errstr); fs_errstr = malloc(strlen(&fs_errbuf[0])+1); if (fs_errstr) { strcpy(fs_errstr,&fs_errbuf[0]); } else { fs_errstr = &fs_mallfail[0]; } } #endif #ifndef TAR_INODE_NONE static void push_errmsg(const char *fmt, ...) { va_list ap; va_start(ap,fmt); vsprintf(&fs_errbuf[0],fmt,ap); va_end(ap); if (fs_errstr) { char *t; t = malloc(strlen(&fs_errbuf[0])+2+strlen(fs_errstr)+1); if (t) { sprintf(t,"%s: %s",&fs_errbuf[0],fs_errstr); free(fs_errstr); fs_errstr = t; } else { free(fs_errstr); fs_errstr = &fs_mallfail[0]; } } else { fs_errstr = malloc(strlen(&fs_errbuf[0])+1); if (fs_errstr) { strcpy(fs_errstr,&fs_errbuf[0]); } else { fs_errstr = &fs_mallfail[0]; } } } #endif /* convert filesystem vector index to FS * */ #ifndef TAR_INODE_NONE static FS *fsx_to_fs(int fsx) { return(((fsx < 0) || (fsx >= nfs)) ? 0 : fsv[fsx]); } #endif /* convert open file vector index to F * */ #ifndef TAR_INODE_NONE static F *ofx_to_f(int ofx) { return(((ofx < 0) || (ofx >= nof)) ? 0 : ofv[ofx]); } #endif /* close a filesystem - blindly assumes no files still open on it */ #ifndef TAR_INODE_NONE static void closefs(FS *fs) { close(fs->fd); free(fs->bufs); #ifdef TAR_INODE_INTERNAL free(fs->inoswap); #endif OLD(fs); } #endif /* seek to a given block offset on a given file descriptor */ /* we check blkno before using LB_SET even when it's defined because LB_SET doesn't work on plain files on at least one system, ugh! */ #ifndef TAR_INODE_NONE static int blkseek(int fd, unsigned long int blkno) { #ifdef LB_SET if (blkno >= (0x80000000UL>>9)) { return((lseek(fd,blkno,LB_SET)<0)?-1:0); } else #endif { return((lseek(fd,DEV_BSIZE*(off_t)blkno,L_SET)<0)?-1:0); } } #endif /* Read from disk for fs, at block blkoff, into buf, size nbytes */ #ifndef TAR_INODE_NONE static int readdisk(FS *fs, unsigned int blkoff, void *buf, int nbytes) { int rv; if (blkseek(fs->fd,blkoff) < 0) { set_errmsg(strerror(errno)); push_errmsg("disk seek error (blk %u)",blkoff); return(-1); } rv = read(fs->fd,buf,nbytes); if (rv < 0) { set_errmsg(strerror(errno)); push_errmsg("disk read error (blk %u)",blkoff); return(-1); } if (rv == nbytes) return(0); set_errmsg("short disk read (blk %u): wanted %d got %d",blkoff,nbytes,rv); return(-1); } #endif /* Get an inode off disk for the fs and return its dinode * */ #ifndef TAR_INODE_NONE static struct dinode *getino(FS *fs, int ino) { int ib; struct dinode *di; int iwb; if ((ino < 0) || (ino > fs->maxino)) { set_errmsg("bad inumber %d (max %d)",ino,fs->maxino); return(0); } ib = fsbtodb(fs->sb,itod(fs->sb,ino)); if (ib != fs->inoblk) { fs->inoblk = -1; if (readdisk(fs,ib,fs->inobuf,fs->sb->fs_bsize) < 0) { push_errmsg("can't read inode %d",ino); push_errmsg("corrupt filesystem"); push_errmsg("%s",fs->dev); return(0); } fs->inoblk = ib; #ifdef TAR_INODE_INTERNAL bzero(fs->inoswap,INOPB(fs->sb)); #endif } iwb = itoo(fs->sb,ino); di = ((struct dinode *)fs->inobuf) + iwb; #ifdef TAR_INODE_INTERNAL if (! fs->inoswap[iwb]) { (*fs->fixdi)(di); fs->inoswap[iwb] = 1; } #endif return(di); } #endif /* Copy from a dinode to a struct stat, for fs_stat() and friends */ #ifndef TAR_INODE_NONE static void load_stb(struct dinode *di, int ino, struct stat *stb) { stb->st_dev = 0; stb->st_ino = ino; stb->st_mode = di->di_mode; stb->st_nlink = di->di_nlink; stb->st_uid = di->di_uid; stb->st_gid = di->di_gid; stb->st_rdev = di->di_rdev; stb->st_size = di->di_size; stb->st_atime = di->tar_di_atime; stb->st_mtime = di->tar_di_mtime; stb->st_ctime = di->tar_di_ctime; } #endif /* Do a stat() operation, given inumber; return 0 on success or -1 on error */ #ifndef TAR_INODE_NONE static int stati(FS *fs, int ino, struct stat *stb) { struct dinode *di; di = getino(fs,ino); if (! di) { push_errmsg("stati"); return(-1); } load_stb(di,ino,stb); return(0); } #endif /* Open a file by inumber; return ofv index, or -1 on error */ #ifndef TAR_INODE_NONE static int openi(FS *fs, int ino) { F *f; int i; struct dinode *di; f = NEW(F); if (! f) { set_errmsg("malloc failed"); return(-1); } di = getino(fs,ino); if (! di) { push_errmsg("openi"); OLD(f); return(-1); } f->flink = fs->of; f->blink = 0; if (f->flink) f->flink->blink = f; fs->of = f; f->fs = fs; f->ino = ino; f->di = *di; f->off = 0; f->bufoff = 0; f->bufsiz = 0; f->buf = malloc(fs->sb->fs_bsize); if (f->buf == 0) { set_errmsg("malloc failed"); OLD(f); return(-1); } for (i=0;i= nof) { nof ++; ofv = ofv ? realloc(ofv,nof*sizeof(F *)) : malloc(nof*sizeof(F *)); } ofv[i] = f; f->ofx = i; return(i); } #endif /* Ensure that f->buf contains offset f->off */ #ifndef TAR_INODE_NONE static int get_off_in_buf(F *f) { FS *fs; struct fs *sb; int bo; int bno; int i; if ((f->off < f->bufoff) || (f->off >= f->bufoff+f->bufsiz)) { fs = f->fs; sb = fs->sb; f->bufoff = f->off - blkoff(sb,f->off); bo = lblkno(sb,f->bufoff); if (bo < NDADDR) { bno = f->di.di_db[bo]; f->bufsiz = dblksize(sb,&f->di,bo); } else { int bwi[NIADDR]; int lev; int bwf; f->bufsiz = sb->fs_bsize; bwf = bo - NDADDR; lev = NIADDR-1; while (1) { if (lev < 0) { set_errmsg("too much indirection needed"); push_errmsg("corrupt filesystem"); push_errmsg("%s",fs->dev); return(-1); } if (bwf < NINDIR(sb)) { bwi[lev] = bwf; break; } else { bwf -= NINDIR(sb); bwi[lev--] = bwf % NINDIR(sb); bwf /= NINDIR(sb); } } if (lev != 0) { for (i=0;levdi.di_ib[lev-1]; for (i=0;idev); return(-1); } if (fragnum(sb,bno)) { set_errmsg("misaligned indirect block"); push_errmsg("corrupt filesystem"); push_errmsg("%s",fs->dev); return(-1); } if (bno >= sb->fs_size) { set_errmsg("indirect block number out of range"); push_errmsg("corrupt filesystem"); push_errmsg("%s",fs->dev); return(-1); } bno = fsbtodb(sb,bno); if (bno != fs->indirblk[i]) { fs->indirblk[i] = -1; if (readdisk(fs,bno,fs->indirbuf[i],sb->fs_bsize) < 0) { push_errmsg("%s",fs->dev); return(-1); } fs->indirblk[i] = bno; } #ifdef TAR_INODE_INTERNAL bno = (*fs->fixblkno)(((daddr_t *)fs->indirbuf[i])[bwi[i]]); #else bno = ((daddr_t *)fs->indirbuf[i])[bwi[i]]; #endif } } if (bno < 0) { set_errmsg("negative block number"); push_errmsg("corrupt filesystem"); push_errmsg("%s",fs->dev); return(-1); } if (bno+dbtofsb(sb,btodb(f->bufsiz)) > sb->fs_size) { set_errmsg("reading beyond end of filesystem"); push_errmsg("corrupt filesystem"); push_errmsg("%s",fs->dev); return(-1); } if (bno) { if (readdisk(fs,fsbtodb(sb,bno),f->buf,f->bufsiz) < 0) { push_errmsg("%s",fs->dev); return(-1); } } else { bzero(f->buf,f->bufsiz); } } return(0); } #endif /* Close an open file. */ #ifndef TAR_INODE_NONE static int fs_close(int ofx) { F *f; f = ofx_to_f(ofx); if (! f) { set_errmsg("bad handle"); return(-1); } if (f->flink) f->flink->blink = f->blink; if (f->blink) f->blink->flink = f->flink; else f->fs->of = f->flink; if (!f->fs->of && (f->fs->fsx < 0)) closefs(f->fs); free(f->buf); OLD(f); return(0); } #endif /* Do a readdir() operation on the filesystem. The reason for the odd code checking if the size is not a multiple of 512, when we find an entry that extends past EOF, is that on some old systems, a newly-created directory is 24 bytes long, even though its entries claim the full 512 bytes of the directory data block. I have also seen at least one directory that was 526 long, though I didn't investigate how much its entries claimed. So if we see ourselves spill off the end of a directory, we round its size up to the next 512-byte boundary and see if we're still falling off the end. (Note that the check for entries overrunning data blocks prevents us from walking farther off the end than the end of the 512-byte block.) */ #ifndef TAR_INODE_NONE static struct fs_direct _fs_readdir_eof; #define FS_READDIR_EOF (&_fs_readdir_eof) static struct fs_direct *fs_readdir(int ofx) { F *f; struct rawi_direct d; f = ofx_to_f(ofx); if (! f) { set_errmsg("bad handle"); return(0); } if (f->off < 0) { set_errmsg("reading before beginning of directory"); push_errmsg("fs_readdir"); return(0); } retrysize:; if (f->off == f->di.di_size) return(FS_READDIR_EOF); if (f->off > f->di.di_size) { if (f->di.di_size & 511) { f->di.di_size = round_up(f->di.di_size,512); goto retrysize; } set_errmsg("reading after end of directory"); push_errmsg("fs_readdir"); return(0); } if (get_off_in_buf(f) < 0) { push_errmsg("fs_readdir"); return(0); } d = * (struct rawi_direct *) (f->buf + (f->off - f->bufoff)); #ifdef TAR_INODE_INTERNAL (*f->fs->fixdirect)(&d); #endif f->off += d.d_reclen; if (f->off > f->bufoff+f->fs->sb->fs_bsize) { set_errmsg("entry runs off end of data block"); push_errmsg("fs_readdir"); return(0); } if (d.d_namlen > sizeof(f->fsd.d_name)-1) { set_errmsg("name too long"); push_errmsg("fs_readdir"); return(0); } f->fsd.d_ino = d.d_ino; f->fsd.d_namlen = d.d_namlen; bcopy(&d.d_name[0],&f->fsd.d_name[0],f->fsd.d_namlen); f->fsd.d_name[f->fsd.d_namlen] = '\0'; return(&f->fsd); } #endif /* Setup the DNLC for a newly-opened filesystem. */ #ifndef TAR_INODE_NONE static void setup_dnlc(FS *fs) { DNLC *d; DNLCENT **vec; int i; d = NEW(DNLC); d->hashindex = 0; d->hashsize = dnlc_hashsizes[0]; d->capacity = dnlc_hashsizes[0] / 3; d->fill = 0; vec = malloc((d->capacity+dnlc_hashsizes[0])*sizeof(DNLCENT *)); d->ageheap = vec; d->hashvec = vec + d->capacity; for (i=d->capacity,vec=d->hashvec;i>0;i--,vec++) *vec = 0; d->fs = fs; fs->dnlc = d; } #endif /* Fix up the ageheap for a DNLC; bubble an element down */ #ifndef TAR_INODE_NONE #define DHL(x) (((x)*2)+1) #define DHR(x) (((x)+1)*2) #define DHP(x) (((x)-1)/2) static void dnlc_reheap_down(DNLC *d, int x) { DNLCENT *e; int l; int r; int s; e = d->ageheap[x]; while (1) { l = DHL(x); r = DHR(x); if ((r >= d->fill) || (e->lastuse <= d->ageheap[r]->lastuse)) { if ((l >= d->fill) || (e->lastuse <= d->ageheap[l]->lastuse)) { e->heapindex = x; return; } s = l; } else { s = ( (e->lastuse <= d->ageheap[l]->lastuse) || (d->ageheap[r]->lastuse <= d->ageheap[l]->lastuse) ) ? r : l; } d->ageheap[x] = d->ageheap[s]; d->ageheap[x]->heapindex = x; d->ageheap[s] = e; x = s; } } #undef DHL #undef DHR #undef DHP #endif /* DNLC hit; update lastuse of entry and bubble it to bottom of heap */ #ifndef TAR_INODE_NONE static void dnlc_hit(DNLC *d, DNLCENT *e) { e->lastuse = dnlc_lastuse ++; dnlc_reheap_down(d,e->heapindex); } #endif /* DNLC miss; do bookkeeping - currently nothing to do here */ #ifndef TAR_INODE_NONE static void dnlc_miss(UNUSED(DNLC *d)) { } #endif /* Return full-size hash code for directory/name pair, for DNLC */ #ifndef TAR_INODE_NONE static unsigned long int dnlc_hash(int ino, const char *name, int namelen) { return(crc(crc(0,&ino,sizeof(ino)),name,namelen)); } #endif /* Look up a directory/name pair in a DNLC. Return inumber found, or -1 if not found. */ #ifndef TAR_INODE_NONE static int dnlc_lookup(DNLC *d, int ino, const char *name, int namelen) { int hash; DNLCENT *e; hash = dnlc_hash(ino,name,namelen) % d->hashsize; if (DEBUG(DNLC)) fprintf(debugf,"dnlc %s: lookup %d/%.*s: hash %d [%d]\n",d->fs->dev,ino,namelen,name,hash,d->hashsize); while (1) { e = d->hashvec[hash]; if (! e) { if (DEBUG(DNLC)) fprintf(debugf,"dnlc %s: miss\n",d->fs->dev); dnlc_miss(d); return(-1); } if ((e->ino == ino) && (e->namelen == namelen) && !bcmp(e->name,name,namelen)) { if (DEBUG(DNLC)) fprintf(debugf,"dnlc %s: hit %d\n",d->fs->dev,e->toino); dnlc_hit(d,e); return(e->toino); } if (++hash >= d->hashsize) hash = 0; if (DEBUG(DNLC)) fprintf(debugf,"dnlc %s: collision with %d/%.*s=%d[%d], ->%d\n",d->fs->dev,e->ino,e->namelen,e->name,e->toino,e->hash,hash); } } #endif /* Remove an entry from the hash table, preparatory to reusing it */ #ifndef TAR_INODE_NONE static void dnlc_unhash(DNLC *d, DNLCENT *e) { int i; DNLCENT *t; i = e->hash; while (1) { t = d->hashvec[i]; if (t == 0) { break; } else if (t == e) { d->hashvec[i] = 0; e = 0; } else if (e == 0) { int j; d->hashvec[i] = 0; j = t->hash; while (1) { if (! d->hashvec[j]) { d->hashvec[j] = t; if (DEBUG(DNLC) && (i != j)) fprintf(debugf,"dnlc %s: reclaim: moved %d/%.*s=%d[%d] from %d to %d\n",d->fs->dev,t->ino,t->namelen,t->name,t->toino,t->hash,i,j); break; } if (++j >= d->hashsize) j = 0; } } if (++i >= d->hashsize) i = 0; } } #endif /* Enter a directory/name/inumber triple in a DNLC */ #ifndef TAR_INODE_NONE static void dnlc_enter(DNLC *d, int ino, const char *name, int namelen, int subino) { int hash; DNLCENT *e; hash = crc(crc(0,&ino,sizeof(ino)),name,namelen) % d->hashsize; if (DEBUG(DNLC)) fprintf(debugf,"dnlc %s: enter %d/%.*s=%d: hash %d [%d]\n",d->fs->dev,ino,namelen,name,subino,hash,d->hashsize); if (d->fill < d->capacity) { e = NEW(DNLCENT); if (DEBUG(DNLC)) fprintf(debugf,"dnlc %s: create new entry\n",d->fs->dev); } else { e = d->ageheap[0]; if (DEBUG(DNLC)) fprintf(debugf,"dnlc %s: reclaim %d/%.*s=%d[%d]\n",d->fs->dev,e->ino,e->namelen,e->name,e->toino,e->hash); d->fill --; d->ageheap[0] = d->ageheap[d->fill]; dnlc_unhash(d,e); dnlc_reheap_down(d,0); free(e->name); } e->heapindex = d->fill; e->hash = hash; e->lastuse = dnlc_lastuse ++; e->ino = ino; e->namelen = namelen; e->name = malloc(namelen); bcopy(name,e->name,namelen); e->toino = subino; d->ageheap[d->fill] = e; d->fill ++; while (1) { if (! d->hashvec[hash]) { if (DEBUG(DNLC)) fprintf(debugf,"dnlc %s: storing in slot %d\n",d->fs->dev,hash); d->hashvec[hash] = e; return; } if (DEBUG(DNLC)) fprintf(debugf,"dnlc %s: collision with %d/%.*s=%d[%d], ->",d->fs->dev,d->hashvec[hash]->ino,d->hashvec[hash]->namelen,d->hashvec[hash]->name,d->hashvec[hash]->toino,d->hashvec[hash]->hash); if (++hash >= d->hashsize) hash = 0; if (DEBUG(DNLC)) fprintf(debugf,"%d\n",hash); } } #endif /* Ensure DNLC won't thrash for a path with n components, by growing it if necessary */ #ifndef TAR_INODE_NONE static void dnlc_sizecheck(DNLC *d, int n) { if ((n+1 >= d->capacity) && dnlc_hashsizes[d->hashindex+1]) { DNLCENT **av; DNLCENT **hv; DNLCENT *e; int i; int h; if (DEBUG(DNLC)) fprintf(debugf,"dnlc %s: growing from %d to ",d->fs->dev,d->capacity); while ((n+1 >= d->capacity) && dnlc_hashsizes[d->hashindex+1]) { d->hashsize = dnlc_hashsizes[++d->hashindex]; d->capacity = d->hashsize / 3; } if (DEBUG(DNLC)) fprintf(debugf,"%d\n",d->capacity); av = malloc((d->capacity+d->hashsize)*sizeof(DNLCENT *)); hv = av + d->capacity; bcopy(d->ageheap,av,d->capacity*sizeof(DNLCENT *)); for (i=d->hashsize-1;i>=0;i--) hv[i] = 0; for (i=d->fill-1;i>=0;i--) { e = av[i]; h = dnlc_hash(e->ino,e->name,e->namelen) % d->hashsize; if (DEBUG(DNLC)) fprintf(debugf,"dnlc %s: rehash %d/%.*s=%d[%d] to %d",d->fs->dev,e->ino,e->namelen,e->name,e->toino,e->hash,h); e->hash = h; while (hv[h]) if (++h >= d->hashsize) h = 0; if (DEBUG(DNLC) && (h != e->hash)) fprintf(debugf," (spilled to %d)",h); hv[h] = e; if (DEBUG(DNLC)) fprintf(debugf,"\n"); } free(d->ageheap); d->ageheap = av; d->hashvec = hv; } } #endif /* Walk a path on an fs, by repeated lookups */ #ifndef TAR_INODE_NONE static int walkpath(FS *fs, int startino, const char *path, int *inop) { int ci; const char *pp; int dfx; struct fs_direct *f; int si; int nlookups; if (DEBUG(DNLC)) fprintf(debugf,"walkpath on %s from %d: %s\n",fs->dev,startino,path); ci = startino; nlookups = 0; while (1) { while (*path == '/') path ++; if (! *path) { *inop = ci; return(0); } pp = path; while (*pp && (*pp != '/')) pp ++; si = dnlc_lookup(fs->dnlc,ci,path,pp-path); dnlc_sizecheck(fs->dnlc,++nlookups); if (si == 0) { push_errmsg("No such file or directory"); return(-1); } if (si > 0) { ci = si; path = pp; continue; } dfx = openi(fs,ci); if (dfx < 0) return(-1); if ((ofv[dfx]->di.di_mode & S_IFMT) != S_IFDIR) { fs_close(dfx); push_errmsg("Not a directory"); return(-1); } while (1) { f = fs_readdir(dfx); if (f == 0) return(-1); if (f == FS_READDIR_EOF) break; if (f->d_ino == 0) continue; if ((f->d_namlen == (pp-path)) && !bcmp(&f->d_name[0],path,f->d_namlen)) break; } if (f == FS_READDIR_EOF) { fs_close(dfx); dnlc_enter(fs->dnlc,ci,path,pp-path,0); push_errmsg("No such file or directory"); return(-1); } dnlc_enter(fs->dnlc,ci,path,pp-path,f->d_ino); ci = f->d_ino; fs_close(dfx); path = pp; } } #endif /* The byte-sex fixup functions. */ #ifdef TAR_INODE_INTERNAL static U16 U16_swap(U16 t) attribute((const)); static S16 S16_swap(S16 t) attribute((const)); static U32 U32_swap(U32 t) attribute((const)); static S32 S32_swap(S32 t) attribute((const)); static U16 U16_swap(U16 t) { return((t>>8)|(t<<8)); } static S16 S16_swap(S16 t) { return((((U16)t)>>8)|(t<<8)); } static U32 U32_swap(U32 t) { return((t>>24)|((t>>8)&0xff00)|((t&0xff00)<<8)|(t<<24)); } static S32 S32_swap(S32 t) { return((((U32)t)>>24)|((t>>8)&0xff00)|((t&0xff00)<<8)|(t<<24)); } #endif /* dinode fixup - fully native - do nothing */ #ifdef TAR_INODE_INTERNAL static void di__nil(struct dinode *di) { di = di; } #endif /* dinode fixup - byte sex native, size needs long-swapping */ #ifdef TAR_INODE_INTERNAL static void di__swap_size(struct dinode *di) { U32 t; t = di->u.s.size_high; di->u.s.size_high = di->u.s.size; di->u.s.size = t; } #endif /* dinode fixup - byte sex needs swapping, size long order is ok */ #ifdef TAR_INODE_INTERNAL static void di__swap_bytes(struct dinode *di) { int i; #define S(field,type) (di->field = type##_swap(di->field)) S(di_mode,U16); S(di_nlink,S16); S(di_uid,U16); S(di_gid,U16); S(di_size,U32); S(di_mtime,U32); S(di_ctime,U32); if (((di->di_mode & S_IFMT) != S_IFLNK) || !isfastlink(di)) { for (i=0;id_namlen & 0xff) && (rd->d_namlen & 0xff00)) rd->d_namlen = ((char *)rd)[7]; } #endif /* rawi_direct fixup - byte sex needs fixing; also deal with BSD di_type */ #ifdef TAR_INODE_INTERNAL static void direct__swap_bytes(struct rawi_direct *rd) { #define S(field,type) (rd->field = type##_swap(rd->field)) S(d_ino,U32); S(d_reclen,U16); if ((rd->d_namlen & 0xff) && (rd->d_namlen & 0xff00)) { rd->d_namlen = ((char *)rd)[7]; } else { S(d_namlen,U16); } #undef S } #endif /* daddr_t fixup - fully native - do nothing */ #ifdef TAR_INODE_INTERNAL static daddr_t blkno__nil(daddr_t d) { return(d); } #endif /* daddr_t fixup - byte sex needs fixing */ #ifdef TAR_INODE_INTERNAL static daddr_t blkno__swap_bytes(daddr_t d) { return(U32_swap(d)); } #endif /* superblock fixup - byte-swap all fields */ /* there's no __nil version of this because it's not stuffed in a (*)(...). */ #ifdef TAR_INODE_INTERNAL static void sb__swap_bytes(struct fs *sb) { #define S(field,type) (sb->field = type##_swap(sb->field)) S(fs_iblkno,S32); S(fs_cgoffset,S32); S(fs_cgmask,S32); S(fs_size,S32); S(fs_ncg,S32); S(fs_bsize,S32); S(fs_fsize,S32); S(fs_frag,S32); S(fs_bmask,S32); S(fs_bshift,S32); S(fs_fragshift,S32); S(fs_fsbtodb,S32); S(fs_nindir,S32); S(fs_inopb,S32); S(fs_ipg,S32); S(fs_fpg,S32); S(fs_magic,U32); #undef S } #endif /* Open a filesystem. Return the fsv index, or -1 on error. */ #ifndef TAR_INODE_NONE static int fs_openfs(const char *path) { FS *fs; int i; fs = NEW(FS); if (! fs) { set_errmsg("malloc failed"); return(0); } fs->fd = open(path,O_RDONLY,0); if (fs->fd < 0) { set_errmsg(strerror(errno)); push_errmsg("can't open %s",path); OLD(fs); return(0); } fs->dev = path; fs->sb = (struct fs *) SBBUFALIGN(&fs->sbbuf[0]); if (readdisk(fs,SBLOCK,fs->sb,SBSIZE) < 0) { push_errmsg("can't read superblock from %s",path); OLD(fs); } #ifdef TAR_INODE_INTERNAL switch (fs->sb->fs_magic) { case FS_MAGIC: fs->fixdirect = direct__nil; fs->fixblkno = blkno__nil; switch (*(char *)&fs->sb->fs_magic) { case FS_MAGIC&0xff: fs->fixdi = di__swap_size; break; case (FS_MAGIC>>24)&0xff: fs->fixdi = di__nil; break; default: set_errmsg("bad magic number"); push_errmsg("%s",path); return(0); break; } break; case FS_MAGIC_SW: sb__swap_bytes(fs->sb); fs->fixdirect = direct__swap_bytes; fs->fixblkno = blkno__swap_bytes; switch (*(char *)&fs->sb->fs_magic) { case FS_MAGIC&0xff: fs->fixdi = di__swap_bytes; break; case (FS_MAGIC>>24)&0xff: fs->fixdi = di__swap_bytes_and_size; break; default: set_errmsg("bad magic number"); push_errmsg("%s",path); return(0); break; } break; default: set_errmsg("bad magic number"); push_errmsg("%s",path); return(0); break; } fs->inoswap = malloc(INOPB(fs->sb)); if (fs->inoswap == 0) { set_errmsg("malloc failed"); OLD(fs); return(0); } #else if (fs->sb->fs_magic != FS_MAGIC) { set_errmsg("bad magic number"); push_errmsg("%s",path); return(0); } #endif SBFIXUP(fs->sb); fs->of = 0; fs->bufs = malloc(fs->sb->fs_bsize*(NIADDR+1)); if (fs->bufs == 0) { set_errmsg("malloc failed"); #ifdef TAR_INODE_INTERNAL free(fs->inoswap); #endif OLD(fs); return(0); } fs->inoblk = -1; fs->inobuf = fs->bufs; fs->indirbuf[0] = fs->inobuf + fs->sb->fs_bsize; for (i=1;iindirbuf[i] = fs->indirbuf[i-1] + fs->sb->fs_bsize; for (i=0;iindirblk[i] = -1; for (i=0;i= nfs) { nfs ++; fsv = fsv ? realloc(fsv,nfs*sizeof(FS *)) : malloc(nfs*sizeof(FS *)); } fsv[i] = fs; fs->fsx = i; fs->maxino = (fs->sb->fs_ipg * fs->sb->fs_ncg) - 1; setup_dnlc(fs); return(i); } #endif /* Close a filesystem. If files remain open on it, just bashes fsx field and the last file close will close the filesystem too. */ #if 0 static int fs_closefs(int fsx) { FS *fs; fs = fsx_to_fs(fsx); if (! fs) { set_errmsg("bad handle"); return(-1); } fsv[fsx] = 0; if (fs->of == 0) { closefs(fs); } else { fs->fsx = -1; } return(0); } #endif /* Return the inumber of the filesystem's root. */ #ifndef TAR_INODE_NONE static int fs_root(UNUSED(int fsx)) { return(ROOTINO); } #endif /* Look up a pathname, relative to a given directory, returning inumber or -1 if error. */ #ifndef TAR_INODE_NONE static int fs_lookup(int fsx, int dir, const char *path) { FS *fs; int ino; fs = fsx_to_fs(fsx); if (! fs) { set_errmsg("bad handle"); return(-1); } if (*path == '/') dir = fs_root(fsx); if (walkpath(fs,dir,path,&ino) < 0) { push_errmsg(path); return(-1); } return(ino); } #endif /* Open a file by inumber. */ #ifndef TAR_INODE_NONE static int fs_open(int fsx, int ino) { FS *fs; int ofx; F *f; fs = fsx_to_fs(fsx); if (! fs) { set_errmsg("bad handle"); return(-1); } ofx = openi(fs,ino); f = ofv[ofx]; if ((f->di.di_mode & S_IFMT) != S_IFREG) { fs_close(ofx); set_errmsg("Not a plain file"); return(-1); } return(ofx); } #endif /* Stat a file by inumber. */ #ifndef TAR_INODE_NONE static int fs_stat(int fsx, int ino, struct stat *stb) { FS *fs; fs = fsx_to_fs(fsx); if (! fs) { set_errmsg("bad handle"); return(-1); } return(stati(fs,ino,stb)); } #endif /* Fstat an already-open file. */ #ifndef TAR_INODE_NONE static int fs_fstat(int ofx, struct stat *stb) { F *f; f = ofx_to_f(ofx); if (! f) { set_errmsg("bad handle"); return(-1); } load_stb(&f->di,f->ino,stb); return(0); } #endif /* Do an opendir(). */ #ifndef TAR_INODE_NONE static int fs_opendir(int fsx, int ino) { FS *fs; int ofx; fs = fsx_to_fs(fsx); if (! fs) { set_errmsg("bad handle"); return(-1); } ofx = openi(fs,ino); if (ofx < 0) { push_errmsg("fs_opendir"); return(-1); } if ((ofv[ofx]->di.di_mode & S_IFMT) != S_IFDIR) { fs_close(ofx); push_errmsg("Not a directory"); return(-1); } return(ofx); } #endif /* Do a closedir(). */ #ifndef TAR_INODE_NONE static int fs_closedir(int ofx) { return(fs_close(ofx)); } #endif /* Do an lseek() on an open file. */ #ifndef TAR_INODE_NONE static long int fs_lseek(int ofx, off_t off, enum fs_whence whence) { F *f; f = ofx_to_f(ofx); if (! f) { set_errmsg("bad handle"); return(-1); } switch (whence) { case FS_SET: f->off = off; break; case FS_INCR: f->off += off; break; case FS_XTND: f->off = f->di.di_size + off; break; default: push_errmsg("Invalid argument"); return(-1); break; } return(f->off); } #endif /* Do a read(), given open file, buf, and size. */ #ifndef TAR_INODE_NONE static int fs_read(int ofx, void *buf, int nb) { F *f; struct fs *sb; char *cb; int left; int n; f = ofx_to_f(ofx); if (! f) { set_errmsg("bad handle"); return(-1); } if ((f->off < 0) || (f->off >= f->di.di_size)) return(0); sb = f->fs->sb; cb = buf; left = nb; while (left > 0) { if (get_off_in_buf(f) < 0) { push_errmsg("fs_read"); return(-1); } n = f->bufoff+sb->fs_bsize - f->off; if (n > nb) n = nb; bcopy(f->buf+(f->off-f->bufoff),cb,n); cb += n; left -= n; f->off += n; } return(nb); } #endif /* Do a readlink(). Returns actual size of link, regardless of buffer size passed. Wish more real kernels would do this. */ #ifndef TAR_INODE_NONE static int fs_readlink(int fsx, int ino, void *buf, int bufsiz) { struct dinode *di; FS *fs; int n; fs = fsx_to_fs(fsx); if (! fs) { set_errmsg("bad handle"); return(-1); } di = getino(fs,ino); if (! di) { push_errmsg("fs_readlink"); return(-1); } if ((di->di_mode & S_IFMT) != S_IFLNK) { set_errmsg("Not a symbolic link"); return(-1); } n = di->di_size; if (n > bufsiz) n = bufsiz; if (isfastlink(di)) { bcopy(&di->di_db[0],buf,n); return(di->di_size); } else { int ofx; int r; char *fsm; ofx = openi(fs,ino); r = fs_read(ofx,buf,n); fsm = fs_errstr; fs_errstr = 0; fs_close(ofx); if (fs_errstr != &fs_mallfail[0]) free(fs_errstr); fs_errstr = fsm; return(di->di_size); } } #endif /* Helper for fs_spacemap: scan found a block that's part of a segment. */ #ifndef TAR_INODE_NONE static void map_set(MAPSTATE *state, int o) { if (state->start < 0) state->start = o; state->last = o; } #endif /* Helper for fs_spacemap: scan found a block that's not part of a segment. */ #ifndef TAR_INODE_NONE static void map_clr(MAPSTATE *state) { if (state->start >= 0) { if (state->rv < state->nmap) { state->map[state->rv][0] = state->start * state->factor; state->map[state->rv][1] = (state->last + 1 - state->start) * state->factor; } state->rv ++; } state->start = -1; } #endif /* Scan a vector of block numbers, adding the non-zero ones to the MAPSTATE. *offp is value-result parameter, holding block number of vec[0] on entry, block number vec[vecsiz] would have on exit, unless we overran EOF, in which case it holds maxoff. */ #ifndef TAR_INODE_NONE static int addmap(MAPSTATE *state, daddr_t *vec, int vecsiz, long int maxoff, long int *offp) { int i; long int o; o = *offp; for (i=0;i= maxoff) break; if (vec[i]) { map_set(state,o); } else { map_clr(state); } o ++; } *offp = o; return(0); } #endif /* Scan an indirect block for fs_spacemap. */ #ifndef TAR_INODE_NONE static int addimap(MAPSTATE *state, daddr_t ib, int lev, FS *fs, long int maxoff, long int *offp) { int bno; long int o; int i; int n; n = NINDIR(fs->sb); if (ib == 0) { map_clr(state); for (o=1;lev>=0;(o*=n),lev--) ; *offp += o; return(0); } bno = fsbtodb(fs->sb,ib); if (bno != fs->indirblk[lev]) { fs->indirblk[lev] = -1; if (readdisk(fs,bno,fs->indirbuf[lev],fs->sb->fs_bsize) < 0) { push_errmsg("addimap"); return(-1); } fs->indirblk[lev] = bno; } if (lev < 1) { return(addmap(state,(daddr_t *)fs->indirbuf[0],n,maxoff,offp)); } for (i=0;iindirbuf[lev])[i],lev-1,fs,maxoff,offp) < 0) return(-1); } return(0); } #endif /* Given a filesystem and inumber, generate and return a map of space actually occupied on-disk. Only nmap entries of map will be filled, but the total number will be returned. (This will usually be called twice, the first time with nmap==0 to find out the correct number.) */ #ifndef TAR_INODE_NONE static int fs_spacemap(int fsx, int ino, long int (*map)[2], int nmap) { struct dinode *di; FS *fs; int i; MAPSTATE state; long int o; long int maxo; fs = fsx_to_fs(fsx); if (! fs) { set_errmsg("bad handle"); return(-1); } di = getino(fs,ino); if (! di) return(-1); if ((di->di_mode & S_IFMT) != S_IFREG) { set_errmsg("Not a plain file"); return(-1); } state.map = map; state.nmap = nmap; state.rv = 0; state.start = -1; state.factor = fs->sb->fs_bsize; o = 0; maxo = (di->di_size + fs->sb->fs_bsize - 1) >> fs->sb->fs_bshift; if (addmap(&state,&di->di_db[0],NDADDR,maxo,&o) < 0) { push_errmsg("fs_spacemap"); return(-1); } for (i=0;idi_ib[i],i,fs,maxo,&o) < 0) { push_errmsg("fs_spacemap"); return(-1); } if (o >= maxo) break; } map_clr(&state); return(state.rv); } #endif /* strerror() for fs_errno */ #ifndef TAR_INODE_NONE static const char *fs_strerror(void) { return(fs_errstr); } #endif /* stat() given a pathname */ #ifndef TAR_INODE_NONE static int fs_stat_p(int fsx, const char *path, struct stat *stb) { int ino; ino = fs_lookup(fsx,fs_cwd,path); return((ino<0)?-1:fs_stat(fsx,ino,stb)); } #endif /* readlink() given a pathname */ #ifndef TAR_INODE_NONE static int fs_readlink_p(int fsx, const char *path, void *buf, int buflen) { int ino; ino = fs_lookup(fsx,fs_cwd,path); return((ino<0)?-1:fs_readlink(fsx,ino,buf,buflen)); } #endif /* opendir() given a pathname */ #ifndef TAR_INODE_NONE static int fs_opendir_p(int fsx, const char *path) { int ino; ino = fs_lookup(fsx,fs_cwd,path); return((ino<0)?-1:fs_opendir(fsx,ino)); } #endif /* open() given a pathname */ #ifndef TAR_INODE_NONE static int fs_open_p(int fsx, const char *path) { int ino; ino = fs_lookup(fsx,fs_cwd,path); return((ino<0)?-1:fs_open(fsx,ino)); } #endif /* Create i_readlink, i_open, etc, that can be used by the rest of tar without caring whether i-full is in effect or not. */ #ifdef TAR_INODE_NONE #define i_readlink readlink #define i_read read #define i_open(path) open(path,O_RDONLY,0) #define i_fstat fstat #define i_lseek lseek #define i_close close #define i_chdir chdir #define i_strerror() strerror(errno) #else static int i_readlink(const char *path, void *buf, int buflen) { return( (iflag == I_FULL) ? fs_readlink_p(i_fs,path,buf,buflen) : readlink(path,buf,buflen) ); } static int i_read(int fd, void *buf, int nb) { return( (iflag == I_FULL) ? fs_read(fd,buf,nb) : read(fd,buf,nb) ); } static const char *i_strerror(void) { return( (iflag == I_FULL) ? fs_strerror() : strerror(errno) ); } static int i_open(const char *path) { return( (iflag == I_FULL) ? fs_open_p(i_fs,path) : open(path,O_RDONLY,0) ); } static int i_fstat(int fd, struct stat *stb) { return( (iflag == I_FULL) ? fs_fstat(fd,stb) : fstat(fd,stb) ); } static int i_lseek(int fd, off_t off, int whence) { if (iflag == I_FULL) { switch (whence) { case L_SET: return(fs_lseek(fd,off,FS_SET)); break; case L_INCR: return(fs_lseek(fd,off,FS_INCR)); break; case L_XTND: return(fs_lseek(fd,off,FS_XTND)); break; } set_errmsg("Invalid argument"); return(-1); } else { return(lseek(fd,off,whence)); } } static int i_close(int fd) { return( (iflag == I_FULL) ? fs_close(fd) : close(fd) ); } static int i_chdir(const char *path) { if (iflag == I_FULL) { int newcwd; newcwd = fs_lookup(i_fs,fs_cwd,path); if (newcwd < 0) { push_errmsg("i_chdir"); return(-1); } if (fs_lookup(i_fs,newcwd,".") < 0) { push_errmsg("corrupt filesystem: missing ."); return(-1); } fs_cwd = newcwd; return(0); } else { return(chdir(path)); } } #endif /* Replace a string with another. Both are assumed malloc()ed. */ static void replacestr(char **sp, char *s) { free(*sp); *sp = s; } /* Hash function for link HTABLE. */ static int link_hfxn(void *e, int siz) { LINK *l; l = (LINK *) e; return(((((int)l->devnum)<<2)+(int)l->inum)%siz); } /* Comparison function for link HTABLE. */ static int link_cmpfxn(void *o, void *e) { LINK *ol; LINK *el; ol = (LINK *) o; el = (LINK *) e; return((ol->inum!=el->inum)||(ol->devnum!=el->devnum)); } /* Convert a string containing an octal number to an integer. First unrecognized character ends the string. Probably should use strtol and force base 8...this is a historical artifact. */ static int otoi(const char *s) { int v; v = 0; while (*s == ' ') s ++; while (1) { switch (*s) { case '0': v = (v << 3) + 0; break; case '1': v = (v << 3) + 1; break; case '2': v = (v << 3) + 2; break; case '3': v = (v << 3) + 3; break; case '4': v = (v << 3) + 4; break; case '5': v = (v << 3) + 5; break; case '6': v = (v << 3) + 6; break; case '7': v = (v << 3) + 7; break; default: return(v); break; } s ++; } } /* Open a path, but recognize the - and -# syntaxes for stdin/stdout and dups of arbitrary file descriptors. forwrite is used to tell whether bare - means stdin or stdout, and for error messages; whatfor is just for error messages. Calls exit() on failure. If where is non-nil, stores a mallocked descriptive string through it; if the pointed-to pointer is non-nil, frees it first. */ static int open_or_dup(const char *fn, int forwrite, const char *whatfor, char **where) { int n; int fd; char *rest; char sbuf[64]; const char *sstr; if (fn[0] == '-') { if (!fn[1]) { fd = dup(forwrite?1:0); if (fd < 0) { fprintf(stderr,"%s: can't dup %s for %s: %s\n",__progname,forwrite?"stdout":"stdin",whatfor,strerror(errno)); exit(1); } sstr = forwrite ? "standard output" : "standard input"; goto gotfd; } n = strtol(fn+1,&rest,0); if (!*rest) { fd = dup(n); if (fd < 0) { fprintf(stderr,"%s: can't dup %d for %s: %s\n",__progname,n,whatfor,strerror(errno)); exit(1); } if (where) { sprintf(&sbuf[0],"fd %d",n); sstr = &sbuf[0]; } goto gotfd; } } fd = open(fn,forwrite?(O_WRONLY|O_CREAT|O_TRUNC):O_RDONLY,0666&~oldumask); if (fd < 0) { fprintf(stderr,"%s: can't open %s for %s: %s\n",__progname,fn,whatfor,strerror(errno)); exit(1); } sstr = fn; gotfd:; if (where) { if (*where) free(*where); *where = strdup(sstr); } return(fd); } /* Like open_or_dup, but returns a FILE * instead of an fd. */ static FILE *fopen_or_dup(const char *fn, int forwrite, const char *whatfor) { return(fdopen(open_or_dup(fn,forwrite,whatfor,0),forwrite?"w":"r")); } /* Open a tape. More complicated than just open_or_dup because of USE_RMT and the need to check to see whether it's a real tape device; also leaves the fd in imt or omt instead of returning it, and resets state variables like firsti/firsto. */ static void openmt(const char *tape, int writing) { #ifdef USE_RMT const char *colon; const char *slash; #endif struct mtget ignore; int rv; int mt; int Bflag; Bflag = 0; #ifdef USE_RMT isrmt = 0; if ((colon=index(tape,':')) && (!(slash=index(tape,'/')) || (slash > colon))) { char *t; t = malloc((colon-tape)+1); bcopy(tape,t,colon-tape); t[colon-tape] = '\0'; mt = rmt_open(t,colon+1,writing?O_RDWR:O_RDONLY); if (mt < 0) { fprintf(stderr,"%s: %s on %s: %s\n",__progname,colon+1,t,strerror(errno)); exit(1); } free(t); isrmt = 1; } else #endif { if (tape[0] == '-') { mt = open_or_dup(tape,writing,writing?"output archive":"input archive",writing?&omtn:&imtn); Bflag = 1; bfactor = NBLOCK; } else { if (writing) { if (omtn) free(omtn); omtn = strdup(tape); if (cflag) { mt = open(tape,O_RDWR|O_CREAT|O_TRUNC,0666&~oldumask); } else { mt = open(tape,O_RDWR,0); } } else { if (imtn) free(imtn); imtn = strdup(tape); mt = open(tape,O_RDONLY,0); } if (mt < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,tape,strerror(errno)); exit(1); } } } #ifdef USE_RMT if (isrmt) { rv = rmt_ioctl(mt,MTIOCGET,&ignore); } else #endif { rv = ioctl(mt,MTIOCGET,&ignore); } ismt = (rv >= 0); isfile = 0; if (! ismt) { Bflag = 1; bfactor = NBLOCK; #ifdef USE_RMT if (! isrmt) #endif { struct stat stb; fstat(mt,&stb); isfile = ((stb.st_mode & S_IFMT) == S_IFREG); } } #ifdef sun /* kernel bug workaround */ if (ismt) { struct stat stb; if ( (fstat(mt,&stb) >= 0) && ((stb.st_mode & S_IFMT) == S_IFCHR) && (major(stb.st_rdev) == 18) ) { Bflag = 1; bfactor = NBLOCK; } } #endif if (writing) { omt = mt; firsto = 1; if (Bflag) Bflag_o |= BF_AUTO; else Bflag_o &= ~BF_AUTO; align_room = align_output; } else { imt = mt; firsti = 1; if (Bflag) Bflag_i |= BF_AUTO; else Bflag_i &= ~BF_AUTO; } } /* Read from the tape. */ static int readmt(void *buf, int siz) { #ifdef USE_RMT return(isrmt?rmt_read(imt,buf,siz):read(imt,buf,siz)); #else return(read(imt,buf,siz)); #endif } /* Write to the tape. */ static int writemt(const void *buf, int siz) { #ifdef USE_RMT return(isrmt?rmt_write(omt,buf,siz):write(omt,buf,siz)); #else return(write(omt,buf,siz)); #endif } /* Initialize an ARGFLAG to the default. */ static void af_default(ARGFLAG *fp) { fp->how = HOW_NORM; fp->I = 0; fp->X = 0; fp->W = 0; fp->C = 0; fp->S = 0; fp->Q = 0; } /* Scan the argument list, saving it in the args and nargs variables. */ static void scanargs(char **av) { int i; ARGFLAG f; int wantit; for (i=0;av[i];i++) ; nargs = 0; args = (ARG *) malloc(i*sizeof(ARG)); /* possibly an overestimate */ af_default(&f); for (i=0;av[i];i++) { if (! f.Q) { if (! strcmp(av[i],"-H")) { f.how = HOW_HEAD; continue; } else if (! strcmp(av[i],"-T")) { f.how = HOW_TAIL; continue; } else if (! strcmp(av[i],"-N")) { f.how = HOW_NORM; continue; } else if (! strcmp(av[i],"-F")) { f.how = HOW_FULL; continue; } else if (! strcmp(av[i],"-I")) { f.I = 1; f.X = 0; continue; } else if (! strcmp(av[i],"-X")) { f.X = 1; f.I = 0; continue; } else if (! strcmp(av[i],"-W")) { f.W = 1; continue; } else if (! strcmp(av[i],"-C")) { f.C = 1; continue; } #ifndef NO_SYNTHETIC else if (! strcmp(av[i],"-S")) { f.S = 1; continue; } #endif else if (! strcmp(av[i],"-Q")) { f.Q = 1; continue; } } wantit = 1; if (f.C && !cflag) { fprintf(stderr,"%s: warning: -C not used except by c operation\n",__progname); wantit = 0; } else if (f.S && !cflag) { fprintf(stderr,"%s: warning: -S not used except by c operation\n",__progname); wantit = 0; } else if ( !(xflag||tflag||Cflag) && ((f.how != HOW_NORM) || f.W) && !f.X && !f.I ) { fprintf(stderr,"%s: warning: -W, -H, -T, -F not used except by t, x, or C operation unless -X or -I also given\n",__progname); } if (wantit) { args[nargs].str = av[i]; args[nargs].flags = f; nargs ++; } af_default(&f); } if ((f.how != HOW_NORM) || f.I || f.X || f.W || f.C || f.S || f.Q) { fprintf(stderr,"%s: warning: stray flag(s) at end of arglist ignored\n",__progname); } if (DEBUG(ARGS)) { for (i=0;i 0) { neg = 0; if ((l > 1) && (*cp == '!')) { cp ++; l --; neg = 1; } for (i=0;debugkeys[i].name;i++) { if (!bcmp(debugkeys[i].name,cp,l) && !debugkeys[i].name[l]) { if (neg) { debugging &= ~debugkeys[i].value; } else { debugging |= debugkeys[i].value; } break; } } if (! debugkeys[i].name) { if ((l > 4) && !bcmp(cp,"out=",4)) { char *t; t = malloc(l-3); bcopy(cp+4,t,l-4); t[l-4] = '\0'; debugf = fopen_or_dup(t,1,"debugging output"); if (debugf == 0) { fprintf(stderr,"%s: can't open debugging output file %s (using stderr)\n",__progname,cp+4); } setbuf(debugf,0); free(t); } else { if (strcmp(cp,"?") && strcmp(cp,"help")) { fprintf(stderr,"%s: warning: unknown debugging keyword `%s' ignored\n",__progname,cp); } if (printlist) { fprintf(stderr,"%s: valid debugging keywords are\n ",__progname); for (i=0;debugkeys[i].name;i++) { fprintf(stderr," %s",debugkeys[i].name); } fprintf(stderr,"\n"); printlist = 0; } } } } while ((*s == ',') || (*s == ' ')) s ++; } } /* Compare two `struct tm's, returning -1, 0, or 1 as the first is less than, equal to, or greater than, the second. */ static int tmcmp(const struct tm *t1, const struct tm *t2) { #define CMP(f) if (t1->f > t2->f) return(1); if (t1->f < t2->f) return(-1); CMP(tm_year) CMP(tm_mon) CMP(tm_mday) CMP(tm_hour) CMP(tm_min) CMP(tm_sec) #undef CMP return(0); } /* Convert a struct tm to a time_t. Does a first cut based on averages, then does exponential search from there until two bracketing times are found, then narrows it with binary search until the exact time is found. */ static time_t tm_to_time_t(const struct tm *t) { time_t lsec; time_t hsec; time_t msec; struct tm htm; struct tm *tm; lsec = ((t->tm_year - 70) * 31556952) + (t->tm_mon * 2629746) + (t->tm_mday * 86400) + (t->tm_hour * 3600) + (t->tm_min * 60) + t->tm_sec; tm = localtime(&lsec); switch (tmcmp(tm,t)) { case 0: return(lsec); /* we got (very) lucky */ break; case 1: hsec = lsec; htm = *tm; msec = 1; while (msec < hsec) { lsec = hsec - msec; tm = localtime(&lsec); switch (tmcmp(tm,t)) { case 0: return(lsec); break; case 1: msec += msec; break; case -1: goto out; /* break switch, while, switch */ break; } } lsec = 0; break; case -1: msec = 1; while (1) { hsec = lsec + msec; tm = localtime(&hsec); switch (tmcmp(tm,t)) { case 0: return(hsec); break; case 1: goto out; /* break switch, while, switch */ break; case -1: msec += msec; break; } } break; } out:; while (lsec < hsec) { msec = (lsec + hsec) / 2; tm = localtime(&msec); switch (tmcmp(tm,t)) { case 0: return(msec); break; case 1: hsec = msec; break; case -1: lsec = msec; break; } } return(lsec); } /* Return number of days in month m of year y. y is full year (eg, 1980 not 80). */ static int monthdays(int y, int m) { if (m == 1) { return( ( (y % 400) && !( (y % 100) && !(y % 4) ) ) ? 28 : 29 ); } else { return("\37\00\37\36\37\36\37\37\36\37\36\37"[m]); } } /* Convert a string to a time_t, applying defaults as necessary. Return 1 on success (and store the result through vp) or 0 on failure. */ static int numeric_time(const char *s, time_t *vp) { int i; char v[5]; int val; struct tm tm; static const char defstr[] = "....0101000000"; static const char monthnames[] = "janfebmaraprmayjunjulaugsepoctnovdec"; for (i=0;(i<4)&&s[i]&&isascii(s[i])&&isdigit(s[i]);i++) ; if (i != 4) return(0); bcopy(s,&v[0],4); v[4] = '\0'; val = atoi(v); if (val < 1900) return(0); tm.tm_year = val - 1900; s += 4; if (!*s) s = defstr + 4; #define TWODIGITS() (isascii(*s)&&isdigit(*s)&&isascii(s[1])&&isdigit(s[1])) #define TWONUM() do{v[0]=*s++;v[1]=*s++;v[2]=0;val=atoi(v);}while(0) if (TWODIGITS()) { TWONUM(); if ((val < 1) || (val > 12)) return(0); tm.tm_mon = val - 1; } else { for (i=0;i<3;i++) { if (!isascii(s[i])) return(0); v[i] = isupper(s[i]) ? tolower(s[i]) : s[i]; } for (i=0;i<12;i++) { if (!bcmp(&v[0],&monthnames[3*i],3)) break; } if (i >= 12) return(0); tm.tm_mon = i; s += 3; } if (!*s) s = defstr + 6; if (! TWODIGITS()) return(0); TWONUM(); if ((val < 1) || (val > monthdays(tm.tm_year,tm.tm_mon))) return(0); tm.tm_mday = val; if (!*s) s = defstr + 8; if (! TWODIGITS()) return(0); TWONUM(); if ((val < 0) || (val > 23)) return(0); tm.tm_hour = val; if (!*s) s = defstr + 10; if (! TWODIGITS()) return(0); TWONUM(); if ((val < 0) || (val > 59)) return(0); tm.tm_min = val; if (!*s) s = defstr + 12; if (! TWODIGITS()) return(0); TWONUM(); if ((val < 0) || (val > 59)) return(0); tm.tm_sec = val; #undef TWODIGITS #undef TWONUM *vp = tm_to_time_t(&tm); return(1); } /* Convert the s key argument to a time: call numeric_time, or if that fails assume it's a timestamp file. */ static int make_sincetime(void) { struct stat stb; if (numeric_time(since,&sincetime)) return(0); if (stat(since,&stb) < 0) return(1); sincetime = stb.st_mtime; return(0); } /* Match a wildcard argument against a pathname string. name is the pathname, arg is the wildcard pattern, and how is one of the HOW_* constants. */ static int argmatch_wildcard(const char *name, const char *arg, int how) { if (how == HOW_TAIL) { const char *t; for (t=name+strlen(name);t!=name;t--) { if (argmatch_wildcard(t,arg,HOW_FULL)) return(1); } if (argmatch_wildcard(name,arg,HOW_FULL)) return(1); return(0); } while (1) { if (DEBUG(WILDCARD)) fprintf(debugf,"argmatch_wildcard: name %s arg %s\n",name,arg); switch (*arg) { case '*': if (argmatch_wildcard(name,arg+1,how)) return(1); if (!*name || (*name == '/')) return(0); name ++; break; case '?': if (!*name || (*name == '/')) return(0); name ++; arg ++; break; case '[': { int neg; int first; int sawit; neg = 0; first = 1; sawit = 0; arg ++; while (1) { if (!*arg) { sawit = 0; neg = 0; break; } else if ((*arg == ']') && !first) { arg ++; break; } else if ((*arg == '^') && first) { neg = 1; arg ++; continue; /* don't clear first */ } else if (arg[0] && (arg[1] == '-') && arg[2] && (arg[0] < arg[2])) { if ((*name != '/') && (arg[0] <= *name) && (*name <= arg[2])) sawit = 1; arg += 3; } else if (arg[0] == *name) { sawit = 1; arg ++; } else { arg ++; } first = 0; } name ++; if (sawit == neg) return(0); } break; case '\\': if (arg[1]) { arg ++; if (*arg != *name) return(0); name ++; arg ++; } break; case '\0': return((how==HOW_HEAD)||(*name=='\0')||((how==HOW_NORM)&&(*name=='/'))); break; default: if (*arg != *name) return(0); name ++; arg ++; break; } } } /* Match a non-wildcard argument against a pathname string. name is the pathname, arg is the argument, and how is one of the HOW_* constants. */ static int argmatch_literal(const char *name, const char *arg, int how) { int nl; int al; nl = strlen(name); al = strlen(arg); if (al > nl) return(0); switch (how) { case HOW_NORM: return( !bcmp(arg,name,al) && ((name[al] == '\0') || (name[al] == '/')) ); break; case HOW_HEAD: return(!bcmp(arg,name,al)); break; case HOW_TAIL: return(!bcmp(arg,name+nl-al,al)); break; case HOW_FULL: return((al==nl)&&!bcmp(arg,name,al)); break; } fprintf(stderr,"%s: internal error: bad how %d in argmatch_literal()\n",__progname,how); abort(); } /* Should the current entry (tocent) be considered of interest, based on the argument list and global flags? Used when reading archives. */ static int inarglist(void) { int ap; if (DEBUG(ARGLIST)) { fprintf(debugf,"inarglist: testing %s\n",tocent.xname); } for (ap=0;aps,name)) { *tp = t->link; free(t->s); OLD(t); if (DEBUG(KILL)) fprintf(debugf,"killing entry %s\n",name); return(1); } else { tp = &t->link; } } return(0); } /* Save a kill entry for this name. */ static void addkill(const char *name) { STRCHAIN *t; t = NEW(STRCHAIN); t->link = kill_list; kill_list = t; t->s = copyofstr(name); if (DEBUG(KILL)) fprintf(debugf,"addkill %s\n",name); } /* A function to pass to scantape() as the file or end function when nothing needs to be done. */ static void null_aux_fe(void) { } /* A function to pass to scantape() as the block function when nothing needs to be done. */ static void null_aux_b(UNUSED(int n)) { } /* Convert mode to a printable drwxr-xr-x style representation and send the result to fp. */ static void printmode(FILE *fp, int mode) { static int m1[] = { 1, S_IREAD>>0, S_IREAD>>0, 'r', '-' }; static int m2[] = { 1, S_IWRITE>>0, S_IWRITE>>0, 'w', '-' }; static int m3[] = { 3, S_ISUID|(S_IEXEC>>0), S_ISUID|(S_IEXEC>>0), 's', S_ISUID, 'S', S_IEXEC>>0, 'x', '-' }; static int m4[] = { 1, S_IREAD>>3, S_IREAD>>3, 'r', '-' }; static int m5[] = { 1, S_IWRITE>>3, S_IWRITE>>3, 'w', '-' }; static int m6[] = { 3, S_ISGID|(S_IEXEC>>3), S_ISGID|(S_IEXEC>>3), 's', S_ISGID, 'S', S_IEXEC>>3, 'x', '-' }; static int m7[] = { 1, S_IREAD>>6, S_IREAD>>6, 'r', '-' }; static int m8[] = { 1, S_IWRITE>>6, S_IWRITE>>6, 'w', '-' }; static int m9[] = { 3, S_ISVTX|(S_IEXEC>>6), S_ISVTX|(S_IEXEC>>6), 't', S_ISVTX, 'T', S_IEXEC>>6, 'x', '-' }; static int *m[] = { m1, m2, m3, m4, m5, m6, m7, m8, m9 }; int **mp; for (mp=m;mp<&m[sizeof(m)/sizeof(m[0])];mp++) { int *pairp; int n; int bitmask; int cmp; pairp = *mp; n = *pairp++; bitmask = *pairp++; for (;n>0;n--) { cmp = *pairp++; if ((mode&bitmask) == cmp) { break; } pairp ++; } putc(*pairp,fp); } } /* Print t-keyletter info for the current entry (tocent). */ static void print_t_info(void) { if (vflag) { char *cp; time_t li; printmode(vfp,tocent.mode&07777); fprintf(vfp,"%3d/%-3d",tocent.uid,tocent.gid); fprintf(vfp,"%7d",tocent.size); li = tocent.mtime; cp = ctime(&li); fprintf(vfp," %.12s %.4s ",cp+4,cp+20); } fprintf(vfp,"%s",tocent.xname); switch (tocent.type) { case TYPE_NORMAL: break; case TYPE_DIRECTORY: break; case TYPE_HARDLINK: fprintf(vfp," linked to %s",tocent.xlinkname); break; case TYPE_SOFTLINK: fprintf(vfp," symbolic link to %s",tocent.xlinkname); break; case TYPE_CSPECIAL: fprintf(vfp," character special device (%d,%d)",otoi(&tocent.devmajor[0]),otoi(&tocent.devminor[0])); break; case TYPE_BSPECIAL: fprintf(vfp," block special device (%d,%d)",otoi(&tocent.devmajor[0]),otoi(&tocent.devminor[0])); break; case TYPE_FIFO: fprintf(vfp," FIFO"); break; case TYPE_SPARSE: fprintf(vfp," (sparse, %d bytes on tape)",tocent.tblocks*TBLOCK); break; case TYPE_CONTIG: fprintf(vfp," (contiguous)"); break; case TYPE_KILL: fprintf(vfp," kill later entry"); break; default: fprintf(vfp," INTERNAL BUG, type %d\n",tocent.type); break; } fprintf(vfp,"\n"); fflush(vfp); } /* Divide a BIGNUM by a small unsigned int, modifying the BIGNUM in-place and returning the remainder. */ static void bignum_divmod(BIGNUM *n, unsigned int d, unsigned int *rp) { unsigned int a; int i; unsigned int q; a = 0; for (i=3;i>=0;i--) { a = (a << 16) | n->limb[i]; q = a / d; a -= q * d; n->limb[i] = q; } *rp = a; } /* Test a BIGNUM for being zero. */ static int bignum_iszero(BIGNUM n) { return( (n.limb[0] == 0) && (n.limb[1] == 0) && (n.limb[2] == 0) && (n.limb[3] == 0) ); } /* Add an off_t to a BIGNUM. Assumes the off_t is small enough that adding 65535 to it won't overflow. */ static BIGNUM bignum_inc(BIGNUM n, off_t v) { int i; for (i=0;v&&(i<3);i++) { v += n.limb[i]; n.limb[i] = v & 0xffff; v >>= 16; } return(n); } /* Warp past this archive member with lseek if we can, ie, if the archive member is entirely within the buffer, or if the archive is stored in a plain file. This _greatly_ improves speed of tar t on archives in files with large members, or tar x on such archives when skipping the large members. Note that if we seek, while we always seek by a multiple of TBLOCK, we may not seek by a multiple of TBLOCK*bfactor...except when B is in effect and we somehow read a non-TBLOCK-multiple amount. */ static void maybe_lseek(void) { if (tocent.tblocks <= 0) return; if (Bflag_i) { if (tocent.tblocks <= (itbfill/TBLOCK)-curiblk) { if (DEBUG(INPUT)) { fprintf(debugf,"maybe_lseek: jumping %d block%s\n",tocent.tblocks,(tocent.tblocks==1)?"":"s"); } curiblk += tocent.tblocks; filen = 0; return; } if (! isfile) return; if (DEBUG(INPUT)) { fprintf(debugf,"maybe_lseek: seeking over %d block%s\n",tocent.tblocks,(tocent.tblocks==1)?"":"s"); } lseek(imt,((tocent.tblocks+curiblk)*(off_t)TBLOCK)-itbfill,L_INCR); itbfill = 0; curiblk = 0; itboff = bignum_inc(itboff,(tocent.tblocks+curiblk)*(off_t)TBLOCK); filen = 0; } else { if (tocent.tblocks <= bfactor-curiblk) { if (DEBUG(INPUT)) { fprintf(debugf,"maybe_lseek: jumping %d block%s\n",tocent.tblocks,(tocent.tblocks==1)?"":"s"); } curiblk += tocent.tblocks; filen = 0; return; } if (! isfile) return; if (DEBUG(INPUT)) { fprintf(debugf,"maybe_lseek: seeking over %d block%s\n",tocent.tblocks,(tocent.tblocks==1)?"":"s"); } lseek(imt,(tocent.tblocks+curiblk-bfactor)*(off_t)TBLOCK,L_INCR); curiblk = bfactor; filen = 0; itboff = bignum_inc(itboff,(tocent.tblocks+curiblk-bfactor)*(off_t)TBLOCK); } } /* * Read a TBLOCK-size block from the input. Normally reads a whole tape * block at once and doles out the TBLOCK-sized blocks one at a time to * its callers. On the first read from an archive when not told the * blocking factor by any other means, this does a max-size read to * determine the actual blocking factor. * * If the B keyletter is in effect for the archive, reads input as a * byte-stream, accumulating at-least-TBLOCK-sized blocks and returning * them. */ static int getblock(char *blk) { if (firsti) { itboff = bigzero; itbfill = 0; curiblk = 0; } if (Bflag_i) { if (DEBUG(INPUT)) { fprintf(debugf,"getblock: Bflag=1 curiblk=%d itbfill=%d firsti=%d\n", curiblk,itbfill,firsti); } firsti = 0; while (itbfill-(curiblk*TBLOCK) < TBLOCK) { int nr; if (itbfill/TBLOCK != curiblk) { fprintf(stderr,"getblock consistency check failed\n"); abort(); } if (itbfill == curiblk*TBLOCK) { itboff = bignum_inc(itboff,curiblk*TBLOCK); itbfill = 0; curiblk = 0; } else if (itbfill > (MAXBLOCK-1)*TBLOCK) { itboff = bignum_inc(itboff,curiblk*TBLOCK); itbfill %= TBLOCK; bcopy(&itapebuf[curiblk*TBLOCK],&itapebuf[0],itbfill); curiblk = 0; } nr = readmt(&itapebuf[itbfill],(MAXBLOCK*TBLOCK)-itbfill); if (nr < 0) { if (DEBUG(INPUT)) { int e; e = errno; fprintf(debugf,"getblock: read got error: %s\n",strerror(e)); errno = e; } fprintf(stderr,"%s: tape read error: %s\n",__progname,strerror(errno)); exit(1); } if (nr == 0) { if (DEBUG(INPUT)) fprintf(debugf,"getblock: read got EOF\n"); if (itbfill) { fprintf(stderr,"%s: warning: EOF partway through an archive block\n",__progname); } return(0); } itbfill += nr; if (DEBUG(INPUT)) fprintf(debugf,"getblock: read got %d, itbfill now %d\n",nr,itbfill); } } else { if (DEBUG(INPUT)) { fprintf(debugf,"getblock: Bflag=0 curiblk=%d bfactor=%d firsti=%d\n", curiblk,bfactor,firsti); } if ((curiblk >= bfactor) || firsti) { int n; itboff = bignum_inc(itboff,curiblk*TBLOCK); n = readmt(itapebuf,((firsti&&!setbfactor)?((MAXBLOCK*TBLOCK)+1):(bfactor*TBLOCK))); if (n < 0) { fprintf(stderr,"%s: tape read error: %s\n",__progname,strerror(errno)); exit(1); } if (firsti) { if (n > MAXBLOCK*TBLOCK) { fprintf(stderr,"%s: tape blocksize too large for this tar\n",__progname); exit(1); } if (n % TBLOCK) { fprintf(stderr,"%s: tape blocksize error\n",__progname); exit(1); } n /= TBLOCK; if (n == 0) { fprintf(stderr,"%s: blocksize = 0\n",__progname); exit(1); } if (n != bfactor) { fprintf(stderr,"%s: blocksize = %d\n",__progname,n); bfactor = n; } } else if (n == 0) { return(0); } else if (n != bfactor*TBLOCK) { fprintf(stderr,"%s: tape blocksize %s\n",__progname,firsti?"doesn't match command-line value":"inconsistent"); exit(1); } curiblk = 0; firsti = 0; } } archive_offset = bignum_inc(itboff,curiblk*TBLOCK); bcopy(itapebuf+(curiblk*TBLOCK),blk,TBLOCK); curiblk ++; return(1); } /* Return the current archive offset in text form. Return value does not have to remain valid past next call, so we can (and do) return a pointer into a static buffer. */ static const char *archiveoffstr(void) { static char rbuf[BIGNUM_TEXTSIZE]; BIGNUM n; unsigned int r; char *rp; n = archive_offset; rp = &rbuf[BIGNUM_TEXTSIZE]; *--rp = '\0'; do { bignum_divmod(&n,10,&r); *--rp = '0' + r; } while (! bignum_iszero(n)); return(rp); } /* A header block was in error, but it's not a fatal error; we can continue with reasonable confidence. */ static void benign_badh(HEADER *h, const char *why) { if (! bcmp((char *)h,zblk,TBLOCK)) { longjmp(goodh_throw,-1); } switch (Fflag) { case 0: if (goodh_complain) { fprintf(stderr, "%s: corrupted header or out-of-sync tape (%s), %s at %s\n", __progname,why,imtn,archiveoffstr()); fprintf(stderr,"%s: try the F keyletter (see `man tar')\n", __progname); } longjmp(goodh_throw,2); break; case 1: if (goodh_complain) { fprintf(stderr, "%s: warning: corrupted header or out-of-sync tape (%s), %s at %s\n", __progname,why,imtn,archiveoffstr()); } break; case 2: break; } } /* See if the nc bytes ptr points to form an octal number, possibly preceded by spaces and followed by spaces and/or NULs. If so, store the number itself through place. */ static int isonum(const char *ptr, int nc, int *place) { int v; int n; for (;(nc>0)&&(' '==*ptr);nc--,ptr++) ; if (nc <= 0) return(0); v = 0; n = 0; for (;nc>0;nc--,ptr++) { if ((*ptr < '0') || (*ptr > '7')) break; v = (v << 3) | (*ptr - '0'); n ++; } for (;nc>0;nc--,ptr++) { if ((*ptr != ' ') && (*ptr != '\0')) break; } if (nc > 0) return(0); *place = v; return(1); } /* A header block was in error, and it's a fatal error; we can't patch up the error and use the header anyway. */ static void fatal_badh(HEADER *h, const char *why) { if (! bcmp((char *)h,zblk,TBLOCK)) { longjmp(goodh_throw,-1); } if (goodh_complain) { fprintf(stderr,"%s: corrupted header or out-of-sync tape (%s), %s at %s\n", __progname,why,imtn,archiveoffstr()); } longjmp(goodh_throw,2); } /* Compute the checksum for a header. Stamp on the chksum field with all spaces, then add up all the bytes. A rudimentary checksum, but good enough for most purposes, and we can't change it now for interoperability reasons. */ static int checksum(HEADER *h) { int i; char *cp; int n; for (cp=h->chksum;cp<&h->chksum[sizeof(h->chksum)];cp++) { *cp = ' '; } i = 0; for (cp=(char *)h,n=TBLOCK;n>0;cp++,n--) { i += *cp; } return(i); } /* Do the n bytes s points to form a string padded with NULs? */ static int nullterms(const char *s, int n) { for (;*s&&(n>0);s++,n--) ; return((n>0)?!bcmp(zblk,s,n):1); } /* Do the n bytes s points to for a string ending with a slash? */ static int slashterms(const char *s, int n) { if (!*s) return(0); if (n == 0) abort(); for (;n&&*s;s++,n--) ; return(s[-1] == '/'); } /* Handle one GNU-style sparse chunk descriptor. Return true if it is non-nil, false if nil. */ static int gnu_sparse_piece(char *nums) { int off; int siz; EXTDESC *xd; if ( !isonum(nums,12,&off) || !isonum(nums+12,12,&siz) ) return(0); if (siz == 0) return(0); xd = NEW(EXTDESC); xd->start = off; xd->length = siz; *xmaptail = xd; xmaptail = &xd->link; return(1); } /* Free the space map stored in an EXTDESC chain, given the root. */ static void free_map(EXTDESC *m) { EXTDESC *t; while (m) { t = m->link; OLD(m); m = t; } } /* Is this header a good one? Return one of the GH_* symbols. If complain is true, print complaints; otherwise, stay quiet. */ static GOODH_RV goodheader(HEADER *h, int complain) { int i; int hcksum; int cksum; int longhdr; char linkflag; int tocmtime; goodh_complain = complain; i = setjmp(goodh_throw); switch (i) { case 0: break; case 1: return(GH_GOOD); break; case -1: return(GH_EOF); break; default: return(GH_NOGOOD); break; } longhdr = 0; if ( (h->mode[6] != ' ') || (h->mode[7] != '\0') ) { benign_badh(h,"invalid delimiter (mode)"); } if ( (h->uid[6] != ' ') || (h->uid[7] != '\0') ) { benign_badh(h,"invalid delimiter (uid)"); } if ( (h->gid[6] != ' ') || (h->gid[7] != '\0') ) { benign_badh(h,"invalid delimiter (gid)"); } if ( (h->size[11] != ' ') && (h->size[11] != '\0') ) { benign_badh(h,"invalid delimiter (size)"); } if (h->mtime[11] != ' ') { benign_badh(h,"invalid delimiter (mtime)"); } if ( (h->chksum[6] != '\0') || (h->chksum[7] != ' ') ) { benign_badh(h,"invalid delimiter (chksum)"); } if (!isonum(h->mode,8,&tocent.mode)) fatal_badh(h,"bad number (mode)"); if (!isonum(h->uid,8,&tocent.uid)) fatal_badh(h,"bad number (uid)"); if (!isonum(h->gid,8,&tocent.gid)) fatal_badh(h,"bad number (gid)"); if (!isonum(h->size,12,&tocent.size)) fatal_badh(h,"bad number (size)"); if (!isonum(h->mtime,12,&tocmtime)) fatal_badh(h,"bad number (mtime)"); tocent.mtime = tocmtime; if (!isonum(h->chksum,8,&hcksum)) fatal_badh(h,"bad number (chksum)"); tocent.tblocks = how_many(tocent.size,TBLOCK); cksum = checksum(h); if (cksum != hcksum) { static char badckbuf[80]; sprintf(badckbuf,"wrong checksum (file %o, computed %o)",hcksum,cksum); fatal_badh(h,badckbuf); } if (! bcmp(h->magic,tmagic,8)) { if (! nullterms(h->uname,TULEN)) benign_badh(h,"garbage instead of nulls (ustar uname)"); if (! nullterms(h->gname,TGLEN)) benign_badh(h,"garbage instead of nulls (ustar gname)"); } linkflag = h->linkflag; replacestr(&tocent.xname,copynofstr(h->name,NAMSIZ)); if ((linkflag == LF_MOUSE_LONG) && !gnu_read) { linkflag = h->name[0]; if (! nullterms(h->name,NAMSIZ)) { benign_badh(h,"garbage instead of nulls (name)"); } switch (h->name[1]) { case '1': case '2': break; default: fatal_badh(h,"unrecognized name field in long header"); break; } longhdr = 1; } switch (linkflag) { case LF_OLDNORMAL: if (longhdr) fatal_badh(h,"NUL linkflag in long header"); /* fall through */ case LF_NORMAL: tocent.type = TYPE_NORMAL; if (!longhdr && slashterms(&h->name[0],NAMSIZ)) tocent.type = TYPE_DIRECTORY; if (bcmp(h->linkname,zblk,NAMSIZ)) benign_badh(h,"garbage instead of nulls (linkname)"); if (bcmp(h->devmajor,zblk,8)) benign_badh(h,"garbage instead of nulls (devmajor)"); if (bcmp(h->devminor,zblk,8)) benign_badh(h,"garbage instead of nulls (devminor)"); break; case LF_LINK: tocent.type = TYPE_HARDLINK; tocent.tblocks = 0; if (! nullterms(h->linkname,NAMSIZ)) benign_badh(h,"garbage instead of nulls (linkname)"); if (bcmp(h->devmajor,zblk,8)) benign_badh(h,"garbage instead of nulls (devmajor)"); if (bcmp(h->devminor,zblk,8)) benign_badh(h,"garbage instead of nulls (devminor)"); if (!longhdr) replacestr(&tocent.xlinkname,copynofstr(h->linkname,NAMSIZ)); break; case LF_SYMLINK: tocent.type = TYPE_SOFTLINK; tocent.tblocks = 0; if (bcmp(h->devmajor,zblk,8)) benign_badh(h,"garbage instead of nulls (devmajor)"); if (bcmp(h->devminor,zblk,8)) benign_badh(h,"garbage instead of nulls (devminor)"); if (longhdr) { if (bcmp(h->linkname,zblk,NAMSIZ)) benign_badh(h,"garbage instead of nulls (linkname)"); } else { if (! nullterms(h->linkname,NAMSIZ)) benign_badh(h,"garbage instead of nulls (linkname)"); replacestr(&tocent.xlinkname,copynofstr(h->linkname,NAMSIZ)); } break; case LF_CHR: tocent.type = TYPE_CSPECIAL; strncpy(&tocent.devmajor[0],h->devmajor,8); tocent.devmajor[8] = '\0'; strncpy(&tocent.devminor[0],h->devminor,8); tocent.devminor[8] = '\0'; tocent.tblocks = 0; if (bcmp(h->linkname,zblk,NAMSIZ)) benign_badh(h,"garbage instead of nulls (linkname)"); if (! nullterms(h->devmajor,8)) benign_badh(h,"garbage instead of nulls (devmajor)"); if (! nullterms(h->devminor,8)) benign_badh(h,"garbage instead of nulls (devminor)"); break; case LF_BLK: tocent.type = TYPE_BSPECIAL; strncpy(&tocent.devmajor[0],h->devmajor,8); tocent.devmajor[8] = '\0'; strncpy(&tocent.devminor[0],h->devminor,8); tocent.devminor[8] = '\0'; tocent.tblocks = 0; if (bcmp(h->linkname,zblk,NAMSIZ)) benign_badh(h,"garbage instead of nulls (linkname)"); if (! nullterms(h->devmajor,8)) benign_badh(h,"garbage instead of nulls (devmajor)"); if (! nullterms(h->devminor,8)) benign_badh(h,"garbage instead of nulls (devminor)"); break; case LF_DIR: tocent.type = TYPE_DIRECTORY; if (!longhdr && !slashterms(&h->name[0],NAMSIZ)) benign_badh(h,"missing / terminator on directory name"); if (bcmp(h->linkname,zblk,NAMSIZ)) benign_badh(h,"garbage instead of nulls (linkname)"); if (bcmp(h->devmajor,zblk,8)) benign_badh(h,"garbage instead of nulls (devmajor)"); if (bcmp(h->devminor,zblk,8)) benign_badh(h,"garbage instead of nulls (devminor)"); break; case LF_FIFO: tocent.type = TYPE_FIFO; if (bcmp(h->linkname,zblk,NAMSIZ)) benign_badh(h,"garbage instead of nulls (linkname)"); if (bcmp(h->devmajor,zblk,8)) benign_badh(h,"garbage instead of nulls (devmajor)"); if (bcmp(h->devminor,zblk,8)) benign_badh(h,"garbage instead of nulls (devminor)"); break; case LF_CONTIG: tocent.type = TYPE_CONTIG; if (bcmp(h->linkname,zblk,NAMSIZ)) benign_badh(h,"garbage instead of nulls (linkname)"); if (bcmp(h->devmajor,zblk,8)) benign_badh(h,"garbage instead of nulls (devmajor)"); if (bcmp(h->devminor,zblk,8)) benign_badh(h,"garbage instead of nulls (devminor)"); break; default: if (gnu_read) { switch (linkflag) { default: tocent.type = TYPE_NORMAL; benign_badh(h,"bad linkflag"); break; case LF_GNU_DUMPDIR: /* We print in this case because this conversion involves throwing away information. */ fprintf(stderr, "%s: converting GNU dumpdir to plain directory\n", __progname); tocent.type = TYPE_DIRECTORY; break; case LF_GNU_MULTIVOL: fprintf(stderr,"%s: skipping GNU multi-volume entry\n", __progname); tocent.type = TYPE_SKIP; break; case LF_GNU_NAMES: fprintf(stderr,"%s: skipping GNU old names entry\n", __progname); tocent.type = TYPE_SKIP; break; case LF_GNU_VOLHDR: fprintf(stderr,"%s: skipping GNU volume header entry\n", __progname); tocent.type = TYPE_SKIP; break; case LF_GNU_LONGLINK: case LF_GNU_LONGNAME: tocent.type = linkflag; /* XXX */ longhdr = 1; break; case LF_GNU_SPARSE: { tocent.tblocks = how_many(tocent.size,TBLOCK); isonum(&h->gnurealsize[0],12,&tocent.size); tocent.type = TYPE_SPARSE; xphase = XPH_MAP; free_map(xmap); xmap = 0; xmaptail = ⟼ for (i=0;(i<4)&&gnu_sparse_piece(&h->gnusparse[12*2*i]);i++) ; xsizeleft = h->gnuext; /* GNU BUG? extension sparse descriptor blocks aren't counted in the original tocent.size(!) */ if (xsizeleft) tocent.tblocks ++; } break; } } else { switch (linkflag) { default: tocent.type = TYPE_NORMAL; benign_badh(h,"bad linkflag"); break; case LF_MOUSE_LONG: fatal_badh(h,"recursively long header"); break; case LF_MOUSE_SPARSE: { int nseg; int ndblk; tocent.type = TYPE_SPARSE; if (bcmp(h->devmajor,zblk,8)) benign_badh(h,"garbage instead of nulls (devmajor)"); if (bcmp(h->devminor,zblk,8)) benign_badh(h,"garbage instead of nulls (devminor)"); if (! nullterms(h->linkname,NAMSIZ)) benign_badh(h,"garbage instead of nulls (linkname)"); if (sscanf(h->linkname,"%o%o",&nseg,&ndblk) != 2) { fatal_badh(h,"invalid linkname in sparse header"); } tocent.tblocks = how_many(nseg*32,TBLOCK) + ndblk; tocent.mapsize = nseg; } break; case LF_MOUSE_KILL: if (bcmp(h->mode,"000000 ",8)) benign_badh(h,"incorrect kill entry (mode)"); if (bcmp(h->uid,"000000 ",8)) benign_badh(h,"incorrect kill entry (uid)"); if (bcmp(h->gid,"000000 ",8)) benign_badh(h,"incorrect kill entry (gid)"); if (bcmp(h->size,"00000000000 ",12)) benign_badh(h,"incorrect kill entry (size)"); if (bcmp(h->mtime,"00000000000 ",12)) benign_badh(h,"incorrect kill entry (mtime)"); if (bcmp(h->linkname,zblk,NAMSIZ)) benign_badh(h,"garbage instead of nulls (linkname)"); tocent.tblocks = 0; tocent.type = TYPE_KILL; break; case LF_MOUSE_IDLE: return(GH_IDLE); break; } } break; } if (! nullterms(h->name,NAMSIZ)) benign_badh(h,"garbage instead of nulls (name)"); replacestr(&tocent.xname,copynofstr(h->name,NAMSIZ)); if (bcmp(zblk,(char *)(h+1),TBLOCK-sizeof(HEADER))) benign_badh(h,"garbage instead of nulls (padding)"); return(longhdr?GH_LONG:GH_GOOD); } static void read_long_gnu(char **strp) { int i; char *t; t = malloc((tocent.tblocks*TBLOCK)+1); for (i=0;i= 0) { free(tocent.xlinkname); tocent.xlinkname = malloc(ll+1); bcopy(t+nl,tocent.xlinkname,ll); tocent.xlinkname[ll] = '\0'; } if ((tocent.type != TYPE_DIRECTORY) && slashterms(tocent.xname,-1)) { fprintf(stderr,"%s: non-directory ends with a slash in long header\n",__progname); abort(); } if ((tocent.type == TYPE_DIRECTORY) && !slashterms(tocent.xname,-1)) { fprintf(stderr,"%s: directory doesn't end with a slash in long header\n",__progname); abort(); } } /* Add a string to a STRCHAIN. */ static void add_str_to_chain(char *s, STRCHAIN **listp) { STRCHAIN *r; r = NEW(STRCHAIN); r->link = *listp; *listp = r; r->s = s; } /* Add a string and an int to a STRINTCHAIN. */ static void add_str_int_to_chain(char *s, int i, STRINTCHAIN **listp) { STRINTCHAIN *r; r = NEW(STRINTCHAIN); r->link = *listp; *listp = r; r->s = s; r->i = i; } /* Pop a string off a STRCHAIN. */ static char *pop_strchain(STRCHAIN **listp) { STRCHAIN *t; char *rv; t = *listp; *listp = t->link; rv = t->s; OLD(t); return(rv); } /* Pop a STRINTCHAIN. */ /* Assumes the string has already been freed, if necessary. */ static void pop_strintchain(STRINTCHAIN **listp) { STRINTCHAIN *t; t = *listp; *listp = t->link; OLD(t); } /* Reverse the order of entries in a STRCHAIN. */ static STRCHAIN *rev_strchain(STRCHAIN *l) { STRCHAIN *rv; STRCHAIN *t; rv = 0; while (l) { t = l->link; l->link = rv; rv = l; l = t; } return(rv); } /* Reverse the order of entries in a STRINTCHAIN. */ static STRINTCHAIN *rev_strintchain(STRINTCHAIN *l) { STRINTCHAIN *rv; STRINTCHAIN *t; rv = 0; while (l) { t = l->link; l->link = rv; rv = l; l = t; } return(rv); } /* Check to see if we've matched a K flag argument. If so, pop the entry off the Kflag chain and set Kon appropriately. */ static void Kmatch(const char *path) { STRINTCHAIN *t; if (!Kflag || (Kflag->s[0] && strcmp(Kflag->s,path))) return; t = Kflag; Kflag = t->link; if (DEBUG(KFILTER)) fprintf(debugf,"K match (action %c): %s\n",t->i,t->s); switch (t->i) { default: fprintf(stderr,"%s: internal error: bad K action `%c'\n",__progname,t->i); abort(); break; case K_ACTION_DEFAULT: Kon = !Kflag; break; case K_ACTION_NONE: break; case K_ACTION_START: Kon = 1; break; case K_ACTION_STOP: Kon = 0; break; } OLD(t); } /* Add a K argument to the Kflag chain. */ static void add_K_option(char *arg) { STRINTCHAIN *t; t = NEW(STRINTCHAIN); t->link = Kflag; Kflag = t; switch (*arg++) { default: t->i = K_ACTION_DEFAULT; arg --; break; case '@': t->i = K_ACTION_DEFAULT; break; case '=': t->i = K_ACTION_NONE; break; case '+': t->i = K_ACTION_START; break; case '-': t->i = K_ACTION_STOP; break; } t->s = arg; } /* Handle an initial zero-length K argument, if present. */ static void fixup_K(void) { STRINTCHAIN *l; STRINTCHAIN *t; l = Kflag; Kflag = 0; while (l) { t = l; l = l->link; t->link = Kflag; Kflag = t; } if (DEBUG(KFILTER)) { for (t=Kflag;t;t=t->link) { fprintf(debugf,"K argument %c%s\n",t->i,t->s); } } Kon = !Kflag; if (Kflag && !Kflag->s[0]) Kmatch(""); if (DEBUG(KFILTER)) fprintf(debugf,"K initial state: %s\n",Kon?"on":"off"); } /* * This is a fairly central routine. It scans an archive, calling * (*eachf) at the beginning of each entry, (*eachb) for each block of * a file (if the entry is a file), and (*eache) at the end. (Entries * with no data blocks will call (*eachf) and then (*eache) on seeing * the header block.) This handles the return values from * goodheader(), calling read_long_header as necessary and if a bad * header is found, skipping header blocks until a valid one is seen. * It also implements the M flag and handles archive EOF, and calls * out to routines to implement the A, K, and k flags. * * Return value is 0 if all goes well and 1 if not. */ static int scantape_(void (*eachf)(void), void (*eachb)(int), void (*eache)(void)) { int rv; int nskip; char *gnu_name; char *gnu_link; filen = 0; gnu_name = 0; gnu_link = 0; while (1) { if (! getblock(ihblk)) return(Mflag?(filen>0):1); if (filen <= 0) { switch (goodheader(iheader,1)) { case GH_NOGOOD: nskip = 0; while (1) { if (! getblock(ihblk)) { fprintf(stderr,"%s: skipped to EOF (%d block%s)\n", __progname,nskip,(nskip==1)?"":"s"); return(0); } nskip ++; rv = goodheader(iheader,0); if (rv == 0) continue; break; } switch (rv) { case 1: fprintf(stderr,"%s: skipped %d non-header block%s\n", __progname,nskip,(nskip==1)?"":"s"); break; case -1: fprintf(stderr, "%s: skipped to end-of-canister (%d block%s)\n", __progname,nskip,(nskip==1)?"":"s"); if (Mflag) continue; else return(0); break; } break; case GH_GOOD: break; case GH_LONG: if (gnu_read) { read_long_gnu((tocent.type==LF_GNU_LONGLINK)?&gnu_link:&gnu_name); continue; } else { read_long_header(); } break; case GH_IDLE: continue; break; case GH_EOF: if (Mflag) continue; else return(0); break; default: fprintf(stderr,"%s: INTERNAL ERROR: bad return from goodheader\n",__progname); abort(); break; } if (gnu_read) { if (gnu_name) { replacestr(&tocent.xname,gnu_name); gnu_name = 0; } if (gnu_link) { replacestr(&tocent.xlinkname,gnu_link); gnu_link = 0; } } filen = tocent.tblocks; if (Aflag) { killed = 0; } else if (tocent.type == TYPE_KILL) { addkill(tocent.xname); killed = 1; } else { killed = k_killed(tocent.xname); } if (! killed) Kmatch(tocent.xname); if (Kon && !killed) (*eachf)(); } else { filen --; if (Kon && !killed) (*eachb)(tocent.tblocks-1-filen); } if ((filen == 0) && Kon && !killed) (*eache)(); } } /* This simply loops over all the archive sources specified, calling scantape_ for each in turn. This is where "premature EOF" messages come from, when scantape_ fails. */ static void scantape(void (*eachf)(void), void (*eachb)(int), void (*eache)(void)) { STRCHAIN *t; while (tapenames) { t = tapenames; tapenames = t->link; openmt(t->s,0); if (scantape_(eachf,eachb,eache)) { fprintf(stderr,"%s: warning: %s: premature EOT\n",__progname,t->s); } close(imt); } } /* Beginning of a file, t operation */ static void t_aux_f(void) { if (inarglist()) print_t_info(); maybe_lseek(); } /* Do a table-of-contents listing. Just scan the archive(s), ignoring everything but the beginning of each member. */ static void do_t(void) { scantape(t_aux_f,null_aux_b,null_aux_fe); } /* Called to abort the extraction of a file for some reason. This longjmp always jumps back to x_aux_f or C_aux_f. */ static void abortx(void) { longjmp(xabort,1); } /* Check to see if this is a rooted path, and if so whether we were told to handle them or not. */ static void check_rooted(void) { isrooted = 0; if (tocent.xname[0] == '/') { if (! rootname) { fprintf(stderr,"%s: %s: rooted path (use R key; see `man tar')\n",__progname,tocent.xname); abortx(); } isrooted = 1; if (rootnamelen > 0) { char *tmp; tmp = malloc(rootnamelen+strlen(tocent.xname)+1); sprintf(tmp,"%s%s",rootname,tocent.xname); free(tocent.xname); tocent.xname = tmp; } } } /* Copy one TOCENTRY to another. freefirst specifies whether the target TOCENTRY is to have all pointed-to strings freed first. */ static void copy_tocentry(TOCENTRY *from, TOCENTRY *to, int freefirst) { if (freefirst) { free(to->xname); free(to->xlinkname); } *to = *from; to->xname = copyofstr(from->xname); to->xlinkname = copyofstr(from->xlinkname); } /* Open the file for an x operation. Return 0 if all goes well; on failure, print a complaint and return 1. */ static int open_x_file(void) { if (Odest) { ffd = dup(Ofd); } else { if (lstat(xtoce.xname,&filestat) == 0) { switch (filestat.st_mode & S_IFMT) { default: if (unlink(xtoce.xname) < 0) { fprintf(stderr,"%s: can't unlink existing %s: %s\n",__progname,xtoce.xname,strerror(errno)); return(1); } break; case S_IFREG: case S_IFDIR: break; } } ffd = open(xtoce.xname,O_WRONLY|O_CREAT|O_TRUNC,0600); if (ffd < 0) { fprintf(stderr,"%s: can't create %s: %s\n",__progname,xtoce.xname,strerror(errno)); return(1); } } return(0); } /* Set the modes on a directory, after we're done with it. */ static void setmodes(char *pn, int mode, int ignorepflag) { struct stat stb; if (ignorepflag || !pflag) mode &= 0777 & ~oldumask; if ( (ignorepflag || (pflag < 2)) && (stat(pn,&stb) >= 0) && ((stb.st_mode & S_IFMT) == S_IFDIR) ) { mode = (mode & ~02000) | (stb.st_mode & 02000); } if (chmod(pn,mode) < 0) { fprintf(stderr,"%s: can't set %s to mode %o: %s\n",__progname,pn,mode,strerror(errno)); } } /* Do the directory stack, setting modes on directories we're done with and preparing to set modes on directories we're entering. */ static void dirstack_stuff(const char *path, int len, int direntp) { const char *dp; const char *dep; char *sp; char *pn; if (path == 0) path = ""; /* "%.*s",0,0 prints "(null)" with some printfs */ dp = path; dep = path + len; sp = dirss; if (DEBUG(DIRSTACK)) fprintf(debugf,"dirstack_stuff: dirss=%s string=%.*s (dirsp=%d)\n",sp,len,dp,dirsp); while ((dp < dep) && (*dp == *sp)) { dp ++; sp ++; } if (DEBUG(DIRSTACK)) fprintf(debugf,"dirstack_stuff: sp at %d, dp at %d\n",(int)(sp-dirss),(int)(dp-path)); if ((dp == dep) && !*sp) { if (DEBUG(DIRSTACK)) fprintf(debugf,"dirstack_stuff: dp==dep && !*sp, still in same directory\n"); if ((dirsp >= 0) && direntp) { modes[dirsp].info = 1; modes[dirsp].mode = tocent.mode; modes[dirsp].mtime = tocent.mtime; modes[dirsp].uid = tocent.uid; modes[dirsp].gid = tocent.gid; if (DEBUG(DIRSTACK)) fprintf(debugf,"dirstack_stuff: saving info\n"); } return; } if (*sp || (*dp != '/')) { if (DEBUG(DIRSTACK)) { int i; fprintf(debugf,"dirstack_stuff: *sp, dirsp=%d, modes[].end at [",dirsp); for (i=0;i<=dirsp;i++) fprintf(debugf,"%s%d",i?" ":"",(int)(modes[i].end-dirss)); fprintf(debugf,"]\n"); } while ( (dirsp >= 0) && ( (dp < dep) ? (modes[dirsp].end >= sp) : (modes[dirsp].end > sp) ) ) { *modes[dirsp].end = '\0'; if (DEBUG(DIRSTACK)) fprintf(debugf,"dirstack_stuff: dirsp=%d path=%s\n",dirsp,dirss); switch (modes[dirsp].setit) { case ROOTED: { char *buf; buf = malloc(strlen(rootname)+strlen(dirss)+1); sprintf(buf,"%s%s",rootname,dirss); pn = buf; } if (0) { case NORMAL: pn = dirss; } if (modes[dirsp].info) { if (DEBUG(DIRSTACK)) fprintf(debugf,"dirstack_stuff: setmodes(\"%s\",0%o,0);\n",pn,modes[dirsp].mode); setmodes(pn,modes[dirsp].mode,0); if (! mflag) { struct timeval tv[2]; tv[0].tv_sec = modes[dirsp].mtime; tv[0].tv_usec = 0; tv[1] = tv[0]; if (DEBUG(DIRSTACK)) fprintf(debugf,"dirstack_stuff: utimes(\"%s\",[%.24s]);\n",pn,ctime(&modes[dirsp].mtime)); if (utimes(pn,tv) < 0) { fprintf(stderr,"%s: error setting times on %s: %s\n",__progname,pn,strerror(errno)); } } if (DEBUG(DIRSTACK)) fprintf(debugf,"dirstack_stuff: (chown \"%s\" to %d.%d)\n",pn,modes[dirsp].uid,modes[dirsp].gid); chown(pn,modes[dirsp].uid,-1); chown(pn,-1,modes[dirsp].gid); } else if (modes[dirsp].made) { if (DEBUG(DIRSTACK)) fprintf(debugf,"dirstack_stuff: setmodes(\"%s\",0777,1);\n",pn); setmodes(pn,0777,1); } else { if (DEBUG(DIRSTACK)) fprintf(debugf,"dirstack_stuff: nil \"%s\"\n",pn); } if (pn != dirss) free(pn); break; } dirsp --; } if (dirsp < 0) dirss[0] = '\0'; } if ( (dp < dep) || ( (dp > path) && ((dirsp < 0) || ((dp-path) > (modes[dirsp].end-dirss))) ) ) { if (DEBUG(DIRSTACK)) { fprintf(debugf,"dirstack_stuff: path left "); if (dirsp < 0) { fprintf(debugf,"(dirsp=%d)\n",dirsp); } else { fprintf(debugf,"(last end at %d, dp at %d)\n",(int)(modes[dirsp].end-dirss),(int)(dp-path)); } } if ((dirsp < 0) && (sp > dirss)) dirss[0] = path[0]; while ((dp < dep) && (*dp == '/')) { *sp++ = '/'; dp ++; } for (;dp= 0) return; tmp = malloc(strlen(path)+1); strcpy(tmp,path); for (tp=tmp;*tp&&(*tp=='/');tp++) ; if (*tp) /* can this be untrue? stat() failed but all slashes? */ { do { while (*tp && (*tp != '/')) tp ++; c = *tp; *tp = '\0'; if ((mkdir(tmp,0777&~oldumask) < 0) && (errno != EEXIST)) { fprintf(stderr,"%s: can't make directory %s: %s\n",__progname,tmp,strerror(errno)); free(tmp); abortx(); } *tp = c; while (*tp == '/') tp ++; } while (c); } free(tmp); } /* * Prefix rootname to a path, if isrooted is true. Returns the new path, if * anything was done, or the argument, if not. In the former case, the * returned string is in malloc()ed memory but the caller must not free it; * it is good up until rootmap() is called again, at which point the previous * return value must be considered invalid - it may be freed or overwritten. */ static char *rootmap(char *p) { static char *buf = 0; static int buflen = -1; int l; if (!isrooted || (rootnamelen == 0)) return(p); l = rootnamelen + strlen(p); if (l > buflen) { free(buf); buflen = l; buf = malloc(buflen+1); } sprintf(buf,"%s%s",rootname,p); return(buf); } /* * Make directory prefixes as necessary for the directory in the dirstack. * Handles rooted paths. Returns trivially if the directory already exists, * otherwise walks down the dirstack making directories from the top down. * Errors other than EEXIST are reported and cause abortx() calls. */ static void makedirs(void) { char *rmap; char *rbase; char *cp; char *rp; char c; int i; struct stat stb; if (isrooted && (rootnamelen > 0)) make_dir_stupid(rootname); if (dirsp < 0) return; *modes[dirsp].end = '\0'; rmap = rootmap(&dirss[0]); if (stat(rmap,&stb) >= 0) return; rbase = rmap; if (isrooted) rbase += rootnamelen; for (i=0;i<=dirsp;i++) { cp = modes[i].end; /* don't mkdir anything with "", ".", or ".." as last component */ if (!( (cp == &dirss[0]) || (cp[-1] == '/') || ( (cp[-1] == '.') && ( (cp == &dirss[1]) || (cp[-2] == '/') || ( (cp[-2] == '.') && ( (cp == &dirss[2]) || (cp[-3] == '/') ) ) ) ) )) { rp = rbase + (cp-&dirss[0]); c = *rp; *rp = '\0'; if (mkdir(rbase,0700) < 0) { int err; err = errno; if (DEBUG(DIRSTACK)) fprintf(debugf,"makedirs %s: %s\n",rbase,strerror(err)); if (err != EEXIST) { fprintf(stderr,"%s: can't make directory %s: %s\n",__progname,rbase,strerror(err)); *rp = c; abortx(); } } else { if (DEBUG(DIRSTACK)) fprintf(debugf,"makedirs %s OK\n",rbase); modes[i].made = 1; } *rp = c; } } } /* Mark the stuff in the directory stack as needing its modes set. */ static void dssetmodes(void) { int i; if (DEBUG(DIRSTACK)) fprintf(debugf,"dssetmodes %s: dirsp=%d dirss=%s\n",isrooted?"ROOTED":"NORMAL",dirsp,dirss); for (i=0;i<=dirsp;i++) modes[i].setit = isrooted ? ROOTED : NORMAL; } /* * Read the symlink pointed to by path into allocated memory, storing a * pointer to the memory into *slbufp and the link-to length into * *llp. If an unexpected syscall error occurs and errf is non-nil, * call (*errf)(path,errno). If fsalways is true, always use * readlink() rather than i_readlink(). * * This function has some icky code to deal with various systems' * responses to attempts to read a link when the link-to string is * longer than the provided buffer. I've seen: * * - Return the actual length of the link-to string * - Return the buffer size * - Return 0 * - Return -1 with errno set to ERANGE * * The first of these is the most useful; this code attempts to deal * with all of them at once. In particular, the third response is * especially annoying, because it means there is no way to tell * whether we have a zero-length link-to or a too-long link-to. * Fortunately, zero-length link-tos are rare enough we don't * especially mind a mildly expensive hack in this case. * * I suppose a case could be made that rather than trying to handle all * cases in a single function, this should be controlled by a * configuration define. I'm reluctant largely because the penalty if * the wrong define is used is likely to be silent failures. */ static int readsymlink(const char *path, char **slbufp, int *llp, void (*errf)(const char *, int), int fsalways) { char *slbuf; int nsl; int ll; nsl = 64; slbuf = malloc(nsl+1); while (1) { ll = fsalways ? readlink(path,slbuf,nsl) : i_readlink(path,slbuf,nsl); if (ll == 0) { #ifndef TAR_INODE_NONE if (fsalways || (iflag != I_FULL)) #endif { free(slbuf); slbuf = malloc(10000); ll = readlink(path,slbuf,9999); nsl = 10000; } if (ll == 0) /* assume a zero-length link-to */ { free(slbuf); slbuf = malloc(1); break; } } if (ll < 0) { if (errno == ERANGE) { ll = nsl; } else { int e; e = errno; free(slbuf); errno = e; if (errf) (*errf)(path,errno); return(0); } } if (ll < nsl) break; nsl = (ll>nsl) ? (ll+1) : (nsl*2); free(slbuf); slbuf = malloc(nsl+1); } slbuf[ll] = '\0'; *slbufp = slbuf; *llp = ll; return(1); } static void x_unlink(const char *path) { #ifdef ROOT_UNLINK_BUG static int euid = -1; if (euid < 0) euid = geteuid(); if (euid == 0) { if (rmdir(path) >= 0) return; } else { errno = ENOTDIR; } switch (errno) { case ENOTDIR: if (unlink(path) >= 0) return; if (errno == ENOENT) return; fprintf(stderr,"%s: can't unlink existing %s: %s\n",__progname,tocent.xname,strerror(errno)); abortx(); break; case ENOENT: return; break; default: fprintf(stderr,"%s: can't unlink existing %s: %s\n",__progname,tocent.xname,strerror(errno)); abortx(); break; } #else if (unlink(path) >= 0) return; if (errno == ENOENT) return; fprintf(stderr,"%s: can't unlink existing %s: %s\n",__progname,tocent.xname,strerror(errno)); abortx(); #endif } /* Grow a buffer if necessary */ static void allocbuf(char **bpp, int *lenp, int len) { if (*lenp < len) { *lenp = len; *bpp = realloc(*bpp,len+1); } } /* * About to extract a file. Check that the pathname is free from * certain evil things that do not normally appear in tar archives, * but could, and would be unpleasant. We walk the path, following * any symlinks that exist in the filesystem (thereby catching * archives that, eg, contain a symlink ./foo->/etc and then a file * ./foo/passwd). If we follow a symlink to an absolute path, or if * we ever try to ../ up out of our current directory, we print a * complaint and skip the extraction of this archive member. Also, we * refuse attempts to hard-link to anything other than a plain file. * * This code is full of potential races, but we aren't trying to * protect against races between tars extracting and other processes * meddling, only against extracting archives that contain evil * things. The idea is that rather than doing a tar tvf of the * archive and eyeball-scanning for evil things, extract with j and * let tar do the checking. */ static void xpath_paranoia(void) { static char *pbuf = 0; static int pbuflen = -1; static char *dbuf = 0; static int dbuflen = -1; int dl; char *slash; char *pbp; int l; struct stat stb; if (DEBUG(J)) fprintf(debugf,"j: input path %s\n",tocent.xname); if (tocent.xname[0] == '/') { if (DEBUG(J)) fprintf(debugf,"j: bad, absolute path\n"); fprintf(stderr,"%s: skipping %s: absolute path\n",__progname,tocent.xname); abortx(); } if (tocent.type == TYPE_HARDLINK) { if ( (lstat(tocent.xlinkname,&stb) >= 0) && ((stb.st_mode & S_IFMT) != S_IFREG) ) { if (DEBUG(J)) fprintf(debugf,"j: bad, hardlink to non-file %s\n",tocent.xlinkname); fprintf(stderr,"%s: skipping %s: hardlink to non-file %s\n",__progname,tocent.xname,tocent.xlinkname); abortx(); } } allocbuf(&pbuf,&pbuflen,strlen(tocent.xname)); strcpy(pbuf,tocent.xname); allocbuf(&dbuf,&dbuflen,1); strcpy(dbuf,"."); dl = 1; pbp = pbuf; while (1) { slash = index(pbp,'/'); if (slash == 0) { if (DEBUG(J)) fprintf(debugf,"j: ok, dbuf=%s pbp=%s\n",dbuf,pbp); return; } l = slash - pbp; *slash++ = '\0'; if (DEBUG(J)) fprintf(debugf,"j: loop, dbuf=%s dl=%d pbp=%s slash=%s\n",dbuf,dl,pbp,slash); if ( (l == 0) || ((l == 1) && (pbp[0] == '.')) ) { if (DEBUG(J)) fprintf(debugf,"j: ignoring, nil or .\n"); pbp = slash; } else if ((l == 2) && (pbp[0] == '.') && (pbp[1] == '.')) { char *ds; if (DEBUG(J)) fprintf(debugf,"j: .. entry\n"); ds = rindex(dbuf,'/'); if (ds == 0) { if (DEBUG(J)) fprintf(debugf,"j: bad, would .. out of .\n"); fprintf(stderr,"%s: skipping %s: would .. up out of .\n",__progname,tocent.xname); abortx(); } else { *ds = '\0'; dl = ds - dbuf; if (DEBUG(J)) fprintf(debugf,"j: .. ok, dbuf=%s\n",dbuf); } pbp = slash; } else { allocbuf(&dbuf,&dbuflen,dl+1+l); sprintf(dbuf+dl,"/%s",pbp); dl += 1 + l; if (DEBUG(J)) fprintf(debugf,"j: moved, dbuf=%s dl=%d\n",dbuf,dl); if ( (lstat(dbuf,&stb) >= 0) && ((stb.st_mode & S_IFMT) == S_IFLNK) ) { char *lbuf; int llen; if (DEBUG(J)) fprintf(debugf,"j: is symlink\n"); if (readsymlink(dbuf,&lbuf,&llen,0,1)) { char *ds; if (DEBUG(J)) fprintf(debugf,"j: link to %.*s\n",llen,lbuf); if ((llen > 0) && (lbuf[0] == '/')) { if (DEBUG(J)) fprintf(debugf,"j: bad, absolute symlink\n"); fprintf(stderr,"%s: skipping %s: would follow symlink to absolute path\n",__progname,tocent.xname); free(lbuf); abortx(); } if (llen+1 > slash-pbuf) { int sl; int sn; sl = strlen(slash); sn = slash - pbuf; allocbuf(&pbuf,&pbuflen,llen+1+sn+sl); slash = pbuf + sn; bcopy(slash,pbuf+llen+1,sl+1); bcopy(lbuf,pbuf,llen); pbuf[llen] = '/'; pbp = pbuf; if (DEBUG(J)) fprintf(debugf,"j: reallocated, pbp=%s\n",pbp); } else { pbp = slash - (llen+1); bcopy(lbuf,pbp,llen); slash[-1] = '/'; if (DEBUG(J)) fprintf(debugf,"j: had space, pbp=%s\n",pbp); } free(lbuf); ds = rindex(dbuf,'/'); if (ds) { *ds = '\0'; dl = ds - dbuf; if (DEBUG(J)) fprintf(debugf,"j: dbuf=%s for symlink\n",dbuf); } else { fprintf(stderr,"%s: INTERNAL ERROR: no slash in symlink dbuf\n",__progname); if (DEBUG(J)) abort(); abortx(); } } else { if (DEBUG(J)) fprintf(debugf,"j: readsymlink failed (%s)\n",strerror(errno)); } } else { pbp = slash; } } } } /* * Extracting a file. First, maintain the directory stack (if we don't * actually extract anything under a directory, it gets popped before * we set its modes). Then check to see if we want the file, and if * not return. Then set a setjmp in case we discover we can't do it * after all, then print a line if v was used, check to see if its a * rooted path and make directories and mark them as needing to have * their modes set. Then switch on the type of the entry and extract * it as appropriate to its type. SPARSE needs a little care; x_aux_b * normally handles everything, but if the file is entirely a hole, * there are no data blocks, so we have to do a little stuff x_aux_b * would normally do. */ static void x_aux_f(void) { if (! Odest) { char *sp; int l; if (tocent.type == TYPE_DIRECTORY) { l = strlen(tocent.xname); while ((l > 0) && (tocent.xname[l-1] == '/')) l --; dirstack_stuff(tocent.xname,l,1); } else { sp = rindex(tocent.xname,'/'); if (sp) { dirstack_stuff(tocent.xname,sp-tocent.xname,0); } else { dirstack_stuff(0,0,0); } } } interested = 0; if (!inarglist() || setjmp(xabort)) { interested = 0; maybe_lseek(); return; } if (vflag) { switch (tocent.type) { case TYPE_NORMAL: case TYPE_CONTIG: fprintf(vfp,"x %s, %d byte%s, %d tape block%s\n", tocent.xname, tocent.size,(tocent.size==1)?"":"s", tocent.tblocks,(tocent.tblocks==1)?"":"s" ); break; case TYPE_DIRECTORY: fprintf(vfp,"x %s directory\n",tocent.xname); break; case TYPE_HARDLINK: fprintf(vfp,"x %s linked to %s\n",tocent.xname,tocent.xlinkname); break; case TYPE_SOFTLINK: fprintf(vfp,"x %s symbolic link to %s\n",tocent.xname,tocent.xlinkname); break; case TYPE_CSPECIAL: fprintf(vfp,"x %s character special device (%d,%d)\n",tocent.xname,otoi(&tocent.devmajor[0]),otoi(&tocent.devminor[0])); break; case TYPE_BSPECIAL: fprintf(vfp,"x %s block special device (%d,%d)\n",tocent.xname,otoi(&tocent.devmajor[0]),otoi(&tocent.devminor[0])); break; case TYPE_FIFO: fprintf(vfp,"x %s FIFO\n",tocent.xname); break; case TYPE_SPARSE: fprintf(vfp,"x %s, %d byte%s, %d-seg sparse, %d tape block%s\n", tocent.xname, tocent.size,(tocent.size==1)?"":"s", tocent.mapsize, tocent.tblocks,(tocent.tblocks==1)?"":"s" ); break; case TYPE_KILL: fprintf(vfp,"x %s kill entry\n",tocent.xname); break; default: fprintf(stderr,"%s: %s: INTERNAL BUG: bad type %d [x_aux_f 1]\n",__progname,tocent.xname,tocent.type); break; } fflush(vfp); } if (! Odest) { switch (tocent.type) { case TYPE_KILL: break; default: if (jflag) xpath_paranoia(); check_rooted(); makedirs(); dssetmodes(); break; } } switch (tocent.type) { case TYPE_NORMAL: case TYPE_CONTIG: copy_tocentry(&tocent,&xtoce,1); if (open_x_file()) abortx(); xsizeleft = tocent.size; xzblocks = 0; interested = 1; break; case TYPE_DIRECTORY: break; case TYPE_HARDLINK: if (Odest) { fprintf(stderr,"%s: can't extract hard link with O flag\n",__progname); } else { x_unlink(tocent.xname); if (link(tocent.xlinkname,tocent.xname) < 0) { fprintf(stderr,"%s: can't link %s to %s: %s\n",__progname,tocent.xname,tocent.xlinkname,strerror(errno)); abortx(); } } break; case TYPE_SOFTLINK: if (Odest) { fprintf(stderr,"%s: can't extract symlink with O flag\n",__progname); } else { x_unlink(tocent.xname); umask((0777&~tocent.mode)|(pflag?0:oldumask)); if (symlink(tocent.xlinkname,tocent.xname) < 0) { fprintf(stderr,"%s: can't symlink %s to %s: %s\n",__progname,tocent.xname,tocent.xlinkname,strerror(errno)); abortx(); } umask(0); #ifdef SYMLINK_CHOWN_CHOWN chown(tocent.xname,tocent.uid,-1); chown(tocent.xname,-1,tocent.gid); #endif #ifdef SYMLINK_CHOWN_LCHOWN lchown(tocent.xname,tocent.uid,-1); lchown(tocent.xname,-1,tocent.gid); #endif } break; case TYPE_CSPECIAL: #ifdef NO_DEVICES fprintf(stderr,"%s: can't extract character special device in this version\n",__progname); #else if (Odest) { fprintf(stderr,"%s: can't extract character special device with O flag\n",__progname); } else { x_unlink(tocent.xname); if (mknod(tocent.xname,(tocent.mode&07777)|S_IFCHR,makedev(otoi(&tocent.devmajor[0]),otoi(&tocent.devminor[0]))) < 0) { fprintf(stderr,"%s: can't make device %s: %s\n",__progname,tocent.xname,strerror(errno)); abortx(); } chown(tocent.xname,tocent.uid,-1); chown(tocent.xname,-1,tocent.gid); } #endif break; case TYPE_BSPECIAL: #ifdef NO_DEVICES fprintf(stderr,"%s: can't extract block special device in this version\n",__progname); #else if (Odest) { fprintf(stderr,"%s: can't extract block special device with O flag\n",__progname); } else { x_unlink(tocent.xname); if (mknod(tocent.xname,(tocent.mode&07777)|S_IFBLK,makedev(otoi(&tocent.devmajor[0]),otoi(&tocent.devminor[0]))) < 0) { fprintf(stderr,"%s: can't make device %s: %s\n",__progname,tocent.xname,strerror(errno)); abortx(); } chown(tocent.xname,tocent.uid,-1); chown(tocent.xname,-1,tocent.gid); } #endif break; case TYPE_FIFO: if (Odest) { fprintf(stderr,"%s: can't extract FIFO with O flag\n",__progname); } else { #ifdef S_IFIFO x_unlink(tocent.xname); if (mkfifo(tocent.xname,tocent.mode&07777) < 0) { fprintf(stderr,"%s: can't make FIFO %s: %s\n",__progname,tocent.xname,strerror(errno)); abortx(); } chown(tocent.xname,tocent.uid,-1); chown(tocent.xname,-1,tocent.gid); #else fprintf(stderr,"%s: can't extract FIFO %s on this system\n", __progname,tocent.xname); #endif } break; case TYPE_SPARSE: if (DEBUG(SPARSE)) fprintf(debugf,"beginning x of sparse file %s\n",tocent.xname); copy_tocentry(&tocent,&xtoce,1); xphase = XPH_MAP; free_map(xmap); xmap = 0; xmaptail = ⟼ xsizeleft = xtoce.mapsize; interested = 1; if (xtoce.mapsize == 0) { /* special case - must get open_x_file called before x_aux_e, and in this case, x_aux_b will never get called! */ if (xtoce.tblocks != 0) { fprintf(stderr,"%s: internal error: sparse mapsize==0 but tblocks==%d\n",__progname,xtoce.tblocks); interested = 0; if (DEBUG(SPARSE)) fprintf(debugf,"sparse extract aborted (mapsize 0 tblocks %d)\n",xtoce.tblocks); } else if (open_x_file()) { interested = 0; if (DEBUG(SPARSE)) fprintf(debugf,"sparse extract aborted (open_x_file failed)\n"); } } break; case TYPE_KILL: fprintf(ifp,"%s: kill entry, ignored\n",tocent.xname); abortx(); break; default: fprintf(stderr,"%s: %s: INTERNAL BUG: bad type %d [x_aux_f 2]\n",__progname,tocent.xname,tocent.type); abortx(); break; } } /* Just like write(), but checks the return value and prints complaints if the write didn't completely succeed. Returns 0 if ok, 1 if error. */ static int chkwrite(int fd, const void *buf, int nb, const char *fn) { int rv; rv = nb ? write(fd,buf,nb) : 0; if (rv != nb) { if (rv < 0) { fprintf(stderr,"%s: error writing %s: %s\n",__progname,fn,strerror(errno)); } else { fprintf(stderr,"%s: short write on %s (tried %d wrote %d)\n",__progname,fn,nb,rv); } return(1); } return(0); } static void xmapterm(void) { EXTDESC *xd; xd = NEW(EXTDESC); xd->start = 0; xd->length = 0; xd->link = 0; *xmaptail = xd; } /* Handle a data block that contains segment map descriptors. tag is used only for messages. Loop over the descriptors, adding them to the map; when we run out of descriptors, switch over to XPH_DATA and set up for the first segment. */ static int standard_xph_map(const char *tag) { int n; char *bp; EXTDESC *xd; n = TBLOCK / 32; if (n > xsizeleft) n = xsizeleft; xsizeleft -= n; bp = &ihblk[0]; for (;n>0;n--) { xd = NEW(EXTDESC); sscanf(bp,"%o%o",&xd->start,&xd->length); *xmaptail = xd; xmaptail = &xd->link; if (DEBUG(SPARSE)) fprintf(debugf,"sparse map entry: %d at %d\n",xd->length,xd->start); bp += 32; } *xmaptail = 0; if (xsizeleft < 1) { int nseg; int nb; xphase = XPH_DATA; xsizeleft = xmap->length; xmapterm(); nseg = 0; nb = 0; for (xd=xmap;xd->link;xd=xd->link) { nseg ++; nb += how_many(xd->length,TBLOCK); if (xd->start % TBLOCK) { fprintf(stderr,"%s: error in %s: segment %d start (%d) isn't a multiple of %d\n",__progname,xtoce.xname,nseg,xd->start,TBLOCK); interested = 0; if (DEBUG(SPARSE)) fprintf(debugf,"sparse %s aborted (seg %d start is %d)\n",tag,nseg,xd->start); return(0); } if ((xd->length % TBLOCK) && xd->link->length) { fprintf(stderr,"%s: error in %s: non-last segment %d length (%d) isn't a multiple of %d\n",__progname,xtoce.xname,nseg,xd->length,TBLOCK); interested = 0; if (DEBUG(SPARSE)) fprintf(debugf,"sparse %s aborted (seg %d length is %d)\n",tag,nseg,xd->length); return(0); } } if (DEBUG(SPARSE)) fprintf(debugf,"sparse map total: segs %d dblks %d\n",nseg,nb); nb += how_many(nseg*32,TBLOCK); if (nb != xtoce.tblocks) { fprintf(stderr,"%s: tape block counts for %s don't match: header needs %d, map needs %d\n",__progname,xtoce.xname,xtoce.tblocks,nb); interested = 0; if (DEBUG(SPARSE)) fprintf(debugf,"sparse %s aborted (hdr tblocks %d, map %d)\n",tag,xtoce.tblocks,nb); return(0); } xzblocks = xmap->start / TBLOCK; return(1); } return(0); } /* Handle a data block when extracting. Switch on the type of the thing being extracted and handle the data block accordingly, checking for blocks of NULs if extracting with S. */ static void x_aux_b(UNUSED(int n)) { if (! interested) return; switch (xtoce.type) { default: abort(); break; case TYPE_NORMAL: case TYPE_CONTIG: { int wrv; int nw; nw = (xsizeleft < TBLOCK) ? xsizeleft : TBLOCK; xsizeleft -= nw; if (Sflag && !bcmp(ihblk,zblk,TBLOCK)) { xzblocks ++; return; } if (xzblocks > 0) { lseek(ffd,xzblocks*(off_t)TBLOCK,L_INCR); xzblocks = 0; } wrv = nw ? write(ffd,ihblk,nw) : 0; if (wrv != nw) { if (wrv < 0) { fprintf(stderr,"%s: error writing %s: %s\n",__progname,xtoce.xname,strerror(errno)); } else { fprintf(stderr,"%s: short write on %s\n",__progname,xtoce.xname); } close(ffd); interested = 0; } } break; case TYPE_SPARSE: switch (xphase) { default: abort(); break; case XPH_MAP: if (standard_xph_map("extract")) { if (open_x_file()) { interested = 0; if (DEBUG(SPARSE)) fprintf(debugf,"sparse extract aborted (open_x_file failed)\n"); } } break; case XPH_DATA: { int nw; if (! xmap->link) { fprintf(stderr,"%s: internal error extracting sparse %s: more data than map wants\n",__progname,xtoce.xname); close(ffd); interested = 0; if (DEBUG(SPARSE)) fprintf(debugf,"sparse extract aborted (more data than map)\n"); return; } nw = (xsizeleft < TBLOCK) ? xsizeleft : TBLOCK; xsizeleft -= nw; if (Sflag && ((xsizeleft > 0) || xmap->link) && !bcmp(ihblk,zblk,TBLOCK)) { xzblocks ++; } else { if (xzblocks > 0) { if (Odest) { for (;xzblocks>0;xzblocks--) write(ffd,&zblk[0],TBLOCK); } else { lseek(ffd,xzblocks*(off_t)TBLOCK,L_INCR); } xzblocks = 0; } if (chkwrite(ffd,ihblk,nw,xtoce.xname)) { close(ffd); interested = 0; if (DEBUG(SPARSE)) fprintf(debugf,"sparse extract aborted (write failed)\n"); return; } } if (xsizeleft < 1) { EXTDESC *t; t = xmap->link; if (! t) { fprintf(stderr,"%s: internal error extracting sparse %s: ran out of map\n",__progname,xtoce.xname); close(ffd); interested = 0; if (DEBUG(SPARSE)) fprintf(debugf,"sparse extract aborted (out of map)\n"); return; } else { if (t->link) { xzblocks += (t->start - (xmap->start+xmap->length)) / TBLOCK; } else if (Odest) { int i; i = xtoce.size - (xmap->start+xmap->length); for (;i>=TBLOCK;i-=TBLOCK) { if (chkwrite(ffd,zblk,TBLOCK,xtoce.xname)) { close(ffd); interested = 0; if (DEBUG(SPARSE)) fprintf(debugf,"sparse extract aborted (padding write failed)\n"); return; } } if (chkwrite(ffd,zblk,i,xtoce.xname)) { close(ffd); interested = 0; if (DEBUG(SPARSE)) fprintf(debugf,"sparse extract aborted (padding write failed)\n"); return; } } OLD(xmap); xmap = t; xsizeleft = xmap->length; } } } break; } break; } } /* End of extracting a file. Make sure it's the right size and close it. On some systems, ftruncate() won't extend files, only shrink them. On such a system, to create a hole at the end of a file, we seek a megabyte past where we want EOF to be, write one byte, and then ftruncate() back to the size we want. The seek far past the end is an attempt to ensure that any blocks allocated because of the write get completely dropped in the ftruncate. We may end up with an indirect block full of nil block pointers, but we can't fix everything; systems without any other way to put a hole at the end of a file will just have to live with that until/unless someone gives me a better idea. ftruncate_behavior is a cached version of how ftruncate() has been observed to behave. When trying to extend a file, if it's UNKNOWN we try a simple ftruncate and see if it works, and if so set it to CAN_GROW and if not to SHRINK_ONLY. If it's already set to PARANOID, we always do the complicated way - see the manpage for more. */ static void x_aux_e(void) { struct timeval tv[2]; if (! interested) return; if (! Odest) { int must_extend; unsigned long int oldsize; struct stat stb; must_extend = 0; switch (ftruncate_behavior) { case FTRUNC_UNKNOWN: if (DEBUG(FTRUNCATE)) fprintf(debugf,"ftruncate: state=unknown\n"); if (0) { case FTRUNC_PARANOID: if (DEBUG(FTRUNCATE)) fprintf(debugf,"ftruncate: state=paranoid\n"); } if (0) { default:; if (DEBUG(FTRUNCATE)) fprintf(debugf,"ftruncate: state=%d (set to paranoid)\n",ftruncate_behavior); fprintf(stderr,"%s: warning: INTERNAL ERROR: ftruncate_behavior = %d\n",__progname,ftruncate_behavior); ftruncate_behavior = FTRUNC_PARANOID; } oldsize = lseek(ffd,0,L_INCR); if (oldsize < xtoce.size) { if (ftruncate(ffd,xtoce.size) < 0) { int e; e = errno; fprintf(stderr,"%s: warning: can't set size of %s: %s\n",__progname,xtoce.xname,strerror(e)); if (DEBUG(FTRUNCATE)) fprintf(debugf,"ftruncate: %lu->%lu failed: %s\n",oldsize,(unsigned long int)xtoce.size,strerror(e)); } else { fstat(ffd,&stb); if (DEBUG(FTRUNCATE)) fprintf(debugf,"ftruncate: %lu set %lu->%lu\n",(unsigned long int)xtoce.size,oldsize,(unsigned long int)stb.st_size); if (stb.st_size == xtoce.size) { if (ftruncate_behavior == FTRUNC_UNKNOWN) { ftruncate_behavior = FTRUNC_CAN_GROW; if (DEBUG(FTRUNCATE)) fprintf(debugf,"ftruncate: shifted to can-grow\n"); } } else { if (ftruncate_behavior == FTRUNC_UNKNOWN) { ftruncate_behavior = FTRUNC_SHRINK_ONLY; if (DEBUG(FTRUNCATE)) fprintf(debugf,"ftruncate: shifted to shrink-only\n"); } must_extend = 1; } } } break; case FTRUNC_CAN_GROW: if (DEBUG(FTRUNCATE)) fprintf(debugf,"ftruncate: state=can-grow\n"); if (ftruncate(ffd,xtoce.size) < 0) { int e; e = errno; fprintf(stderr,"%s: warning: can't set size of %s: %s\n",__progname,xtoce.xname,strerror(e)); if (DEBUG(FTRUNCATE)) fprintf(debugf,"ftruncate: %lu->%lu failed: %s\n",oldsize,(unsigned long int)xtoce.size,strerror(e)); } break; case FTRUNC_SHRINK_ONLY: if (DEBUG(FTRUNCATE)) fprintf(debugf,"ftruncate: state=shrink-only\n"); must_extend = 1; break; } if (must_extend) { lseek(ffd,xtoce.size+(off_t)(1L<<20),L_SET); write(ffd,"",1); if (ftruncate(ffd,xtoce.size) < 0) { int e; e = errno; fprintf(stderr,"%s: warning: can't set size of %s: %s\n",__progname,xtoce.xname,strerror(e)); if (DEBUG(FTRUNCATE)) fprintf(debugf,"ftruncate: 1Mb shrink to %lu failed: %s\n",(unsigned long int)xtoce.size,strerror(e)); } } fchown(ffd,xtoce.uid,-1); fchown(ffd,-1,xtoce.gid); if (fchmod(ffd,pflag?xtoce.mode:(xtoce.mode&0777&~oldumask)) < 0) { fprintf(stderr,"%s: error setting mode of %s: %s\n",__progname,xtoce.xname,strerror(errno)); } } if (close(ffd) < 0) { fprintf(stderr,"%s: error closing %s: %s\n",__progname,xtoce.xname,strerror(errno)); } if (DEBUG(SPARSE)) fprintf(debugf,"sparse extract done\n"); if (DEBUG(MODETIME)) fprintf(debugf,"x_aux_e: mflag %d\n",mflag); if (!mflag && !Odest) { tv[0].tv_sec = xtoce.mtime; tv[0].tv_usec = 0; tv[1] = tv[0]; if (DEBUG(MODETIME)) fprintf(debugf,"calling utimes on %s to %s",xtoce.xname,ctime(&xtoce.mtime)); if (utimes(xtoce.xname,tv) < 0) { fprintf(stderr,"%s: error setting times on %s: %s\n",__progname,xtoce.xname,strerror(errno)); } } interested = 0; } /* Do an x pass. Scan the tape, then finish up anything left on the directory stack. */ static void do_x(void) { scantape(x_aux_f,x_aux_b,x_aux_e); if (! Odest) { dirstack_stuff(0,0,0); } } /* Open a file on-disk for comparison. */ static int open_C_file(void) { ffd = open(tocent.xname,O_RDONLY,0); if (ffd < 0) { fprintf(stderr,"%s: can't open %s: %s\n",__progname,tocent.xname,strerror(errno)); return(1); } return(0); } /* Set the prefix fo C messages. */ static void Cpref(const char *fmt, ...) { va_list ap; va_start(ap,fmt); vsprintf(&Cbuf[0],fmt,ap); va_end(ap); } /* Emit a C message. */ static void Cmsg(const char *fmt, ...) { va_list ap; fprintf(ifp,"%s",&Cbuf[0]); va_start(ap,fmt); vfprintf(ifp,fmt,ap); va_end(ap); } /* Error function for readsymlink() when used by C_aux_f. */ static void C_readlink_err(UNUSED(const char *path), UNUSED(int err)) { Cmsg("unreadable symbolic link: %s\n",strerror(errno)); abortx(); } /* Compare-extract a file. Very similar to x_aux_f; the differences between x and C arise largely when comparing data blocks, though this is the place for the code that compares non-files. */ static void C_aux_f(void) { int noabort; interested = 0; if (!inarglist() || setjmp(xabort)) { interested = 0; maybe_lseek(); return; } if (vflag) { switch (tocent.type) { case TYPE_NORMAL: case TYPE_CONTIG: fprintf(vfp,"C %s, %d byte%s, %d tape block%s\n", tocent.xname, tocent.size,(tocent.size==1)?"":"s", tocent.tblocks,(tocent.tblocks==1)?"":"s" ); break; case TYPE_DIRECTORY: fprintf(vfp,"C %s directory\n",tocent.xname); break; case TYPE_HARDLINK: fprintf(vfp,"C %s linked to %s\n",tocent.xname,tocent.xlinkname); break; case TYPE_SOFTLINK: fprintf(vfp,"C %s symbolic link to %s\n",tocent.xname,tocent.xlinkname); break; case TYPE_CSPECIAL: fprintf(vfp,"C %s character special device (%d,%d)\n",tocent.xname,otoi(&tocent.devmajor[0]),otoi(&tocent.devminor[0])); break; case TYPE_BSPECIAL: fprintf(vfp,"C %s block special device (%d,%d)\n",tocent.xname,otoi(&tocent.devmajor[0]),otoi(&tocent.devminor[0])); break; case TYPE_FIFO: fprintf(vfp,"C %s FIFO\n",tocent.xname); break; case TYPE_SPARSE: fprintf(vfp,"C %s sparse\n",tocent.xname); break; case TYPE_KILL: fprintf(vfp,"C %s kill later entry\n",tocent.xname); break; default: fprintf(stderr,"%s: %s INTERNAL BUG: bad type %d [C_aux_f 1]\n",__progname,tocent.xname,tocent.type); break; } fflush(vfp); } check_rooted(); noabort = 0; switch (tocent.type) { case TYPE_NORMAL: Cpref("C %s: on tape: plain file, on disk: ",tocent.xname); if (0) { case TYPE_CONTIG: Cpref("C %s: on tape: contiguous file, on disk: ",tocent.xname); } if (0) { case TYPE_SPARSE: Cpref("C %s: on tape: sparse file, on disk: ",tocent.xname); } if (lstat(tocent.xname,&filestat) < 0) { Cmsg("nonexistent: %s\n",strerror(errno)); abortx(); } switch (filestat.st_mode & S_IFMT) { case S_IFDIR: Cmsg("directory\n"); break; case S_IFCHR: #ifdef NO_DEVICES Cmsg("character special device; comparing anyway\n"); #else Cmsg("character special device (%d,%d); comparing anyway\n",major(filestat.st_rdev),minor(filestat.st_rdev)); #endif noabort = 1; break; case S_IFBLK: #ifdef NO_DEVICES Cmsg("block special device; comparing anyway\n"); #else Cmsg("block special device (%d,%d); comparing anyway\n",major(filestat.st_rdev),minor(filestat.st_rdev)); #endif noabort = 1; break; case S_IFREG: noabort = 1; break; #ifdef S_IFLNK case S_IFLNK: Cmsg("symbolic link; comparing linked-to file\n"); noabort = 1; break; #endif #ifdef S_IFSOCK case S_IFSOCK: Cmsg("socket\n"); break; #endif #ifdef S_IFIFO case S_IFIFO: Cmsg("FIFO\n"); break; #endif default: Cmsg("something strange (%o)\n",filestat.st_mode); break; } if (! noabort) abortx(); copy_tocentry(&tocent,&xtoce,1); if (xtoce.type == TYPE_SPARSE) { if (DEBUG(SPARSE)) fprintf(debugf,"beginning C of sparse file %s\n",xtoce.xname); xphase = XPH_MAP; free_map(xmap); xmap = 0; xmaptail = ⟼ xsizeleft = xtoce.mapsize; } else { xsizeleft = xtoce.size; if (open_C_file()) abortx(); } interested = 1; break; case TYPE_DIRECTORY: Cpref("C %s: on tape: directory, on disk: ",tocent.xname); if (lstat(tocent.xname,&filestat) < 0) { Cmsg("nonexistent: %s\n",strerror(errno)); abortx(); } switch (filestat.st_mode & S_IFMT) { case S_IFDIR: noabort = 1; break; case S_IFCHR: #ifdef NO_DEVICES Cmsg("character special device\n"); #else Cmsg("character special device (%d,%d)\n",major(filestat.st_rdev),minor(filestat.st_rdev)); #endif break; case S_IFBLK: #ifdef NO_DEVICES Cmsg("block special device\n"); #else Cmsg("block special device (%d,%d)\n",major(filestat.st_rdev),minor(filestat.st_rdev)); #endif break; case S_IFREG: Cmsg("plain file\n"); break; #ifdef S_IFLNK case S_IFLNK: Cmsg("symbolic link\n"); break; #endif #ifdef S_IFSOCK case S_IFSOCK: Cmsg("socket\n"); break; #endif #ifdef S_IFIFO case S_IFIFO: Cmsg("FIFO\n"); break; #endif default: Cmsg("something strange (%o)\n",filestat.st_mode); break; } if (! noabort) abortx(); break; case TYPE_HARDLINK: { struct stat linkstat; Cpref("C %s: on tape: hard link to %s, on disk: ",tocent.xname,tocent.xlinkname); if (lstat(tocent.xname,&filestat) < 0) { Cmsg("nonexistent: %s\n",strerror(errno)); abortx(); } switch (filestat.st_mode & S_IFMT) { case S_IFDIR: Cmsg("directory\n"); break; case S_IFCHR: #ifdef NO_DEVICES Cmsg("character special device\n"); #else Cmsg("character special device (%d,%d)\n",major(filestat.st_rdev),minor(filestat.st_rdev)); #endif break; case S_IFBLK: #ifdef NO_DEVICES Cmsg("block special device\n"); #else Cmsg("block special device (%d,%d)\n",major(filestat.st_rdev),minor(filestat.st_rdev)); #endif break; case S_IFREG: noabort = 1; break; #ifdef S_IFLNK case S_IFLNK: Cmsg("symbolic link\n"); break; #endif #ifdef S_IFSOCK case S_IFSOCK: Cmsg("socket\n"); break; #endif #ifdef S_IFIFO case S_IFIFO: Cmsg("FIFO\n"); break; #endif default: Cmsg("something strange (%o)\n",filestat.st_mode); break; } if (! noabort) abortx(); if (lstat(tocent.xlinkname,&linkstat) < 0) { Cmsg("plain file, but %s nonexistent: %s\n",tocent.xlinkname,strerror(errno)); abortx(); } if ((linkstat.st_dev != filestat.st_dev) || (linkstat.st_ino != filestat.st_ino)) { Cmsg("both exist but are different\n"); abortx(); } } break; case TYPE_SOFTLINK: { char *slbuf; int ll; Cpref("C %s: on tape: symbolic link to %s, on disk: ",tocent.xname,tocent.xlinkname); if (lstat(tocent.xname,&filestat) < 0) { Cmsg("nonexistent: %s\n",strerror(errno)); abortx(); } switch (filestat.st_mode & S_IFMT) { case S_IFDIR: Cmsg("directory\n"); break; case S_IFCHR: #ifdef NO_DEVICES Cmsg("character special device\n"); #else Cmsg("character special device (%d,%d)\n",major(filestat.st_rdev),minor(filestat.st_rdev)); #endif break; case S_IFBLK: #ifdef NO_DEVICES Cmsg("block special device\n"); #else Cmsg("block special device (%d,%d)\n",major(filestat.st_rdev),minor(filestat.st_rdev)); #endif break; case S_IFREG: Cmsg("plain file\n"); break; #ifdef S_IFLNK case S_IFLNK: noabort = 1; break; #endif #ifdef S_IFSOCK case S_IFSOCK: Cmsg("socket\n"); break; #endif #ifdef S_IFIFO case S_IFIFO: Cmsg("FIFO\n"); break; #endif default: Cmsg("something strange (%o)\n",filestat.st_mode); break; } if (! noabort) abortx(); readsymlink(tocent.xname,&slbuf,&ll,C_readlink_err,0); if (strcmp(tocent.xlinkname,slbuf)) { Cmsg("symbolic link to %s\n",slbuf); free(slbuf); abortx(); } free(slbuf); } break; case TYPE_CSPECIAL: Cpref("C %s: on tape: character special device (%d,%d), on disk: ",tocent.xname,otoi(&tocent.devmajor[0]),otoi(&tocent.devminor[0])); if (lstat(tocent.xname,&filestat) < 0) { Cmsg("nonexistent: %s\n",strerror(errno)); abortx(); } switch (filestat.st_mode & S_IFMT) { case S_IFDIR: Cmsg("directory\n"); break; case S_IFCHR: noabort = 1; break; case S_IFBLK: #ifdef NO_DEVICES Cmsg("block special device\n"); #else Cmsg("block special device (%d,%d)\n",major(filestat.st_rdev),minor(filestat.st_rdev)); #endif break; case S_IFREG: Cmsg("plain file\n"); break; #ifdef S_IFLNK case S_IFLNK: Cmsg("symbolic link\n"); break; #endif #ifdef S_IFSOCK case S_IFSOCK: Cmsg("socket\n"); break; #endif #ifdef S_IFIFO case S_IFIFO: Cmsg("FIFO\n"); break; #endif default: Cmsg("something strange (%o)\n",filestat.st_mode); break; } if (! noabort) abortx(); #ifdef NO_DEVICES Cmsg("character special device (can't compare)\n"); abortx(); #else if ((major(filestat.st_rdev) != otoi(&tocent.devmajor[0])) || (minor(filestat.st_rdev) != otoi(&tocent.devminor[0]))) { Cmsg("character special device (%d,%d)\n",major(filestat.st_rdev),minor(filestat.st_rdev)); abortx(); } #endif break; case TYPE_BSPECIAL: Cpref("C %s: on tape: block special device (%d,%d), on disk: ",tocent.xname,otoi(&tocent.devmajor[0]),otoi(&tocent.devminor[0])); if (lstat(tocent.xname,&filestat) < 0) { Cmsg("nonexistent: %s\n",strerror(errno)); abortx(); } switch (filestat.st_mode & S_IFMT) { case S_IFDIR: Cmsg("directory\n"); break; case S_IFCHR: #ifdef NO_DEVICES Cmsg("character special device\n"); #else Cmsg("character special device (%d,%d)\n",major(filestat.st_rdev),minor(filestat.st_rdev)); #endif break; case S_IFBLK: noabort = 1; break; case S_IFREG: Cmsg("plain file\n"); break; #ifdef S_IFLNK case S_IFLNK: Cmsg("symbolic link\n"); break; #endif #ifdef S_IFSOCK case S_IFSOCK: Cmsg("socket\n"); break; #endif #ifdef S_IFIFO case S_IFIFO: Cmsg("FIFO\n"); break; #endif default: Cmsg("something strange (%o)\n",filestat.st_mode); break; } if (! noabort) abortx(); #ifdef NO_DEVICES Cmsg("block special device (can't compare)\n"); abortx(); #else if ((major(filestat.st_rdev) != otoi(&tocent.devmajor[0])) || (minor(filestat.st_rdev) != otoi(&tocent.devminor[0]))) { Cmsg("block special device (%d,%d)\n",major(filestat.st_rdev),minor(filestat.st_rdev)); abortx(); } #endif break; case TYPE_FIFO: Cpref("C %s: on tape: FIFO, on disk: ",tocent.xname); if (lstat(tocent.xname,&filestat) < 0) { Cmsg("nonexistent: %s\n",strerror(errno)); abortx(); } switch (filestat.st_mode & S_IFMT) { case S_IFDIR: Cmsg("directory\n"); break; case S_IFCHR: #ifdef NO_DEVICES Cmsg("character special device\n"); #else Cmsg("character special device (%d,%d)\n",major(filestat.st_rdev),minor(filestat.st_rdev)); #endif break; case S_IFBLK: #ifdef NO_DEVICES Cmsg("block special device\n"); #else Cmsg("block special device (%d,%d)\n",major(filestat.st_rdev),minor(filestat.st_rdev)); #endif break; case S_IFREG: Cmsg("plain file\n"); break; #ifdef S_IFLNK case S_IFLNK: Cmsg("symbolic link\n"); break; #endif #ifdef S_IFSOCK case S_IFSOCK: Cmsg("socket\n"); break; #endif #ifdef S_IFIFO case S_IFIFO: noabort = 1; break; #endif default: Cmsg("something strange (%o)\n",filestat.st_mode); break; } if (! noabort) abortx(); break; case TYPE_KILL: fprintf(ifp,"C %s: on tape: kill entry, can't do much with it\n",tocent.xname); break; default: fprintf(stderr,"%s: %s INTERNAL BUG: bad type %d [C_aux_f 2]\n",__progname,tocent.xname,tocent.type); break; } } /* Compare nb bytes pointed to by data with the on-disk data. We assume that the on-disk file pointer is at off. */ static int compare_C_block(const void *data, int nb, int off) { int nr; char crbuf[TBLOCK]; nr = read(ffd,&crbuf[0],nb); if (nr < 0) { fprintf(stderr,"%s: read error on disk file %s: %s\n",__progname,xtoce.xname,strerror(errno)); close(ffd); interested = 0; if ((xtoce.type == TYPE_SPARSE) && DEBUG(SPARSE)) fprintf(debugf,"sparse compare aborted (read error)\n"); return(1); } if (nr < nb) { fprintf(stderr,"C %s: disk file too short (eof at offset %d)\n",xtoce.xname,off+nr); close(ffd); interested = 0; if ((xtoce.type == TYPE_SPARSE) && DEBUG(SPARSE)) fprintf(debugf,"sparse compare aborted (disk file shorter)\n"); return(1); } if (bcmp(data,&crbuf[0],nb)) { const char *hp; const char *bp; for (hp=data,bp=(&crbuf[0]);*hp==*bp;hp++,bp++) ; fprintf(stderr,"C %s: contents differ, offset %lu\n",xtoce.xname,(unsigned long int)(off+(hp-(const char *)data))); close(ffd); interested = 0; if ((xtoce.type == TYPE_SPARSE) && DEBUG(SPARSE)) fprintf(debugf,"sparse compare aborted (contents differ)\n"); return(1); } return(0); } /* Compare-extract block function. Much like x_aux_b except that rather than writing to disk, we read from disk and compare. */ static void C_aux_b(int n) { if (! interested) return; switch (xtoce.type) { default: abort(); break; case TYPE_NORMAL: case TYPE_CONTIG: { int cnb; cnb = (xsizeleft > TBLOCK) ? TBLOCK : xsizeleft; if (compare_C_block(&ihblk[0],cnb,n*TBLOCK)) return; xsizeleft -= cnb; } break; case TYPE_SPARSE: switch (xphase) { default: abort(); break; case XPH_MAP: if (standard_xph_map("compare")) { if (open_C_file()) { interested = 0; if (DEBUG(SPARSE)) fprintf(debugf,"sparse compare aborted (open_C_file failed)\n"); } } break; case XPH_DATA: { int nb; if (! xmap->link) { fprintf(stderr,"%s: internal error comparing sparse %s: more data than map wants\n",__progname,xtoce.xname); close(ffd); interested = 0; if (DEBUG(SPARSE)) fprintf(debugf,"sparse compare aborted (more data than map)\n"); return; } nb = (xsizeleft < TBLOCK) ? xsizeleft : TBLOCK; xsizeleft -= nb; for (;xzblocks>0;xzblocks--) { if (compare_C_block(&zblk[0],TBLOCK,n*TBLOCK)) return; } if (compare_C_block(&ihblk[0],nb,n*TBLOCK)) return; if (xsizeleft < 1) { EXTDESC *t; t = xmap->link; if (! t) { fprintf(stderr,"%s: internal error comparing sparse %s: ran out of map\n",__progname,xtoce.xname); close(ffd); interested = 0; if (DEBUG(SPARSE)) fprintf(debugf,"sparse compare aborted (out of map)\n"); return; } else { if (t->link) { xzblocks += (t->start - (xmap->start+xmap->length)) / TBLOCK; } else { int i; i = xtoce.size - (xmap->start+xmap->length); for (n++;i>=TBLOCK;n++,i-=TBLOCK) { if (compare_C_block(&zblk[0],TBLOCK,n*TBLOCK)) return; } if (compare_C_block(&zblk[0],i,n*TBLOCK)) return; } OLD(xmap); xmap = t; xsizeleft = xmap->length; } } } break; } break; } } /* End of a compare-extract. Check to make sure EOFs match. */ static void C_aux_e(void) { char c; if (! interested) return; switch (read(ffd,&c,1)) { case 1: fprintf(stderr,"C %s: disk file too long\n",xtoce.xname); break; case 0: break; case -1: fprintf(stderr,"%s: read error on disk file %s: %s\n",__progname,xtoce.xname,strerror(errno)); break; } close(ffd); interested = 0; } /* Compare-extract. Just scan the tape... */ static void do_C(void) { scantape(C_aux_f,C_aux_b,C_aux_e); } /* Set the device we shouldn't stray outside of, for the d keyletter. */ static void set_d_device(const char *fn) { struct stat stb; if (! dflag) return; if ( (hflag ? stat(fn,&stb) : lstat(fn,&stb)) < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,fn,strerror(errno)); return; } d_device = stb.st_dev; } /* addit is called recursively by (at least) adddir... */ static void addit(const char *); /* forward */ /* Put a block to the output archive. Handle output blocking. */ static void putblock(const void *blk) { if (firsto) { curoblk = 0; firsto = 0; } bcopy(blk,otapebuf+(curoblk++*TBLOCK),TBLOCK); if (align_output && (--align_room < 1)) align_room = align_output; if (curoblk >= bfactor) { if (Bflag_o) { int left; int did; char *bp; left = bfactor * TBLOCK; bp = otapebuf; while (left > 0) { did = writemt(bp,left); if (did < 0) { fprintf(stderr,"%s: tape write error: %s\n",__progname,strerror(errno)); exit(1); } if (did != left) { fprintf(stderr, "%s: warning: tape write shortfall (wanted %d, did %d)\n", __progname,left,did); fprintf(stderr,"%s: trying remainder\n",__progname); } bp += did; left -= did; } } else { int n; n = writemt(otapebuf,bfactor*TBLOCK); if (n < 0) { fprintf(stderr,"%s: tape write error: %s\n",__progname,strerror(errno)); exit(1); } if (n != bfactor*TBLOCK) { fprintf(stderr, "%s: warning: tape write shortfall (wanted %d, did %d)\n", __progname,bfactor*TBLOCK,n); exit(1); } } curoblk = 0; } } /* Checksum an output header and put it to the tape. This is where the align keyword is implemented. */ static void puthcksum(int tblk) { int cksum; if (align_output && (align_room < tblk+1) && (align_room < align_output)) { static char idlehblk[TBLOCK]; static int did_setup = 0; if (! did_setup) { bzero(&idlehblk[0],TBLOCK); #define idleh ((HEADER *)&idlehblk[0]) sprintf(idleh->mode,"%06o ",0); sprintf(idleh->uid,"%06o ",0); sprintf(idleh->gid,"%06o ",0); sprintf(idleh->size,"%011o",0); idleh->size[11] = ' '; sprintf(idleh->mtime,"%011o",0); idleh->mtime[11] = ' '; idleh->linkflag = LF_MOUSE_IDLE; cksum = checksum(idleh); sprintf(idleh->chksum,"%06o",cksum); idleh->chksum[7] = ' '; #undef idleh did_setup = 1; } while (align_room != align_output) putblock(idlehblk); } cksum = checksum(oheader); sprintf(oheader->chksum,"%06o",cksum); oheader->chksum[7] = ' '; putblock(ohblk); } /* Emit the header block in oheader, given the name is to be the concatentation of fn and suf, converting it to a long header if necessary. tblocks is the number of data tape blocks occupied, for passing down to puthcksum so it can do alignment. */ static void putheaderblock(const char *fn, const char *suf, int tblocks) { int lf; int ls; lf = strlen(fn); ls = strlen(suf); if (lf+ls <= NAMSIZ) { bcopy(fn,&oheader->name[0],lf); bcopy(suf,&oheader->name[lf],ls); puthcksum(tblocks); } else { int o; const char *t; char *tt; if (Lflag > 1) { fprintf(stderr,"%s: LL given, but adding a long header?\n",__progname); abort(); } oheader->name[0] = oheader->linkflag; oheader->linkflag = LF_MOUSE_LONG; sprintf(&oheader->name[1],"1%o ",lf+ls); puthcksum(tblocks+how_many(lf+ls,TBLOCK)); if (suf[0]) { tt = malloc(lf+ls+1); bcopy(fn,tt,lf); bcopy(suf,tt+lf,ls); tt[lf+ls] = '\0'; t = tt; } else { tt = 0; t = fn; } for (o=0;o NAMSIZ)) { fprintf(stderr,"%s: %s: name too long for standard tar\n",__progname,fn); if (Lflag > 1) return; } bzero(&ohblk[0],TBLOCK); sprintf(oheader->mode,"%06o ",07777&(int)filestat.st_mode); sprintf(oheader->uid,"%06o ",65535&(int)filestat.st_uid); sprintf(oheader->gid,"%06o ",65535&(int)filestat.st_gid); sprintf(oheader->size,"%011o",0); oheader->size[11] = ' '; sprintf(oheader->mtime,"%011o",(int)filestat.st_mtime); oheader->mtime[11] = ' '; oheader->linkflag = chrdev ? LF_CHR : LF_BLK; maj &= 0777777; min &= 0777777; sprintf(&oheader->devmajor[0],"%06o ",maj); sprintf(&oheader->devminor[0],"%06o ",min); putheaderblock(fn,"",0); if (vflag) { fprintf(vfp,"a %s %s special device (%d,%d)\n",fn,chrdev?"character":"block",maj,min); fflush(vfp); } if (unlinkflag) { if (unlink(fn) < 0) { fprintf(stderr,"%s: warning: can't unlink %s: %s\n",__progname,fn,strerror(errno)); } } } /* Sort a STRINTCHAIN. A straightforward split-recurse-merge sort. */ /* Sorts according to the string, ignoring the int. */ static STRINTCHAIN *sort_strintchain(STRINTCHAIN *chain) { STRINTCHAIN *s1; STRINTCHAIN *s2; STRINTCHAIN *t; STRINTCHAIN **tail; s1 = 0; s2 = 0; while (chain) { t = chain; chain = chain->link; t->link = s1; s1 = t; if (! chain) break; t = chain; chain = chain->link; t->link = s2; s2 = t; } if (! s2) return(s1); s1 = sort_strintchain(s1); s2 = sort_strintchain(s2); tail = &chain; while (s1 || s2) { if (!s2 || (s1 && (strcmp(s1->s,s2->s) < 0))) { t = s1; s1 = s1->link; } else { t = s2; s2 = s2->link; } *tail = t; tail = &t->link; } *tail = 0; return(chain); } /* Test whether a directory entry is . or .. */ static int dirent_is_dot_or_dotdot(struct direct *d) { switch (D_NAMLEN(d)) { case 1: return(d->d_name[0]=='.'); break; case 2: return((d->d_name[0]=='.')&&(d->d_name[1]=='.')); break; } return(0); } /* Helper for adddir, writes out the header block. This is a separate function for the benefit of append_synthetic(). */ static void adddirhdr(const char *fn) { bzero(&ohblk[0],TBLOCK); sprintf(oheader->mode,"%06o ",07777&(int)filestat.st_mode); sprintf(oheader->uid,"%06o ",65535&(int)filestat.st_uid); sprintf(oheader->gid,"%06o ",65535&(int)filestat.st_gid); sprintf(oheader->size,"%011o",0); oheader->size[11] = ' '; sprintf(oheader->mtime,"%011o",(int)filestat.st_mtime); oheader->mtime[11] = ' '; oheader->linkflag = LF_DIR; putheaderblock(fn,"/",0); if (vflag) { fprintf(vfp,"a %s/ directory\n",fn); fflush(vfp); } } /* Add a directory, and recursively its contents, to the archive. The duplication of code in this function is galling, but I haven't come up with a good way around it. */ static void adddir(const char *fn) { DIR *dir; struct direct *d; int l; char *newname; int nn; STRINTCHAIN *names; #ifndef TAR_INODE_NONE int fs_dir; struct fs_direct *fs_d; #endif l = strlen(fn); if (Lflag && (l+1 > NAMSIZ)) { fprintf(stderr,"%s: %s/: name too long for standard tar\n",__progname,fn); if (Lflag > 1) return; } #ifdef TAR_INODE_NONE dir = opendir(fn); if (dir == 0) #else if (iflag == I_FULL) { fs_dir = fs_opendir_p(i_fs,fn); dir = 0; } else { dir = opendir(fn); fs_dir = -1; } if ((dir == 0) && (fs_dir < 0)) #endif { fprintf(stderr,"%s: can't read directory %s\n",__progname,fn); return; } if (!oflag && Kon) adddirhdr(fn); nn = 1; newname = malloc(1); #ifndef TAR_INODE_NONE if (iflag == I_FULL) { if (sortflag) { names = 0; while (1) { fs_d = fs_readdir(fs_dir); if (fs_d == 0) { fprintf(stderr,"%s: warning: error reading %s: %s\n",__progname,fn,fs_strerror()); break; } if (fs_d == FS_READDIR_EOF) break; if ( (fs_d->d_ino == 0) || ( (fs_d->d_name[0] == '.') && ( (fs_d->d_namlen == 1) || ( (fs_d->d_namlen == 2) && (fs_d->d_name[1] == '.') ) ) ) ) continue; add_str_int_to_chain(copyofstr(fs_d->d_name),fs_d->d_namlen,&names); } fs_closedir(fs_dir); } else { while (1) { fs_d = fs_readdir(fs_dir); if (fs_d == 0) { fprintf(stderr,"%s: warning: error reading %s: %s\n",__progname,fn,fs_strerror()); break; } if (fs_d == FS_READDIR_EOF) break; if ( (fs_d->d_ino == 0) || ( (fs_d->d_name[0] == '.') && ( (fs_d->d_namlen == 1) || ( (fs_d->d_namlen == 2) && (fs_d->d_name[1] == '.') ) ) ) ) continue; if (Lflag && (l+1+fs_d->d_namlen > NAMSIZ)) { fprintf(stderr,"%s: %s/%s: name too long for standard tar\n", __progname,fn,fs_d->d_name); if (Lflag > 1) continue; } if (l+1+fs_d->d_namlen+1 > nn) { nn = l + 1 + fs_d->d_namlen + 1; free(newname); newname = malloc(nn); } sprintf(newname,"%s/%s",fn,fs_d->d_name); addit(newname); } fs_closedir(fs_dir); } } else #endif { if (sortflag) { names = 0; while ((d=readdir(dir))) { if ((d->d_ino == 0) || dirent_is_dot_or_dotdot(d)) continue; add_str_int_to_chain(copyofstr(d->d_name),D_NAMLEN(d),&names); } closedir(dir); } else { while ((d=readdir(dir))) { if ((d->d_ino == 0) || dirent_is_dot_or_dotdot(d)) continue; if (Lflag && (l+1+D_NAMLEN(d) > NAMSIZ)) { fprintf(stderr,"%s: %s/%s: name too long for standard tar\n", __progname,fn,d->d_name); if (Lflag > 1) continue; } if (l+1+D_NAMLEN(d)+1 > nn) { nn = l + 1 + D_NAMLEN(d) + 1; free(newname); newname = malloc(nn); } sprintf(newname,"%s/%s",fn,d->d_name); addit(newname); } closedir(dir); } } if (sortflag) { for ( names = sort_strintchain(names); names; (void)free(names->s),pop_strintchain(&names) ) { if (Lflag && (l+1+names->i > NAMSIZ)) { fprintf(stderr,"%s: %s/%s: name too long for standard tar\n", __progname,fn,names->s); if (Lflag > 1) continue; } if (l+1+names->i+1 > nn) { nn = l + 1 + names->i + 1; free(newname); newname = malloc(nn); } sprintf(newname,"%s/%s",fn,names->s); addit(newname); } } free(newname); if (unlinkflag) { if (rmdir(fn) < 0) { fprintf(stderr,"%s: can't rmdir %s: %s\n",__progname,fn,strerror(errno)); } } } /* Like putheaderblock, but is used for two-name link header blocks. Deals with pushing both names to a long header if either is too long. */ static void putheaderblocklink(const char *fn, const char *link) { int lf; int ll; lf = strlen(fn); ll = strlen(link); if ((lf <= NAMSIZ) && (ll <= NAMSIZ)) { bcopy(fn,oheader->name,lf); bcopy(link,oheader->linkname,ll); puthcksum(0); } else { int o; char *t; if (Lflag > 1) { fprintf(stderr,"%s: LL given, but adding a long header?\n",__progname); abort(); } oheader->name[0] = oheader->linkflag; oheader->linkflag = LF_MOUSE_LONG; sprintf(&oheader->name[1],"2%o %o ",lf,ll); puthcksum(0); t = malloc(lf+ll+1); bcopy(fn,t,lf); bcopy(link,t+lf,ll); t[lf+ll] = '\0'; for (o=0;ocount and look at lp->xpathname. */ static void addlink(const char *fn, LINK *lp) { lp->count --; if (! Kon) return; bzero(&ohblk[0],TBLOCK); sprintf(oheader->mode,"%06o ",07777&(int)filestat.st_mode); sprintf(oheader->uid,"%06o ",65535&(int)filestat.st_uid); sprintf(oheader->gid,"%06o ",65535&(int)filestat.st_gid); sprintf(oheader->size,"%011o",0); oheader->size[11] = ' '; sprintf(oheader->mtime,"%011o",(int)filestat.st_mtime); oheader->mtime[11] = ' '; oheader->linkflag = LF_LINK; putheaderblocklink(fn,lp->xpathname); if (vflag) { fprintf(vfp,"a %s link to %s\n",fn,lp->xpathname); fflush(vfp); } } /* Generate a failure, emitting messages and/or saving the name as specified by the E keyletter. */ static void Efail(const char *, int, const char *, ...) attribute((format(printf,3,4))); static void Efail(const char *fn, int hard, const char *fmt, ...) { va_list ap; if (DEBUG(ERRORS)) { fprintf(debugf,"Efail %s (%s): ",fn,hard?"hard":"soft"); va_start(ap,fmt); vfprintf(debugf,fmt,ap); va_end(ap); fprintf(debugf,"\n"); } fprintf(stderr,"%s: %s: ",__progname,fn); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fprintf(stderr,"\n"); if (E_logf) { fprintf(E_logf,"%s: %s: ",__progname,fn); va_start(ap,fmt); vfprintf(E_logf,fmt,ap); va_end(ap); fprintf(E_logf,"\n"); } E_failed ++; if (hard) E_failhard ++; } /* va_list version of Efail - vEfail : Efail :: vprintf : printf */ #ifndef TAR_INODE_NONE static void vEfail(const char *, int, const char *, va_list ap) attribute((format(printf,3,0))); static void vEfail(const char *fn, int hard, const char *fmt, va_list ap) { if (DEBUG(ERRORS)) { va_list t; fprintf(debugf,"vEfail %s (%s): ",fn,hard?"hard":"soft"); va_copy(t,ap); vfprintf(debugf,fmt,t); fprintf(debugf,"\n"); } if (E_logf) { fprintf(E_logf,"%s: %s: ",__progname,fn); vfprintf(E_logf,fmt,ap); fprintf(E_logf,"\n"); } E_failed ++; if (hard) E_failhard ++; } #endif /* Sleep if necessary to make sure we detect changing ctimes */ static void maybe_sleep(void) { static struct timeval now = { 0 }; if (filestat.st_ctime >= now.tv_sec) gettimeofday(&now,0); if (filestat.st_ctime >= now.tv_sec) sleep(1); } /* Write a segment of a file to the output archive. Any necessary seeking on the file descriptor is assumed to have already been done. pad is true if we've already gotten an error and are just generating pad blocks to make up the amount of data we've committed (via the header) to putting in the archive. Return value is the new value of pad. */ static int write_file_chunk(int fd, const char *fn, int size, int pad) { int todo; int nb; int nr; int padding; todo = size; padding = pad; if (pad) bzero(&ohblk[0],TBLOCK); while (todo > 0) { if (todo >= TBLOCK) { nb = TBLOCK; } else { nb = todo; if (! padding) bzero(&ohblk[nb],TBLOCK-nb); } if (! padding) { nr = i_read(fd,&ohblk[0],nb); if (nr < 0) { Efail(fn,1,"read error: %s",i_strerror()); bzero(&ohblk[0],nb); padding = 1; } else if (nr < nb) { Efail(fn,0,"unexpected EOF"); bzero(&ohblk[nr],nb-nr); padding = 1; } } todo -= nb; putblock(ohblk); } return(padding); } /* Make sure this file is at EOF. */ static void check_at_eof(int fd, const char *fn) { switch (i_read(fd,&ohblk[0],1)) { case 0: break; case 1: Efail(fn,0,"grew"); break; default: /* really should do something with this case, but who cares... */ break; } } /* Write out the blocks holding the sparse map. */ static void write_map_blocks(EXTDESC *map) { int i; EXTDESC *m; bzero(&ohblk[0],TBLOCK); i = 0; for (m=map;m->link;m=m->link) { if (i+32 > TBLOCK) { putblock(ohblk); bzero(&ohblk[0],TBLOCK); i = 0; } sprintf(&ohblk[i],"%015o %015o",m->start,m->length); i += 32; } if (i > 0) putblock(ohblk); } /* Write a sparse file, given its map. Just seeks around and calls write_file_chunk per the map. */ static void write_sparse(int fd, const char *fn, EXTDESC *map) { int i; EXTDESC *m; write_map_blocks(map); i = 0; for (m=map;m->link;m=m->link) { if (! i) i_lseek(fd,m->start,L_SET); i = write_file_chunk(fd,fn,m->length,i); } i_lseek(fd,filestat.st_size,L_SET); check_at_eof(fd,fn); } /* Write a file, not sparse. Just write_file_chunk the whole thing. */ static void write_nonsparse(int fd, const char *fn) { write_file_chunk(fd,fn,filestat.st_size,0); check_at_eof(fd,fn); } /* Define mapit() as appropriate for the mapping method in use. This is used for fast determination of sparsity when dumping files from the filesystem (as opposed to directly from disk, where we _know_ the sparsity). */ #ifdef TAR_MAP_SUN /* Sun: be careful to deal with pre-_MAP_NEW systems. */ static void *mapit(int fd, const char *fn) { char *buf; #ifdef _MAP_NEW buf = (char *) mmap((caddr_t)0,filestat.st_size,PROT_READ,MAP_SHARED,fd,(off_t)0); if (buf == (char *)-1) { fprintf(stderr,"%s: can't mmap %s: %s\n",__progname,fn,strerror(errno)); return(0); } #else buf = valloc(filestat.st_size); if (buf == (char *)-1) { fprintf(stderr,"%s: out of memory for %s\n",__progname,fn); return(0); } if (mmap((caddr_t)buf,filestat.st_size,PROT_READ,MAP_SHARED,fd,(off_t)0) < 0) { fprintf(stderr,"%s: can't mmap %s: %s\n",__progname,fn,strerror(errno)); free(buf); return(0); } #endif return(buf); } static void unmapit(void *data) { munmap((caddr_t)data,filestat.st_size); #ifndef _MAP_NEW free((char *)data); #endif } #endif #ifdef TAR_MAP_NEXT static void *mapit(int fd, const char *fn) { vm_offset_t addr; kern_return_t rv; addr = 0; rv = map_fd(fd,0,&addr,1,filestat.st_size); if (rv != KERN_SUCCESS) { fprintf(stderr,"%s: can't mmap %s: %s\n",__progname,fn,mach_error_string(rv)); return(0); } return((char *)addr); } static void unmapit(void *data) { /* vm_address_t is an integer, not compatible with void *. bah. */ vm_deallocate(task_self(),(vm_address_t)data,filestat.st_size); } #endif #if defined(TAR_MAP_NETBSD) || defined(TAR_MAP_OSF1) static void *mapit(int fd, const char *fn) { char *buf; buf = (char *) mmap((caddr_t)0,filestat.st_size,PROT_READ,MAP_FILE|MAP_SHARED,fd,(off_t)0); if (buf == (char *)-1) { fprintf(stderr,"%s: can't mmap %s: %s\n",__progname,fn,strerror(errno)); return(0); } return(buf); } static void unmapit(void *data) { munmap((caddr_t)data,filestat.st_size); } #endif /* Given a file, figure out its usage map and return it. */ static EXTDESC *sparse_map(int fd, const char *fn) { EXTDESC *root; EXTDESC **tail; EXTDESC *piece; int start; int o; int s; if (filestat.st_size == 0) return(0); tail = &root; #ifndef TAR_INODE_NONE if (iflag == I_FULL) { long int (*map)[2]; int ino; int nmap; ino = fs_lookup(i_fs,fs_cwd,fn); if (ino < 0) return(0); nmap = fs_spacemap(i_fs,ino,0,0); if (nmap < 0) return(0); map = malloc(nmap*sizeof(*map)); s = fs_spacemap(i_fs,ino,map,nmap); if (s != nmap) { free(map); return(0); } if ((nmap == 1) && (map[0][0] == 0) && (map[0][1] >= filestat.st_size)) { free(map); return(0); } for (s=0;sstart = map[s][0]; piece->length = map[s][1]; if (piece->start+piece->length > filestat.st_size) { piece->length = filestat.st_size - piece->start; } *tail = piece; tail = &piece->link; } piece = NEW(EXTDESC); piece->start = 0; piece->length = 0; *tail = piece; piece->link = 0; free(map); return(root); } #endif #define NEWPC(a,b) do{piece=NEW(EXTDESC);piece->start=(a);piece->length=\ (b)-(a);*tail=piece;tail= &piece->link;start= -1;}while(0) start = -1; #ifndef TAR_MAP_NONE { char *data; data = mapit(fd,fn); if (data) { for (o=0;o TBLOCK) s = TBLOCK; if (bcmp(data+o,&zblk[0],s)) { if (start < 0) start = o; } else { if (start >= 0) NEWPC(start,o); } } unmapit(data); if (start == 0) return(0); if (start > 0) NEWPC(start,filestat.st_size); NEWPC(0,0); *tail = 0; return(root); } else { fprintf(stderr,"%s: processing %s the slow way\n",__progname,fn); } } #endif if (0) { free_ret:; *tail = 0; free_map(root); return(0); } { char blk[TBLOCK]; int n; for (o=0;o TBLOCK) s = TBLOCK; n = read(fd,&blk[0],s); if (n != s) { if (n < 0) { fprintf(stderr,"%s: error reading from %s (treating it as non-sparse): %s\n",__progname,fn,strerror(errno)); } else { fprintf(stderr,"%s: short read from %s (treating it as non-sparse)\n",__progname,fn); } goto free_ret; } if (bcmp(&blk[0],&zblk[0],s)) { if (start < 0) start = o; } else { if (start >= 0) NEWPC(start,o); } } if (start >= 0) NEWPC(start,filestat.st_size); NEWPC(0,0); *tail = 0; return(root); } #undef NEWPC } /* Report an error encountered while reading directly from disk. This handles retrying if requested by the r keyletter. */ #ifndef TAR_INODE_NONE static void inoerr(const char *, ...) attribute((format(printf,1,2))); static void inoerr(const char *fmt, ...) { va_list ap; if (rvalue < rflag) { rvalue ++; sync(); if (rvalue > 1) sleep(1); longjmp(rjmp,1); } va_start(ap,fmt); fprintf(stderr,"%s: %s: ",__progname,diskfn); vfprintf(stderr,fmt,ap); va_end(ap); va_start(ap,fmt); vEfail(diskfn,0,fmt,ap); va_end(ap); } #endif /* Report a warning encountered while reading directly from disk. This handles retrying if requested by the r keyletter. */ #ifndef TAR_INODE_NONE static void inowarn(const char *, ...) attribute((format(printf,1,2))); static void inowarn(const char *fmt, ...) { va_list ap; if (rvalue < rflag) { rvalue ++; sync(); if (rvalue > 1) sleep(1); longjmp(rjmp,1); } va_start(ap,fmt); fprintf(stderr,"%s: warning: %s: ",__progname,diskfn); vfprintf(stderr,fmt,ap); va_end(ap); va_start(ap,fmt); vEfail(diskfn,0,fmt,ap); va_end(ap); } #endif /* Read a block from the current disk, of size size, into buf. */ #ifndef TAR_INODE_NONE static int readblock(int bn, void *buf, int size) { int i; if (bn == 0) { bzero(buf,size); return(0); } if (blkseek(disk->fd,fsbtodb(disk->sb,bn)) < 0) { fprintf(stderr,"%s: %s: can't seek to block %d on %s: %s\n",__progname,diskfn,bn,disk->name,strerror(errno)); return(1); } i = read(disk->fd,buf,size); if (i < 0) { fprintf(stderr,"%s: %s: can't read block %d from %s: %s\n",__progname,diskfn,bn,disk->name,strerror(errno)); return(1); } else if (i != size) { fprintf(stderr,"%s: %s: reading block %d from %s: short read (wanted %d, got %d)\n",__progname,diskfn,bn,disk->name,size,i); bzero(((char *)buf)+i,size-i); return(1); } return(0); } #endif /* Add a segment to an EXTDESC. Check to see if we're adjacent to the last segment on the list, and if so just extend it; otherwise, add a new one. */ #ifndef TAR_INODE_NONE static void add_map_piece(EXTDESC ***tailp, int start, int size) #define tail (*tailp) { EXTDESC *e; if (start+size > di->di_size) size = di->di_size - start; e = *tail; if (e == 0) { e = NEW(EXTDESC); e->start = start; e->length = size; e->link = 0; *tail = e; } else if (e->start+e->length == start) { e->length += size; } else { tail = &e->link; e->link = NEW(EXTDESC); e = e->link; e->start = start; e->length = size; e->link = 0; } } #undef tail #endif /* Update a map for a direct block. */ #ifndef TAR_INODE_NONE static void add_map_dblk(int off, int blk, EXTDESC ***tailp) { if (blk) add_map_piece(tailp,off,disk->sb->fs_bsize); } #endif /* Update a map for an indirect block. If the indirect block number isn't 0 (which would indicate the whole indirect area is a hole), scan the indirect block and recurse. We call on ourselves for level -1 for leaf blocks, so just pass such calls on to add_map_dblk. */ #ifndef TAR_INODE_NONE static int add_map_iblk(int off, int blk, EXTDESC ***tailp, int lev) { int i; int inc; int tot; if (lev < 0) { add_map_dblk(off,blk,tailp); return(disk->sb->fs_bsize); } if ((blk < 0) || (blk+disk->bsifsb > disk->sb->fs_size)) { inoerr("invalid indirect block number %d",blk); return(-1); } if (blk == 0) { int rv; rv = disk->sb->fs_bsize; for (;lev>=0;lev--) rv *= NINDIR(disk->sb); return(rv); } if (readblock(blk,indirblks[lev],disk->sb->fs_bsize)) { inoerr("error reading indirect block %d",blk); return(-1); } tot = 0; for (i=0;isb);i++) { if (off >= di->di_size) break; inc = add_map_iblk(off,dbvecs[lev][i],tailp,lev-1); if (inc < 0) return(-1); off += inc; tot += inc; } return(tot); } #endif /* Compute and return the sparsity map for the current dinode. */ #ifndef TAR_INODE_NONE static EXTDESC *inode_sparse_map(void) { EXTDESC *root; EXTDESC **tail; EXTDESC *e; int o; int b; if (di->di_size == 0) return(0); tail = &root; root = 0; o = 0; for (b=0;b= di->di_size) break; add_map_dblk(o,di->di_db[b],&tail); o += disk->sb->fs_bsize; } for (b=0;b= di->di_size) break; inc = add_map_iblk(o,di->di_ib[b],&tail,b); if (inc < 0) { free_map(root); return(0); } o += inc; } if ((tail == &root) && (root->start == 0) && (root->length == di->di_size)) { free_map(root); return(0); } e = NEW(EXTDESC); e->start = 0; e->length = 0; e->link = 0; (*tail)->link = e; return(root); } #endif /* Do two struct stats differ? If so, return a string indicating what field differs. If not, return a nil pointer. */ static const char *statdiff(struct stat *a, struct stat *b) { #define CHECKFIELD(field,what) if (a->field != b->field) return(what) CHECKFIELD(st_dev,"filesystem"); CHECKFIELD(st_ino,"inumber"); CHECKFIELD(st_mode,"permissions"); CHECKFIELD(st_nlink,"number of links"); CHECKFIELD(st_uid,"owner UID"); CHECKFIELD(st_gid,"owner GID"); CHECKFIELD(st_size,"size"); CHECKFIELD(st_mtime,"modification time"); #undef CHECKFIELD if ((a->st_mode & S_IFMT) != (b->st_mode & S_IFMT)) return("file type"); switch (a->st_mode & S_IFMT) { case S_IFCHR: case S_IFBLK: if (a->st_rdev != b->st_rdev) return("device numbers"); break; } return(0); } /* Like write_file_chunk, above, but writes directly from disk instead of from a filesystem file. */ #ifndef TAR_INODE_NONE static int write_inode_chunk(unsigned long int start, unsigned long int length, int padding) { int i; int o; int b; int len; int toread; if (padding) { dopadding:; for (b=how_many(length,TBLOCK);b>0;b--) putblock(&zblk[0]); return(1); } if (start+length > di->di_size) { fprintf(stderr,"%s: internal error dumping %s: writing past EOF [%lu@%lu, EOF %lu]\n",__progname,diskfn,(unsigned long int)length,(unsigned long int)start,(unsigned long int)di->di_size); goto dopadding; } if (start % disk->sb->fs_bsize) { fprintf(stderr,"%s: internal error dumping %s: chunk start (%lu) not on a block boundary\n",__progname,diskfn,start); goto dopadding; } if ((start+length != di->di_size) && (length % disk->sb->fs_bsize)) { fprintf(stderr,"%s: internal error dumping %s: chunk length (%lu) not a multiple of block size\n",__progname,diskfn,length); goto dopadding; } o = start / disk->sb->fs_bsize; while (length > 0) { len = disk->sb->fs_bsize; if (len > length) len = length; if (o < NDADDR) { b = di->di_db[o]; toread = fragroundup(disk->sb,len); } else { int bwi[3]; int lev; int bwf; toread = disk->sb->fs_bsize; bwf = o - NDADDR; lev = 2; while (1) { if (lev < 0) { fprintf(stderr,"%s: too much indirection (%s trashed, maybe?)\n",__progname,disk->name); goto dopadding; } if (bwf < NINDIR(disk->sb)) { bwi[lev] = bwf; break; } else { bwf -= NINDIR(disk->sb); bwi[lev--] = bwf % NINDIR(disk->sb); bwf /= NINDIR(disk->sb); } } if (lev != 0) { for (i=0;lev<3;i++,lev++) bwi[i] = bwi[lev]; lev = i; } b = di->di_ib[lev-1]; for (i=0;ibsifsb > disk->sb->fs_size)) { Efail(diskfn,0,"invalid indirect block number %d",b); goto dopadding; } if (readblock(b,indirblks[i],disk->sb->fs_bsize)) { Efail(diskfn,1,"indirect block %d read failed",b); goto dopadding; } curindirs[lev] = b; } b = dbvecs[i][bwi[i]]; } } if ((b < 0) || (b+dbtofsb(disk->sb,btodb(toread)) > disk->sb->fs_size)) { Efail(diskfn,0,"invalid data block number %d",b); goto dopadding; } if (readblock(b,&fileblk[0],toread)) { Efail(diskfn,1,"data block %d read failed",b); goto dopadding; } if (len % TBLOCK) { bzero(&fileblk[len],TBLOCK-(len%TBLOCK)); } for (i=0;isb->fs_bsize) && length) { fprintf(stderr,"%s: internal error: at neither block boundary nor EOF\n",__progname); abort(); } o += len / disk->sb->fs_bsize; } return(0); } #endif /* Write out a file direct-from-disk, according to the given sparse map. This generates all the data blocks, including the ones holding the map. */ #ifndef TAR_INODE_NONE static void inode_write_sparse(EXTDESC *map) { int i; EXTDESC *m; write_map_blocks(map); i = 0; for (m=map;m;m=m->link) i = write_inode_chunk(m->start,m->length,i); } #endif /* Write out a file direct-from-disk non-sparse. */ #ifndef TAR_INODE_NONE static void inode_write_nonsparse(void) { write_inode_chunk(0,di->di_size,0); } #endif /* Add a file to the tape, when reading files direct-from-disk. Perform various consistency checks to be sure what we're dumping from disk is what we think we're dumping from the filesystem name (without these checks, i-file with U can go very badly wrong if it crosses a mount point, which is why we warn if U is given without d). */ #ifndef TAR_INODE_NONE static int addfileinode(const char *fn) { EXTDESC * volatile map; int ntb; int n; unsigned long int ino; diskfn = fn; rvalue = 0; ino = filestat.st_ino; map = 0; while (setjmp(rjmp)) { if (map) { free_map(map); map = 0; } } if (blkseek(disk->fd,fsbtodb(disk->sb,itod(disk->sb,ino))) < 0) { inoerr("can't seek to inode %lu on %s: %s\n",ino,disk->name,strerror(errno)); return(1); } n = read(disk->fd,&inobuf[0],disk->sb->fs_bsize); if (n < 0) { inoerr("can't read inode %lu from %s: %s\n",ino,disk->name,strerror(errno)); return(1); } else if (n < disk->sb->fs_bsize) { inoerr("can't read inode %lu from %s: wanted %lu got %lu\n",ino,disk->name,(unsigned long int)disk->sb->fs_bsize,(unsigned long int)n); return(1); } di = ((struct dinode *)&inobuf[0]) + itoo(disk->sb,ino); #ifdef TAR_INODE_INTERNAL (*disk->fixdi)(di); #endif if ((di->di_mode & S_IFMT) != S_IFREG) { inoerr("inode %lu on %s isn't a plain file\n",ino,disk->name); return(1); } if ((di->di_mode ^ filestat.st_mode) & 07777) { inowarn("mode doesn't match inode %lu on %s\n",ino,disk->name); } /* casts are needed to avoid "comparison between signed and unsigned" under some OS/compiler combinations */ if ((unsigned long int)di->di_nlink != (unsigned long int)filestat.st_nlink) { inowarn("link count doesn't match inode %lu on %s\n",ino,disk->name); } if ((unsigned long int)di->di_uid != (unsigned long int)filestat.st_uid) { inowarn("owner UID doesn't match inode %lu on %s\n",ino,disk->name); } if ((unsigned long int)di->di_gid != (unsigned long int)filestat.st_gid) { inowarn("owner GID doesn't match inode %lu on %s\n",ino,disk->name); } if (di->di_size != filestat.st_size) { inowarn("size doesn't match inode %lu on %s\n",ino,disk->name); } #if 0 /* probably _don't_ want to check the atime - it changes too often */ if (di->di_atime != filestat.st_atime) { inowarn("atime doesn't match inode %lu on %s\n",ino,disk->name); } #endif if (di->tar_di_mtime != filestat.st_mtime) { inowarn("mtime doesn't match inode %lu on %s\n",ino,disk->name); } if (di->tar_di_ctime != filestat.st_ctime) { inowarn("ctime doesn't match inode %lu on %s\n",ino,disk->name); } bzero(&ohblk[0],TBLOCK); sprintf(oheader->mode,"%06o ",07777&(int)di->di_mode); sprintf(oheader->uid,"%06o ",65535&(int)di->di_uid); sprintf(oheader->gid,"%06o ",65535&(int)di->di_gid); sprintf(oheader->size,"%011o",(int)di->di_size); oheader->size[11] = ' '; sprintf(oheader->mtime,"%011o",(int)di->tar_di_mtime); oheader->mtime[11] = ' '; oheader->linkflag = LF_NORMAL; ntb = how_many(di->di_size,TBLOCK); map = 0; if (Sflag) { map = inode_sparse_map(); if (map) { EXTDESC *m; int n; int l; oheader->linkflag = LF_MOUSE_SPARSE; n = 0; l = 0; for (m=map;m->link;m=m->link) { n ++; l += how_many(m->length,TBLOCK); } sprintf(&oheader->linkname[0],"%o %o",n,l); ntb = l + how_many(n*32,TBLOCK); } } if (setjmp(rjmp)) { fprintf(stderr,"%s: internal error: jumped to rjmp after committing\n",__progname); abort(); } putheaderblock(fn,"",ntb); if (vflag) { fprintf(vfp,"a %s %d block%s%s\n",fn,ntb,(ntb==1)?"":"s",map?" (sparse)":""); fflush(vfp); } if (map) { inode_write_sparse(map); free_map(map); } else { inode_write_nonsparse(); } return(0); } #endif /* Actually do the work of appending a file, given its tape name and an open fd on it. */ static void addfilefd(const char *fn, int fd) { EXTDESC *map; int ntb; bzero(&ohblk[0],TBLOCK); sprintf(oheader->mode,"%06o ",07777&(int)filestat.st_mode); sprintf(oheader->uid,"%06o ",65535&(int)filestat.st_uid); sprintf(oheader->gid,"%06o ",65535&(int)filestat.st_gid); sprintf(oheader->size,"%011o",(int)filestat.st_size); oheader->size[11] = ' '; sprintf(oheader->mtime,"%011o",(int)filestat.st_mtime); oheader->mtime[11] = ' '; oheader->linkflag = LF_NORMAL; ntb = how_many(filestat.st_size,TBLOCK); map = 0; if (Sflag) { map = sparse_map(fd,fn); i_lseek(fd,0,L_SET); if (map) { EXTDESC *m; int n; int l; oheader->linkflag = LF_MOUSE_SPARSE; n = 0; l = 0; for (m=map;m->link;m=m->link) { n ++; l += how_many(m->length,TBLOCK); } sprintf(&oheader->linkname[0],"%o %o",n,l); ntb = l + how_many(n*32,TBLOCK); } } putheaderblock(fn,"",ntb); if (vflag) { fprintf(vfp,"a %s %d block%s%s\n",fn,ntb,(ntb==1)?"":"s",map?" (sparse)":""); fflush(vfp); } if (map) { write_sparse(fd,fn,map); free_map(map); } else { write_nonsparse(fd,fn); } } /* Add a file to the tape, when reading files through the filesystem. Here's where we handle most of the E keyletter. */ static int addfilefilesys(const char *fn) { int fd; fd = i_open(fn); if (fd < 0) { Efail(fn,1,"%s",i_strerror()); return(1); } if (eflag) { struct stat stb; const char *diff; if (i_fstat(fd,&stb) < 0) { Efail(fn,1,"can't fstat: %s",i_strerror()); } else { if ((stb.st_mode & S_IFMT) != S_IFREG) { Efail(fn,1,"was, but is no longer, a plain file (not dumped)"); return(1); } diff = statdiff(&filestat,&stb); if (diff) { Efail(fn,0,"warning: changed %s before opening",diff); filestat = stb; } } } addfilefd(fn,fd); i_close(fd); return(0); } /* Update filestat for the given pathname. */ static int update_filestat(const char *fn) { return( ( #ifndef TAR_INODE_NONE (iflag == I_FULL) ? fs_stat_p(i_fs,fn,&filestat) : #endif hflag ? stat(fn,&filestat) : lstat(fn,&filestat) ) < 0); } /* Add a file to the tape, or at least try to. This handles the s and e keys, and link counts, and switches out based on whether i-file or i-full, or neither, is in effect. */ static void addfile_(const char *fn) { if (since && (filestat.st_mtime < sincetime)) { if (DEBUG(SINCE)) { fprintf(debugf,"%s: too old (%.24s), not dumped\n",fn,ctime(&filestat.st_mtime)); } return; } if (Lflag && (strlen(fn) > NAMSIZ)) { if (Lflag > 1) { Efail(fn,1,"name too long for standard tar"); return; } fprintf(stderr,"%s: warning: %s: name too long for standard tar\n",__progname,fn); } if (eflag) maybe_sleep(); /* test unlinkflag here because if it's set, then by the time we get to the last link to a multiply-linked file, all other links will be gone. */ if (unlinkflag || (filestat.st_nlink > 1)) { char *lcp; LINK l; l.inum = filestat.st_ino; l.devnum = filestat.st_dev; lcp = find_hentry(linkht,(char *)&l); if (lcp) { addlink(fn,(LINK *)lcp); return; } else if (filestat.st_nlink > 1) { LINK *lp; lp = NEW(LINK); lp->inum = filestat.st_ino; lp->devnum = filestat.st_dev; lp->count = filestat.st_nlink - 1; lp->xpathname = copyofstr(fn); add_new_hentry(linkht,(char *)lp); } } #if !defined(TAR_INODE_NONE) && !defined(NO_DEVICES) if (iflag == I_FILE) { #ifdef TAR_MOUNT_NONE disk = Udisk; #else if (Udisk) { disk = Udisk; } else { MTABENT *m; for (m=mounts;m&&(m->dev!=filestat.st_dev);m=m->link) ; disk = m ? m->disk : 0; } #endif } if ((iflag == I_FILE) && !disk) { Efail(fn,1,"on unknown device (%d,%d), can't dump",major(filestat.st_dev),minor(filestat.st_dev)); return; } else if (iflag == I_FILE) { if (addfileinode(fn)) return; } else #endif { if (addfilefilesys(fn)) return; } if (eflag) { struct stat stb; const char *diff; stb = filestat; if (update_filestat(fn) < 0) { Efail(fn,1,"can't %s to verify: %s",hflag?"stat":"lstat",strerror(errno)); } else { diff = statdiff(&filestat,&stb); if (diff) { Efail(fn,0,"warning: changed %s during dump",diff); } } filestat = stb; } } /* This adds a "fake" file, whose contents are given by `contents' (a normal NUL-terminated C string) to the tape. */ static void addfile_fake(const char *fn, const char *contents, time_t mtime) { int l; int ntb; bzero(&ohblk[0],TBLOCK); strcpy(oheader->mode,"000600 "); sprintf(oheader->uid,"%06o ",65535&(int)filestat.st_uid); sprintf(oheader->gid,"%06o ",65535&(int)filestat.st_gid); l = strlen(contents); sprintf(oheader->size,"%011o",l); oheader->size[11] = ' '; sprintf(oheader->mtime,"%011o",(int)mtime); oheader->mtime[11] = ' '; oheader->linkflag = LF_NORMAL; ntb = how_many(l,TBLOCK); putheaderblock(fn,"",ntb); if (vflag) { fprintf(vfp,"a %s %d block%s (fake)\n",fn,ntb,(ntb==1)?"":"s"); fflush(vfp); } while (l >= TBLOCK) { putblock(contents); contents += TBLOCK; l -= TBLOCK; } if (l > 0) { bcopy(contents,&ohblk[0],l); bzero(&ohblk[l],TBLOCK-l); putblock(&ohblk[0]); } } /* A file write to tape failed. Do the appropriate thing based on the E keyletter spec. */ static void do_E_fail(const char *fn) { time_t now; switch (E_appfile) { default: fprintf(stderr,"%s: internal error: bad E_appfile\n",__progname); abort(); break; case E_AF_NONE: if (DEBUG(ERRORS)) fprintf(debugf,"%s failure: appfile NONE\n",fn); break; case E_AF_EMPTY: if (DEBUG(ERRORS)) fprintf(debugf,"%s failure: appfile EMPTY\n",fn); time(&now); addfile_fake(fn,"",now); break; case E_AF_MSGFILE: if (DEBUG(ERRORS)) fprintf(debugf,"%s failure: appfile MSGFILE\n",fn); { char contents[512]; time(&now); sprintf(contents,"\ Backup date: %.24s\n\ \n\ This file was modified while a backup was in progress, and therefore was\n\ not backed up correctly.\n\ \n\ It may be possible to find an intact version on another backup.\n\ ",ctime(&now)); addfile_fake(fn,&contents[0],now); } break; } } /* Add a file to the tape. Tries to add it, then retries if appropriate based on the E spec. */ static void addfile(const char *fn) { int retries; if (! Kon) return; retries = 0; do { E_failed = 0; E_failhard = 0; addfile_(fn); if (E_failed) { if (DEBUG(ERRORS)) fprintf(debugf,"%s failed%s, try %d/%d\n",fn,E_failhard?" hard":"",retries,E_retry_now); if (E_listfd >= 0) { write(E_listfd,fn,strlen(fn)); write(E_listfd,"",1); if (DEBUG(ERRORS)) fprintf(debugf,"wrote %s to %d\n",fn,E_listfd); } } } while (E_failed && !E_failhard && (retries++ < E_retry_now)); if (E_failed) { if (E_retry_later && !E_failhard) { add_str_to_chain(copyofstr(fn),&mustretry); if (DEBUG(ERRORS)) fprintf(debugf,"%s queued for later\n",fn); } else { do_E_fail(fn); } } else { if (unlinkflag) { if (unlink(fn) < 0) { fprintf(stderr,"%s: warning: can't unlink %s: %s\n",__progname,fn,strerror(errno)); } } } } /* Do all files we saved for retrying "later". */ static void do_retries(void) { char *rfn; int retries; mustretry = rev_strchain(mustretry); if (DEBUG(ERRORS)) fprintf(debugf,"it's now `later'\n"); while (mustretry) { rfn = pop_strchain(&mustretry); if (DEBUG(ERRORS)) fprintf(debugf,"retrying %s\n",rfn); retries = 1; do { E_failed = 0; E_failhard = 0; if (update_filestat(rfn)) { Efail(rfn,1,"%s",strerror(errno)); } else { addfile_(rfn); } if (E_failed) { if (DEBUG(ERRORS)) fprintf(debugf,"%s failed%s, try %d/%d\n",rfn,E_failhard?" hard":"",retries,E_retry_later); if (E_listfd >= 0) { write(E_listfd,rfn,strlen(rfn)); write(E_listfd,"",1); if (DEBUG(ERRORS)) fprintf(debugf,"wrote %s to %d\n",rfn,E_listfd); } } } while (E_failed && !E_failhard && (retries++ < E_retry_later)); if (E_failed) { do_E_fail(rfn); } else { if (unlinkflag) { if (unlink(rfn) < 0) { fprintf(stderr,"%s: warning: can't unlink %s: %s\n",__progname,rfn,strerror(errno)); } } } free(rfn); } } /* Add a symbolic link to the archive. This would be #ifdef S_IFLNK except that it's used by -S symlink: support. slbuf is the link-to string. */ static void addsymlink(const char *fn, const char *slbuf) { if (! Kon) return; if (Lflag && (strlen(fn) > NAMSIZ)) { fprintf(stderr,"%s: %s: name too long for standard tar\n",__progname,fn); if (Lflag > 1) return; } if (Lflag && (strlen(slbuf) > NAMSIZ)) { fprintf(stderr,"%s: %s: linked-to name too long for standard tar\n",__progname,fn); if (Lflag > 1) return; } bzero(&ohblk[0],TBLOCK); sprintf(oheader->mode,"%06o ",07777&(int)filestat.st_mode); sprintf(oheader->uid,"%06o ",65535&(int)filestat.st_uid); sprintf(oheader->gid,"%06o ",65535&(int)filestat.st_gid); sprintf(oheader->size,"%011o",0); oheader->size[11] = ' '; sprintf(oheader->mtime,"%011o",(int)filestat.st_mtime); oheader->mtime[11] = ' '; oheader->linkflag = LF_SYMLINK; putheaderblocklink(fn,slbuf); if (vflag) { fprintf(vfp,"a %s symbolic link to %s\n",fn,slbuf); fflush(vfp); } if (unlinkflag) { if (unlink(fn) < 0) { fprintf(stderr,"%s: warning: can't unlink %s: %s\n",__progname,fn,strerror(errno)); } } } /* Add a FIFO to the archive. This would be #ifdef S_IFIFO except that it's used by -S fifo: support. */ static void addfifo(const char *fn) { if (! Kon) return; if (Lflag && (strlen(fn) > NAMSIZ)) { fprintf(stderr,"%s: %s: name too long for standard tar\n",__progname,fn); if (Lflag > 1) return; } bzero(&ohblk[0],TBLOCK); sprintf(oheader->mode,"%06o ",07777&(int)filestat.st_mode); sprintf(oheader->uid,"%06o ",65535&(int)filestat.st_uid); sprintf(oheader->gid,"%06o ",65535&(int)filestat.st_gid); sprintf(oheader->size,"%011o",0); oheader->size[11] = ' '; sprintf(oheader->mtime,"%011o",(int)filestat.st_mtime); oheader->mtime[11] = ' '; oheader->linkflag = LF_FIFO; putheaderblock(fn,"",0); if (vflag) { fprintf(vfp,"a %s FIFO\n",fn); fflush(vfp); } if (unlinkflag) { if (unlink(fn) < 0) { fprintf(stderr,"%s: warning: can't unlink %s: %s\n",__progname,fn,strerror(errno)); } } } /* Set up a kill header in oheader. */ static void make_kill_header(void) { bzero(&ohblk[0],TBLOCK); sprintf(oheader->mode,"%06o ",0); sprintf(oheader->uid,"%06o ",0); sprintf(oheader->gid,"%06o ",0); sprintf(oheader->size,"%011o",0); oheader->size[11] = ' '; sprintf(oheader->mtime,"%011o",0); oheader->mtime[11] = ' '; oheader->linkflag = LF_MOUSE_KILL; } /* Add something to the output archive. Check to see if the arglist excludes it, then see if we're creating a kill archive, then see if it's off-device if d was given, check for K operation, then switch out based on the type of the entity we're considering adding. */ static void addit(const char *fn) { if (argsexclude(fn)) return; if (kflag) { Kmatch(fn); if (Kon) { make_kill_header(); putheaderblock(fn,"",0); } return; } if (update_filestat(fn)) { fprintf(stderr,"%s: %s: %s\n",__progname,fn,strerror(errno)); return; } if (dflag && (filestat.st_dev != d_device)) { fprintf(ifp,"%s: off-device, not dumped\n",fn); return; } Kmatch(fn); switch (filestat.st_mode & S_IFMT) { default: fprintf(stderr,"%s: %s: unknown type %#o, not dumped\n", __progname,fn,(int)filestat.st_mode); return; break; #if defined(S_IFCHR) || defined(S_IFBLK) { int chrdev; #ifdef S_IFCHR case S_IFCHR: chrdev = 1; #endif #ifdef S_IFBLK if (0) { case S_IFBLK: chrdev = 0; } #endif #ifdef NO_DEVICES fprintf(stderr,"%s: %s: device special file, not dumped\n",__progname,fn); return; #else adddev(fn,chrdev,major(filestat.st_rdev),minor(filestat.st_rdev)); #endif break; } #endif #ifdef S_IFSOCK case S_IFSOCK: fprintf(stderr,"%s: %s: socket, not dumped\n",__progname,fn); return; break; #endif case S_IFDIR: adddir(fn); break; case S_IFREG: addfile(fn); break; #ifdef S_IFLNK case S_IFLNK: { char *slbuf; int ll; if (! readsymlink(fn,&slbuf,&ll,0,0)) { fprintf(stderr,"%s: can't read link %s: %s\n",__progname,fn,strerror(errno)); break; } addsymlink(fn,slbuf); free(slbuf); } break; #endif #ifdef S_IFIFO case S_IFIFO: addfifo(fn); break; #endif } } /* Complain about any files we didn't find all links to. */ static void linkcomplain(void *lcp) { #define lp ((LINK *)lcp) if (lp->count > 0) { fprintf(stderr,"%s: %d missing link%s to %s\n", __progname,lp->count,(lp->count==1)?"":"s",lp->xpathname); } else if (lp->count < 0) { fprintf(stderr,"%s: %d extra link%s to %s\n", __progname,-lp->count,(lp->count==-1)?"":"s",lp->xpathname); } #undef lp } /* Beginning of a file, P operation. Check if we're interested based on the arglist, then print t information if requested, then pass it on, setting up much as for extraction if it's a file with data blocks. */ static void P_aux_f(void) { interested = (!xflag || inarglist()); if (! interested) return; if (tflag) { print_t_info(); } else if (vflag) { switch (tocent.type) { case TYPE_NORMAL: case TYPE_CONTIG: fprintf(vfp,"P %s, %d byte%s, %d tape block%s\n", tocent.xname, tocent.size,(tocent.size==1)?"":"s", tocent.tblocks,(tocent.tblocks==1)?"":"s" ); break; case TYPE_DIRECTORY: fprintf(vfp,"P %s directory\n",tocent.xname); break; case TYPE_HARDLINK: fprintf(vfp,"P %s linked to %s\n",tocent.xname,tocent.xlinkname); break; case TYPE_SOFTLINK: fprintf(vfp,"P %s symbolic link to %s\n",tocent.xname,tocent.xlinkname); break; case TYPE_CSPECIAL: fprintf(vfp,"P %s character special device (%d,%d)\n",tocent.xname,otoi(&tocent.devmajor[0]),otoi(&tocent.devminor[0])); break; case TYPE_BSPECIAL: fprintf(vfp,"P %s block special device (%d,%d)\n",tocent.xname,otoi(&tocent.devmajor[0]),otoi(&tocent.devminor[0])); break; case TYPE_FIFO: fprintf(vfp,"P %s FIFO\n",tocent.xname); break; case TYPE_SPARSE: fprintf(vfp,"P %s, %d byte%s, %d-seg sparse, %d tape block%s%s\n", tocent.xname, tocent.size,(tocent.size==1)?"":"s", tocent.mapsize, tocent.tblocks,(tocent.tblocks==1)?"":"s", Sflag?"":" (expanded)" ); break; case TYPE_KILL: fprintf(vfp,"P %s kill entry\n",tocent.xname); break; default: fprintf(stderr,"%s: %s: INTERNAL BUG: bad type %d [P_aux_f 1]\n",__progname,tocent.xname,tocent.type); break; } fflush(vfp); } if (!oflag || (tocent.type != TYPE_DIRECTORY)) { bzero(&ohblk[0],TBLOCK); sprintf(oheader->mode,"%06o ",07777&(int)tocent.mode); sprintf(oheader->uid,"%06o ",65535&(int)tocent.uid); sprintf(oheader->gid,"%06o ",65535&(int)tocent.gid); sprintf(oheader->size,"%011o",tocent.size); oheader->size[11] = ' '; sprintf(oheader->mtime,"%011o",(int)tocent.mtime); oheader->mtime[11] = ' '; desparsify_P = 0; switch (tocent.type) { case TYPE_NORMAL: oheader->linkflag = LF_NORMAL; putheaderblock(tocent.xname,"",tocent.tblocks); break; case TYPE_DIRECTORY: oheader->linkflag = LF_DIR; putheaderblock(tocent.xname,"",0); break; case TYPE_HARDLINK: oheader->linkflag = LF_LINK; putheaderblocklink(tocent.xname,tocent.xlinkname); break; case TYPE_SOFTLINK: oheader->linkflag = LF_SYMLINK; putheaderblocklink(tocent.xname,tocent.xlinkname); break; case TYPE_CSPECIAL: oheader->linkflag = LF_CHR; strncpy(oheader->devmajor,&tocent.devmajor[0],NAMSIZ); strncpy(oheader->devminor,&tocent.devminor[0],NAMSIZ); putheaderblock(tocent.xname,"",0); break; case TYPE_BSPECIAL: oheader->linkflag = LF_BLK; strncpy(oheader->devmajor,&tocent.devmajor[0],NAMSIZ); strncpy(oheader->devminor,&tocent.devminor[0],NAMSIZ); putheaderblock(tocent.xname,"",0); break; case TYPE_FIFO: oheader->linkflag = LF_FIFO; putheaderblock(tocent.xname,"",0); break; case TYPE_SPARSE: { int notb; if (Sflag) { oheader->linkflag = LF_MOUSE_SPARSE; sprintf(&oheader->linkname[0],"%o %o",tocent.mapsize,tocent.tblocks-how_many(tocent.mapsize*32,TBLOCK)); notb = tocent.tblocks; } else { oheader->linkflag = LF_NORMAL; copy_tocentry(&tocent,&xtoce,1); xphase = XPH_MAP; free_map(xmap); xmap = 0; xmaptail = ⟼ xsizeleft = xtoce.mapsize; interested = 1; des_left = xtoce.size; desparsify_P = 1; notb = how_many(xtoce.size,TBLOCK); } putheaderblock(tocent.xname,"",notb); } break; case TYPE_KILL: make_kill_header(); putheaderblock(tocent.xname,"",0); break; case TYPE_CONTIG: oheader->linkflag = LF_CONTIG; putheaderblock(tocent.xname,"",tocent.tblocks); break; default: fprintf(stderr,"%s: %s: INTERNAL BUG: bad type %d [P_aux_f 2]\n",__progname,tocent.xname,tocent.type); interested = 0; return; break; } } } /* Pad out a P expansion to the size we committed to in the header. */ static void pad_P_expansion(void) { for (;des_left>=TBLOCK;des_left-=TBLOCK) { putblock(&zblk[0]); } if (des_left) putblock(&zblk[0]); } /* Just got a data block for a P operation. If not expanding sparse files, this is trivial: just echo it to the output archive. If we are, process the map as for extraction, then handle the data by generating blocks of NULs corresponding to the holes in the file. */ static void P_aux_b(int n) { if (! interested) return; if (! desparsify_P) { putblock(ihblk); return; } switch (xphase) { default: abort(); break; case XPH_MAP: standard_xph_map("expansion"); break; case XPH_DATA: { int nb; if (! xmap->link) { fprintf(stderr,"%s: internal error expanding sparse %s: more data than map wants\n",__progname,xtoce.xname); interested = 0; pad_P_expansion(); if (DEBUG(SPARSE)) fprintf(debugf,"sparse expansion aborted (more data than map)\n"); return; } nb = (xsizeleft < TBLOCK) ? xsizeleft : TBLOCK; xsizeleft -= nb; des_left -= (xzblocks * TBLOCK) + nb; if (des_left < 0) { fprintf(stderr,"%s: internal error expanding sparse %s: %d-byte overrun\n",__progname,xtoce.xname,-des_left); interested = 0; des_left += xzblocks * TBLOCK; pad_P_expansion(); if (DEBUG(SPARSE)) fprintf(debugf,"sparse expansion aborted (size overrun)\n"); return; } for (;xzblocks>0;xzblocks--) putblock(&zblk[0]); putblock(&ihblk[0]); if (xsizeleft < 1) { EXTDESC *t; t = xmap->link; if (! t) { fprintf(stderr,"%s: internal error expanding sparse %s: ran out of map\n",__progname,xtoce.xname); interested = 0; pad_P_expansion(); if (DEBUG(SPARSE)) fprintf(debugf,"sparse expansion aborted (out of map)\n"); return; } else { if (t->link) { xzblocks += (t->start - (xmap->start+xmap->length)) / TBLOCK; } else { int i; i = xtoce.size - (xmap->start+xmap->length); if (i != des_left) { fprintf(stderr,"%s: internal error expanding sparse %s: data left mismatch (%d not %d)\n",__progname,xtoce.xname,i,des_left); interested = 0; pad_P_expansion(); if (DEBUG(SPARSE)) fprintf(debugf,"sparse expansion aborted (data left mismatch)\n"); return; } pad_P_expansion(); des_left = 0; } OLD(xmap); xmap = t; xsizeleft = xmap->length; } } if ((n == xtoce.tblocks) && (xmap->link || xsizeleft || des_left)) { fprintf(stderr,"%s: internal error expanding sparse %s: last block and more left to do\n",__progname,xtoce.xname); interested = 0; pad_P_expansion(); if (DEBUG(SPARSE)) fprintf(debugf,"sparse expansion aborted (leftover on last block)\n"); return; } } break; } } /* Flush the output archive, generating two blocks of all-0s (the usual end-of-tape indication) and then enough more to fill out to the next blocking-factor boundary. */ static void flushblock(void) { putblock(zblk); putblock(zblk); while (curoblk > 0) { putblock(zblk); } } /* Read a stream of strings, calling a given function for each one. src is the place to read things from, what is a tag used for error messages, termc is the terminator (usually \n or \0), and fn is the function to call for each string. */ static void read_terminated_lines(FILE *src, const char *what, int termc, void (*fn)(const char *)) { char *line; int len; int have; int c; line = malloc(1); len = 0; have = 0; while (1) { c = getc(src); if (c == EOF) { if (len > 0) { fprintf(stderr,"%s: warning: missing terminator (supplied) on last input %s\n",__progname,what); line[len] = '\0'; (*fn)(line); } break; } if (c == termc) { line[len] = '\0'; (*fn)(line); len = 0; } else { if (len >= have) { line = realloc(line,(have=len+8)+1); } line[len++] = c; } } free(line); } /* Helper, called by append_arglist and (indirectly) by append_input. */ static void append_something(const char *s) { set_d_device(s); addit(s); } /* Helper for append_synthetic's parser for -S arguments. Parses a number in base `base' from string s, length l; the resulting number is stored to *vp. `what' is used for error messages. Return value is 0 if all went well, nonzero if an error was printed. */ static int number_value(const char *s, int l, unsigned int *vp, int base, const char *what) { unsigned int v; int dv; int i; v = 0; for (i=0;i= base) { fprintf(stderr,"%s: -S error: bad char `%c' in %s `%.*s'\n",__progname,s[i],what,l,s); return(1); } v += dv; } *vp = v; return(0); } /* Loads filestat with the usual default values for -S. */ static void default_filestat(void) { time_t now; time(&now); filestat.st_mode = 0666 & ~oldumask; filestat.st_uid = getuid(); filestat.st_gid = getgid(); filestat.st_mtime = now; } /* Called to handle a -S argument for a c operation, that is, to create a `synthetic' tape entry. See the manpage (or the code) for the details of the argument. */ static void append_synthetic(const char *s) { int got; #define S_GOT_MODE 1 unsigned int mode; #define S_GOT_UID 2 unsigned int uid; #define S_GOT_GID 4 unsigned int gid; #define S_GOT_MTIME 8 unsigned int mtime; char sep; const char *kp; int kl; const char *vp; int vl; int save_unlink; char *name; /* Really would like this to be a nested function, but that's gcc-specific. */ #define UPDATE_FILESTAT() do { \ if (got & S_GOT_MODE) filestat.st_mode = mode; \ if (got & S_GOT_UID) filestat.st_uid = uid; \ if (got & S_GOT_GID) filestat.st_gid = gid; \ if (got & S_GOT_MTIME) filestat.st_mtime = mtime; } while (0) got = 0; while (1) { kp = s; while (*s && isalpha(*s)) s ++; if (! *s) { fprintf(stderr,"%s: -S error: missing value(s) after keyword `%s'\n",__progname,kp); exit(1); } kl = s - kp; sep = *s++; vp = s; while (*s && (*s != sep)) s ++; vl = s - vp; if ((kl == 4) && !strncasecmp(kp,"mode",4)) { if (number_value(vp,vl,&mode,8,"mode")) exit(1); mode &= 07777; got |= S_GOT_MODE; } else if ((kl == 3) && !strncasecmp(kp,"uid",3)) { if (number_value(vp,vl,&uid,10,"uid")) exit(1); got |= S_GOT_UID; } else if ((kl == 3) && !strncasecmp(kp,"gid",3)) { if (number_value(vp,vl,&gid,10,"gid")) exit(1); got |= S_GOT_GID; } else if ((kl == 5) && !strncasecmp(kp,"mtime",5)) { if (number_value(vp,vl,&mtime,10,"mtime")) exit(1); got |= S_GOT_MTIME; } else { break; } if (! *s) { fprintf(stderr,"%s: -S error: missing entry spec\n",__progname); exit(1); } s ++; } /* All entry types have a tape-name as the first "argument" */ name = malloc(vl+1); strncpy(name,vp,vl); name[vl] = '\0'; save_unlink = unlinkflag; unlinkflag = 0; if ((kl == 4) && !strncasecmp(kp,"file",4)) { int fd; if (! *s) { fprintf(stderr,"%s: -S error: missing link-to string\n",__progname); exit(1); } vp = ++s; while (*s && (*s != sep)) s ++; if (*s) { fprintf(stderr,"%s: -S error: junk after entry spec\n",__progname); exit(1); } update_filestat(vp); if ((filestat.st_mode & S_IFMT) != S_IFREG) { fprintf(stderr,"%s: -S file: error: %s: Not a plain file\n",__progname,vp); goto retabort; } UPDATE_FILESTAT(); fd = i_open(vp); if (fd < 0) { fprintf(stderr,"%s: -S error: %s: %s\n",__progname,vp,i_strerror()); goto retabort; } addfilefd(name,fd); i_close(fd); } else if ((kl == 4) && !strncasecmp(kp,"link",4)) { LINK lk; default_filestat(); UPDATE_FILESTAT(); if (! *s) { fprintf(stderr,"%s: -S error: missing link-to string\n",__progname); exit(1); } vp = ++s; while (*s && (*s != sep)) s ++; if (*s) { fprintf(stderr,"%s: -S error: junk after entry spec\n",__progname); exit(1); } /* NUL terminator already in place */ /* bcopy to avoid "assignment discards const" whine */ bcopy(&vp,&lk.xpathname,sizeof(vp)); /* We count on addlink() as described in its comment. */ addlink(name,&lk); } else if ((kl == 7) && !strncasecmp(kp,"symlink",7)) { default_filestat(); UPDATE_FILESTAT(); if (! *s) { fprintf(stderr,"%s: -S error: missing link-to string\n",__progname); exit(1); } vp = ++s; while (*s && (*s != sep)) s ++; if (*s) { fprintf(stderr,"%s: -S error: junk after entry spec\n",__progname); exit(1); } addsymlink(name,vp); } else if ((kl == 4) && (!strncasecmp(kp,"bdev",4) || !strncasecmp(kp,"cdev",4))) { unsigned int maj; unsigned int min; default_filestat(); UPDATE_FILESTAT(); if (! *s) { fprintf(stderr,"%s: -S error: missing device major number\n",__progname); exit(1); } vp = ++s; while (*s && (*s != sep)) s ++; if (number_value(vp,s-vp,&maj,10,"major number")) exit(1); if (! *s) { fprintf(stderr,"%s: -S error: missing device minor number\n",__progname); exit(1); } vp = ++s; while (*s && (*s != sep)) s ++; if (number_value(vp,s-vp,&min,10,"minor number")) exit(1); if (*s) { fprintf(stderr,"%s: -S error: junk after entry spec\n",__progname); exit(1); } adddev(name,kp[0]=='c',maj,min); } else if ((kl == 4) && !strncasecmp(kp,"fifo",4)) { default_filestat(); UPDATE_FILESTAT(); if (*s) { fprintf(stderr,"%s: -S error: junk after entry spec\n",__progname); exit(1); } addfifo(name); } else if ((kl == 3) && !strncasecmp(kp,"dir",3)) { default_filestat(); filestat.st_mode = 0777 & ~oldumask; UPDATE_FILESTAT(); if (*s) { fprintf(stderr,"%s: -S error: junk after entry spec\n",__progname); exit(1); } adddirhdr(name); } else { fprintf(stderr,"%s: -S error: unknown entry type `%.*s'\n",__progname,kl,kp); exit(1); } retabort:; free(name); unlinkflag = save_unlink; } /* When doing a create operation (c or Pc), scan the arglist, handling -C arguments and plain pathname arguments, the former with chdir, the latter with addit. Finally, if requested, check to see if there are unresolved links left over. */ static void append_arglist(void) { int ap; for (ap=0;aplink; src = fopen_or_dup(t->s,0,"archive filename input"); read_terminated_lines(src,"filename",t->i,append_something); fclose(src); free(t); } if (lflag) { map_htable(linkht,linkcomplain); } } /* Do a c operation, or the c part of a Pc operation. Append the requested stuff to the archive, then terminate the archive. */ static void do_c(void) { if (Isource) append_input(); else append_arglist(); do_retries(); flushblock(); } /* Do a P operation. First scan the input archive(s), then either terminate the archive or call do_c to append anything requested and let it terminate the archive itself. */ static void do_P(void) { scantape(P_aux_f,P_aux_b,null_aux_fe); if (cflag) { do_c(); } else { flushblock(); } } /* Open a disk for the raw-disk-reading code. Read the superblock and check magic numbers. Create and return a DISKDEV. */ #ifndef TAR_INODE_NONE static DISKDEV *open_disk(const char *devname) { DISKDEV *d; int fd; int n; fd = open(devname,O_RDONLY,0); if (fd < 0) { int e; e = errno; fprintf(stderr,"%s: can't open %s: %s\n",__progname,devname,strerror(e)); if (DEBUG(DISKS)) fprintf(debugf,"can't open %s: %s\n",devname,strerror(e)); return(0); } d = NEW(DISKDEV); if (blkseek(fd,SBLOCK) < 0) { fprintf(stderr,"%s: can't seek to superblock on %s: %s\n",__progname,devname,strerror(errno)); OLD(d); return(0); } d->sb = (struct fs *) SBBUFALIGN(&d->sbbuf[0]); n = read(fd,d->sb,SBSIZE); if (n < 0) { fprintf(stderr,"%s: can't read superblock from %s: %s\n",__progname,devname,strerror(errno)); OLD(d); return(0); } else if (n != SBSIZE) { fprintf(stderr,"%s: can't read superblock from %s: wanted %d got %d\n",__progname,devname,SBSIZE,n); OLD(d); return(0); } #ifdef TAR_INODE_INTERNAL switch (d->sb->fs_magic) { case FS_MAGIC: switch (*(char *)&d->sb->fs_magic) { case FS_MAGIC&0xff: d->fixdi = di__swap_size; break; case (FS_MAGIC>>24)&0xff: d->fixdi = di__nil; break; default: fprintf(stderr,"%s: unknown endianism on %s\n",__progname,devname); OLD(d); return(0); break; } break; case FS_MAGIC_SW: fprintf(stderr,"%s: warning: filesystem on %s appears to be byte-swapped\n",__progname,devname); sb__swap_bytes(d->sb); switch (*(char *)&d->sb->fs_magic) { case FS_MAGIC&0xff: d->fixdi = di__swap_bytes_and_size; break; case (FS_MAGIC>>24)&0xff: d->fixdi = di__swap_bytes; break; default: fprintf(stderr,"%s: unknown endianism on %s\n",__progname,devname); OLD(d); return(0); break; } break; default: fprintf(stderr,"%s: bad superblock magic number on %s\n",__progname,devname); OLD(d); return(0); break; } #else if (d->sb->fs_magic != FS_MAGIC) { fprintf(stderr,"%s: bad superblock magic number on %s\n",__progname,devname); OLD(d); return(0); } #endif d->bsifsb = dbtofsb(d->sb,btodb(d->sb->fs_bsize)); d->fd = fd; d->name = copyofstr(devname); d->link = alldisks; alldisks = d; if (DEBUG(DISKS)) fprintf(debugf,"opened %s on fd %d\n",devname,fd); return(d); } #endif /* Create a raw device name from a block device name. Used if u is given. */ #ifndef TAR_MOUNT_NONE static char *make_raw_name(const char *name) { char *t; t = malloc(strlen(name)+2); sprintf(t,"/dev/r%s",name+5); return(t); } #endif /* Read the mount table, opening disk devices as we go. */ #ifndef TAR_MOUNT_NONE static void read_mounts(void) { #ifdef TAR_MOUNT_GETMNTENT FILE *mef; struct mntent *me; #define DEV me->mnt_fsname #define MNT me->mnt_dir #endif #ifdef TAR_MOUNT_GETFSSTAT int n; int i; int nfss; struct statfs *fss; #define DEV &fss[i].f_mntfromname[0] #define MNT &fss[i].f_mntonname[0] #endif #ifdef TAR_MOUNT_GETMNT int pos; struct fs_data fs; #define DEV fs.fd_req.devname #define MNT fs.fd_req.path #endif MTABENT *mte; #ifdef TAR_MOUNT_GETMNTENT mef = setmntent(MOUNTED,"r"); if (mef == 0) { fprintf(stderr,"%s: can't read mount table %s\n",__progname,MOUNTED); exit(1); } while ((me=getmntent(mef))) #endif #ifdef TAR_MOUNT_GETFSSTAT nfss = getfsstat(0,0,MNT_NOWAIT); while (1) { fss = malloc(nfss*sizeof(*fss)); n = getfsstat(fss,nfss*sizeof(*fss),MNT_NOWAIT); if (n < 1) return; if (n > nfss) { free(fss); nfss = n; continue; } break; } for (i=0;i 0) #endif { char *dev; struct stat stb; if (bcmp(DEV,"/dev/",5)) continue; dev = uflag ? make_raw_name(DEV) : DEV; if (! dev) { fprintf(stderr,"%s: make_raw_name failed for %s (ignoring)\n",__progname,DEV); if (DEBUG(DISKS)) fprintf(debugf,"make_raw_name failed for %s (ignoring)\n",DEV); continue; } if (stat(DEV,&stb) < 0) { fprintf(stderr,"%s: can't stat %s (mounted on %s) (ignoring)\n",__progname,DEV,MNT); if (DEBUG(DISKS)) fprintf(debugf,"can't stat %s (mounted on %s) (ignoring)\n",DEV,MNT); } else { DISKDEV *d; d = open_disk(dev); if (d) { mte = NEW(MTABENT); mte->dev = stb.st_rdev; mte->disk = d; mte->link = mounts; mounts = mte; } } if (dev != DEV) free(dev); } #ifdef TAR_MOUNT_GETMNTENT endmntent(mef); #endif } #endif /* Process an E argument. */ static void set_E_option(const char *s) { const char *cp; int l; int errs; errs = 0; cp = s; while (1) { for (;*cp&&isascii(*cp)&&isspace(*cp);cp++) ; if (! *cp) break; for (;*cp&&(*cp!=',');cp++) ; l = cp - s; if (DEBUG(ERRORS)) fprintf(debugf,"E arg %.*s\n",l,s); if ((l > 4) && !bcmp(s,"now=",4)) { E_retry_now = atoi(s+4); } else if ((l > 6) && !bcmp(s,"later=",6)) { E_retry_later = atoi(s+6); } else if ((l > 5) && !bcmp(s,"list=",5)) { char *t; t = malloc(l-4); bcopy(s+5,t,l-5); t[l-5] = '\0'; E_listfd = open_or_dup(t,1,"E list= output",0); if (E_listfd < 0) { fprintf(stderr,"%s: can't open E list file %s\n",__progname,t); } free(t); } else if ((l == 9) && !bcmp(s,"file=none",9)) { E_appfile = E_AF_NONE; } else if ((l == 10) && !bcmp(s,"file=empty",10)) { E_appfile = E_AF_EMPTY; } else if ((l == 8) && !bcmp(s,"file=msg",8)) { E_appfile = E_AF_MSGFILE; } else if ((l > 4) && !bcmp(s,"log=",4)) { char *t; t = malloc(l-3); bcopy(s+4,t,l-4); t[l-4] = '\0'; E_logf = fopen_or_dup(t,1,"E log= output"); if (E_logf == 0) { fprintf(stderr,"%s: can't open E logfile %s\n",__progname,t); } setlinebuf(E_logf); free(t); } else { fprintf(stderr,"%s: unrecognized E argument value `%.*s'\n",__progname,l,s); errs ++; } while (*cp == ',') cp ++; s = cp; } if (errs) exit(1); } /* Parse a character name, handling backslash escapes. */ static int parse_char(const char *name) { int c; if (*name != '\\') return(*name); switch (*++name) { case '\0': return('\\'); break; case 'a': return('\a'); break; case 'b': return('\b'); break; case 'e': return('\e'); break; case 'f': return('\f'); break; case 'n': return('\n'); break; case 'r': return('\r'); break; case 't': return('\t'); break; case 'v': return('\v'); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': break; default: return(*name); break; } c = *name - '0'; switch (*++name) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': break; default: return(c); break; } c = (c << 3) + (*name - '0'); switch (*++name) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': break; default: return(c); break; } c = (c << 3) + (*name - '0'); return(c); } /* Helper, called for f keyletters and (indirectly) by f-stream and f-stream-term long keys. */ static void append_tapename(const char *s) { add_str_to_chain(copyofstr(s),&tapenames); } /* We've processed the last chunk descriptor from a GNU-format sparse file; write out the output header and chunk descriptors. */ static void fromgnu_start_sparse(void) { EXTDESC *xd; int dcount; int tblocks; oheader->linkflag = LF_MOUSE_SPARSE; tblocks = 0; dcount = 0; xmapterm(); for (xd=xmap;xd->link;xd=xd->link) { dcount ++; tblocks += how_many(xd->length,TBLOCK); if (xd->link->link && (xd->length % TBLOCK)) xd->length = round_up(xd->length,TBLOCK); } sprintf(&oheader->linkname[0],"%o %o",dcount,tblocks); putheaderblock(tocent.xname,"",how_many(dcount*32,TBLOCK)+tblocks); write_map_blocks(xmap); if (filen != tblocks) fprintf(stderr,"%s: fromgnu_start_sparse: filen=%d, tblocks=%d\n",__progname,filen,tblocks); } /* Beginning of a file, GNU->native conversion operation. */ static void rgnu_aux_f(void) { bzero(&ohblk[0],TBLOCK); sprintf(oheader->mode,"%06o ",07777&(int)tocent.mode); sprintf(oheader->uid,"%06o ",65535&(int)tocent.uid); sprintf(oheader->gid,"%06o ",65535&(int)tocent.gid); sprintf(oheader->size,"%011o",tocent.size); oheader->size[11] = ' '; sprintf(oheader->mtime,"%011o",(int)tocent.mtime); oheader->mtime[11] = ' '; switch (tocent.type) { case TYPE_NORMAL: oheader->linkflag = LF_NORMAL; putheaderblock(tocent.xname,"",tocent.tblocks); break; case TYPE_DIRECTORY: oheader->linkflag = LF_DIR; putheaderblock(tocent.xname,"",0); break; case TYPE_HARDLINK: oheader->linkflag = LF_LINK; putheaderblocklink(tocent.xname,tocent.xlinkname); break; case TYPE_SOFTLINK: oheader->linkflag = LF_SYMLINK; putheaderblocklink(tocent.xname,tocent.xlinkname); break; case TYPE_CSPECIAL: oheader->linkflag = LF_CHR; strncpy(oheader->devmajor,&tocent.devmajor[0],NAMSIZ); strncpy(oheader->devminor,&tocent.devminor[0],NAMSIZ); putheaderblock(tocent.xname,"",0); break; case TYPE_BSPECIAL: oheader->linkflag = LF_BLK; strncpy(oheader->devmajor,&tocent.devmajor[0],NAMSIZ); strncpy(oheader->devminor,&tocent.devminor[0],NAMSIZ); putheaderblock(tocent.xname,"",0); break; case TYPE_FIFO: oheader->linkflag = LF_FIFO; putheaderblock(tocent.xname,"",0); break; case TYPE_SPARSE: if (! xsizeleft) fromgnu_start_sparse(); break; case TYPE_CONTIG: oheader->linkflag = LF_CONTIG; putheaderblock(tocent.xname,"",tocent.tblocks); break; default: fprintf(stderr,"%s: %s: INTERNAL BUG: bad type %d [rgnu_aux_f]\n",__progname,tocent.xname,tocent.type); break; } } /* Just got a data block for a read-GNU conversion operation. For most types, this is pretty trivial - echo it to the output archive, or drop it for TYPE_DIRECTORY - but for sparse files we need to do sparse format conversion. */ static void rgnu_aux_b(UNUSED(int n)) { switch (tocent.type) { case TYPE_DIRECTORY: break; case TYPE_NORMAL: case TYPE_CONTIG: putblock(ihblk); break; case TYPE_SPARSE: if (xsizeleft) { /* It's another block of sparse chunk descriptors... */ int i; for (i=0;(i<21)&&gnu_sparse_piece(&ihblk[12*2*i]);i++) ; xsizeleft = ihblk[12*2*21]; if (! xsizeleft) fromgnu_start_sparse(); else filen++; } else { /* It's not. Fortunately the sparse formats are similar enough that the contents blocks can be just copied verbatim. */ putblock(ihblk); } break; default: fprintf(stderr,"%s: %s: INTERNAL BUG: bad type %d [rgnu_aux_b]\n",__progname,tocent.xname,tocent.type); break; } } /* Read a GNU tar archive, dropping (with warnings) what we can't represent and mapping what we can, outputting our own format. This always converts stdin to stdout. */ static void convert_fromgnu(void) { gnu_read = 1; openmt("-",0); openmt("-",1); Fflag = 2; fixup_K(); scantape_(rgnu_aux_f,rgnu_aux_b,null_aux_fe); close(imt); flushblock(); } /* Read one of our archives, outputting a GNU tar archive, dropping (with warnings) what GNU tar can't represent and mapping what it can. This always converts stdin to stdout. */ static void convert_tognu(void) { gnu_write = 1; /* openmt("-",0); openmt("-",1); fixup_K(); scantape_(wgnu_aux_f,wgnu_aux_b,null_aux_fe); close(imt); flushblock(); */ fprintf(stderr,"%s: tognu unimplemented\n",__progname); exit(1); } /* long_keys and onechar_key call one another */ static void long_keys(char ***); /* forward */ /* Process a single-character key, either in argv[1] or a one-byte-long "long" key. */ static void onechar_key(char c, char ***avptr) #define av (*avptr) { switch (c) { default: fprintf(stderr,"%s: %c: unknown option\n",__progname,c); exit(1); break; case '+': long_keys(avptr); break; case 'A': Aflag = 1; break; case 'B': Bflag_i |= BF_KEY; Bflag_o |= BF_KEY; bfactor = NBLOCK; break; case 'b': if (*av) { bfactor = atoi(*av); if (bfactor < 1) { fprintf(stderr,"%s: bad blocking factor `%s'\n",__progname,*av); exit(1); } setbfactor = 1; av ++; } else { fprintf(stderr,"%s: b keyletter needs a blocking factor\n",__progname); exit(1); } break; case 'C': Cflag = 1; break; case 'c': cflag = 1; break; case 'd': dflag ++; break; case 'E': if (*av) { add_str_to_chain(*av++,&E_options); } else { fprintf(stderr,"%s: E keyletter needs an argument\n",__progname); exit(1); } break; case 'e': eflag = 1; break; case 'F': Fflag ++; break; case 'f': if (*av) { append_tapename(*av++); } else { fprintf(stderr,"%s: f keyletter needs a tape name\n",__progname); exit(1); } break; case 'h': hflag = 1; break; case 'I': if (Isource) { if (*av) { Isource->i = *(*av++); } else { fprintf(stderr,"%s: multiple I keyletters requires a separator\n",__progname); exit(1); } } else { static char minus[] = "-"; add_str_int_to_chain(&minus[0],'\n',&Isource); } break; case 'i': #if defined(TAR_INODE_NONE) || defined(NO_DEVICES) fprintf(stderr,"%s: warning: i option ignored in this version\n",__progname); #else iflag = I_FILE; #endif break; case 'j': jflag = 1; break; case 'K': if (*av) { add_K_option(*av++); } else { fprintf(stderr,"%s: K keyletter needs an argument\n",__progname); exit(1); } break; case 'k': kflag = 1; break; case 'l': lflag = 1; break; case 'L': Lflag ++; break; case 'm': mflag = 1; break; case 'M': Mflag = 1; break; case 'o': oflag = 1; break; case 'O': if (Odest) { fprintf(stderr,"%s: multiple O specs given (last one used)\n",__progname); } Odest = "-"; break; case 'p': pflag ++; break; case 'P': if (Pdest) { fprintf(stderr,"%s: multiple P output specs given (last one used)\n",__progname); } Pdest = "-"; break; case 'r': #ifdef TAR_INODE_NONE fprintf(stderr,"%s: warning: r option ignored in this version\n",__progname); #else if (*av) { rflag = atoi(*av++); } else { fprintf(stderr,"%s: r keyletter needs an argument\n",__progname); exit(1); } #endif break; case 'R': if (*av) { if (rootname) { fprintf(stderr,"%s: excessive R keyletters, last one used\n",__progname); } rootname = *av++; rootnamelen = strlen(rootname); } else { fprintf(stderr,"%s: R keyletter needs a pathname\n",__progname); exit(1); } break; case 's': if (*av) { if (since) { fprintf(stderr,"%s: excessive s keyletters, last one used\n",__progname); } since = *av++; } else { fprintf(stderr,"%s: s keyletter needs an argument\n",__progname); exit(1); } break; case 'S': Sflag = 1; break; case 't': tflag = 1; break; case 'T': ftruncate_behavior = FTRUNC_PARANOID; break; case 'u': #ifdef TAR_INODE_NONE fprintf(stderr,"%s: warning: u option ignored in this version\n",__progname); #else uflag = 1; #endif break; case 'U': #ifdef TAR_INODE_NONE fprintf(stderr,"%s: warning: U option ignored in this version\n",__progname); #else if (*av) { if (Udisk) { fprintf(stderr,"%s: excessive U keyletters, last one used\n",__progname); } Udisk = open_disk(*av++); if (! Udisk) exit(1); } else { fprintf(stderr,"%s: U keyletter needs a disk device\n",__progname); exit(1); } #endif break; case 'v': vflag = 1; break; case 'V': if (*av) { if (vfn) { fprintf(stderr,"%s: excessive V keyletters, last one used\n",__progname); } vfn = *av++; } else { fprintf(stderr,"%s: V keyletter needs an argument\n",__progname); exit(1); } break; case 'W': Wflag = 1; break; case 'X': Xflag = 1; break; case 'x': xflag = 1; break; case '-': break; case 'D': if (*av) { setdebugging(*av++); } else { fprintf(stderr,"%s: D keyletter needs debugging keyword(s)\n",__progname); exit(1); } break; } } #undef av /* Process "long" keys, until we find the terminating + sign (or the end of the argument list, if that occurs first). */ static void long_keys(char ***avptr) #define av (*avptr) { char *key; while (1) { key = *av++; if (! key) { av --; return; } if (! key[0]) { fprintf(stderr,"%s: null key\n",__progname); exit(1); } if (! key[1]) { if (key[0] == '+') return; onechar_key(key[0],avptr); } else if (!strcmp(key,"I-source")) { if (av[0] && av[1]) { add_str_int_to_chain(av[0],parse_char(av[1]),&Isource); } else { fprintf(stderr,"%s: I-source key needs source and terminator\n",__progname); exit(1); } av += 2; } else if (!strcmp(key,"O-to")) { if (*av) { if (Odest) { fprintf(stderr,"%s: multiple O specs given (last one used)\n",__progname); } Odest = *av; } else { fprintf(stderr,"%s: O-to key needs destination argument\n",__progname); exit(1); } av ++; } else if (!strcmp(key,"P-to")) { if (*av) { if (Pdest) { fprintf(stderr,"%s: multiple P output specs given (last one used)\n",__progname); } Pdest = *av; } else { fprintf(stderr,"%s: P-to key needs destination argument\n",__progname); exit(1); } av ++; } else if (!strcmp(key,"V-toc")) { if (*av) { if (vfn) { fprintf(stderr,"%s: multiple V toc output specs given (last one used)\n",__progname); } vfn = *av; if (ifn == COPY_FROM_VFP) ifn = 0; } else { fprintf(stderr,"%s: V-toc key needs output spec\n",__progname); exit(1); } av ++; } else if (!strcmp(key,"V-info")) { if (*av) { if (ifn && (ifn != COPY_FROM_VFP)) { fprintf(stderr,"%s: multiple V info output specs given (last one used)\n",__progname); } ifn = *av; } else { fprintf(stderr,"%s: V-info key needs output spec\n",__progname); exit(1); } av ++; } else if (!strcmp(key,"i-file")) { #if defined(TAR_INODE_NONE) || defined(NO_DEVICES) fprintf(stderr,"%s: warning: i-file ignored in this version\n",__progname); #else iflag = I_FILE; #endif } else if (!strcmp(key,"i-full")) { if (*av) { #ifdef TAR_INODE_NONE fprintf(stderr,"%s: warning: i-full ignored in this version\n",__progname); #else i_fs = fs_openfs(*av); if (i_fs < 0) { fprintf(stderr,"%s: can't open %s: %s\n",__progname,*av,fs_strerror()); exit(1); } iflag = I_FULL; fs_cwd = fs_root(i_fs); #endif } else { fprintf(stderr,"%s: i-full key needs filesystem argument\n",__progname); exit(1); } av ++; } else if (!strcmp(key,"f-stream") || !strcmp(key,"f-stream-term")) { int term; char *s; FILE *f; if (!strcmp(key,"f-stream")) { if (*av) { s = *av; term = '\n'; } else { fprintf(stderr,"%s: f-stream key needs input spec argument\n",__progname); exit(1); } av ++; } else { if (av[0] && av[1]) { s = av[0]; term = parse_char(av[1]); } else { fprintf(stderr,"%s: f-stream-term key needs input spec and terminator arguments\n",__progname); exit(1); } av += 2; } f = fopen_or_dup(s,0,"input source input"); read_terminated_lines(f,"source",term,append_tapename); fclose(f); } else if (!strcmp(key,"align")) { if (*av) { align_output = atoi(*av); } else { fprintf(stderr,"%s: align key needs size argument\n",__progname); exit(1); } av ++; } else if (!strcmp(key,"ftruncate")) { if (*av) { if (!strcmp(*av,"unknown")) { ftruncate_behavior = FTRUNC_UNKNOWN; } else if (!strcmp(*av,"can-grow")) { ftruncate_behavior = FTRUNC_CAN_GROW; } else if (!strcmp(*av,"shrink-only")) { ftruncate_behavior = FTRUNC_SHRINK_ONLY; } else if (!strcmp(*av,"paranoid")) { ftruncate_behavior = FTRUNC_PARANOID; } else { fprintf(stderr,"%s: bad ftruncate argument `%s'\n",__progname,*av); exit(1); } } else { fprintf(stderr,"%s: ftruncate key needs size argument\n",__progname); exit(1); } av ++; } else if (!strcmp(key,"config")) { int l; int w; int i; char c; for (l=0;l<2;l++) { printf(l?"Unset":"Set"); w = l ? 5 : 3; c = ':'; for (i=0;config[i];i++) { if (l ? (config[i][0] != '!') : (config[i][0] == '!')) continue; if (w+2+strlen(config[i]+l)+1 > 78) { printf(",\n "); w = 2; c = ' '; } printf("%c %s",c,config[i]+l); w += 2 + strlen(config[i]+l); c = ','; } printf("\n"); } exit(0); } else if (!strcmp(key,"sortdirs")) { sortflag = 1; } else if (!strcmp(key,"unlink")) { unlinkflag = 1; } else if (!strcmp(key,"fromgnu")) { convert_fromgnu(); exit(0); } else if (!strcmp(key,"unlink")) { convert_tognu(); exit(0); } else if (!strcmp(key,"debug")) { if (*av) { setdebugging(*av); } else { fprintf(stderr,"%s: debug key needs debugging keyword(s)\n",__progname); exit(1); } av ++; } else { fprintf(stderr,"%s: unknown keyword `%s'\n",__progname,key); exit(1); } } } #undef av /* Check to see if anything is colliding over stdin or stdout. */ static void std_collide(int forwrite, const char *whatfor) { const char **var; var = forwrite ? &stdout_busy : &stdin_busy; if (*var) { fprintf(stderr,"%s: %s collides with %s over %s\n",__progname,whatfor,*var,forwrite?"stdout":"stdin"); fprintf(stderr,"%s: if you want this, use -%d rather than -\n",__progname,forwrite?1:0); exit(1); } *var = whatfor; } /* Run through the things that can refer to stdin/stdout, using std_collide to see if any two of them collide over either stream. */ static void check_for_collisions(void) { STRCHAIN *sc; STRINTCHAIN *sic; int wrt; wrt = cflag && !Pdest; for (sc=tapenames;sc;sc=sc->link) { if (!strcmp(sc->s,"-")) { std_collide(wrt,wrt?"output archive":"input archive"); } } for (sic=Isource;sic;sic=sic->link) { if (!strcmp(sic->s,"-")) { std_collide(0,"archive filename input"); } } if (Odest && !strcmp(Odest,"-")) std_collide(1,"extracted file output"); if (Pdest && !strcmp(Pdest,"-")) std_collide(1,"output archive"); if (vfn && !strcmp(vfn,"-")) std_collide(1,"V output listing"); if (ifn && !strcmp(ifn,"-")) std_collide(1,"informational output"); vfp = stdout_busy ? stderr : stdout; } /* Main entry point. Initialize things, process keys, fix up linked lists that were built in backwards order, check for an initial zero-length K argument, check for inconsistent arguments (eg, t and c both), scan remaining arguments, open up initial things, and switch out to do_[PCcxt] based on flags. */ int main(int, char **); int main(int ac, char **av) { char *cp; if (ac < 2) { usage: fprintf(stderr,"Usage: %s [op][flags] [args...] files...\n",__progname); exit(1); } linkht = new_htable(link_hfxn,link_cmpfxn); xtoce.xname = copyofstr(""); xtoce.xlinkname = copyofstr(""); tocent.xname = copyofstr(""); tocent.xlinkname = copyofstr(""); bfactor = NBLOCK; setbfactor = 0; bzero(&zblk[0],TBLOCK); av ++; for (cp=(*av++);*cp;cp++) { onechar_key(*cp,&av); } if (debugging) { int i; unsigned long int v; if (debugf == 0) debugf = stderr; fprintf(debugf,"%s: debugging:",__progname); v = debugging; for (i=0;debugkeys[i].name;i++) { if (debugkeys[i].value & (debugkeys[i].value-1)) continue; if (v & debugkeys[i].value) { v &= ~debugkeys[i].value; fprintf(debugf," %s",debugkeys[i].name); } } fprintf(debugf,"\n"); } E_options = rev_strchain(E_options); tapenames = rev_strchain(tapenames); Isource = rev_strintchain(Isource); while (E_options) set_E_option(pop_strchain(&E_options)); fixup_K(); if (Pdest && !tapenames) add_str_to_chain(copyofstr("-"),&tapenames); check_for_collisions(); switch (cflag+tflag+xflag+Cflag) { case 0: if (! Pdest) goto usage; break; case 1: break; default: fprintf(stderr,"%s: conflicting operation keyletters\n",__progname); exit(1); break; } if (Isource && !cflag) { fprintf(stderr,"%s: warning: operations other than c ignore I\n",__progname); Isource = 0; } if (Mflag && cflag && !Pdest) { fprintf(stderr,"%s: warning: M ignored when not reading archives\n",__progname); Mflag = 0; } if (Odest && !xflag) { fprintf(stderr,"%s: warning: O ignored when not extracting files\n",__progname); Odest = 0; } if (Odest && Sflag) { fprintf(stderr,"%s: warning: S ignored when doing O-style extraction\n",__progname); Sflag = 0; } #ifndef TAR_INODE_NONE if ((iflag != I_NONE) && !cflag) { fprintf(stderr,"%s: warning: i ignored when not using c\n",__progname); iflag = I_NONE; /* suppress noise messages in case r, u, or U also given */ rflag = 0; uflag = 0; Udisk = 0; } #ifdef NO_DEVICES if (rflag) if (rflag) { fprintf(stderr,"%s: warning: r ignored in this version\n",__progname); rflag = 0; } if (uflag) { fprintf(stderr,"%s: warning: u ignored in this version\n",__progname); uflag = 0; } if (Udisk) { fprintf(stderr,"%s: warning: U ignored in this version\n",__progname); Udisk = 0; } #else if (iflag != I_FILE) { if (rflag) { fprintf(stderr,"%s: warning: r ignored when not doing i-file dumping\n",__progname); rflag = 0; } if (uflag) { fprintf(stderr,"%s: warning: u ignored when not doing i-file dumping\n",__progname); uflag = 0; } if (Udisk) { fprintf(stderr,"%s: warning: U ignored when not doing i-file dumping\n",__progname); Udisk = 0; } } #endif if (iflag == I_FULL) { dflag = 0; if (hflag) { fprintf(stderr,"%s: h mode not possible with i-full\n",__progname); exit(1); } if (unlinkflag) { fprintf(stderr,"%s: unlink mode ignored when using i-full\n",__progname); unlinkflag = 0; } } if (Udisk && uflag) { fprintf(stderr,"%s: warning: u ignored in the presence of U\n",__progname); uflag = 0; } #if defined(TAR_MOUNT_NONE) && !defined(NO_DEVICES) if ((iflag == I_FILE) && !Udisk) { fprintf(stderr,"%s: i not usable without U in this version\n",__progname); exit(1); } #endif if (Udisk && !dflag) { fprintf(stderr,"%s: warning: U without d is probably a mistake\n",__progname); } #endif if (since && !cflag) { fprintf(stderr,"%s: warning: s ignored in the absence of c\n",__progname); since = 0; } scanargs(av); oldumask = umask(0); if (Odest) Ofd = open_or_dup(Odest,1,"O-style extraction output",0); if (Pdest) { openmt(Pdest,1); } else { if (! tapenames) { fprintf(stderr,"%s: must specify an archive (see the f keyletter)\n",__progname); exit(1); } imt = -1; omt = -1; if (cflag) { if (tapenames->link) { fprintf(stderr,"%s: multiple archives not usable with c\n",__progname); exit(1); } openmt(tapenames->s,1); } } ifp = vfp; if (vfn) { vfp = fopen_or_dup(vfn,1,"V output"); setlinebuf(vfp); } if (ifn == COPY_FROM_VFP) { ifp = vfp; } else if (ifn) { ifp = fopen_or_dup(ifn,1,"information output"); setlinebuf(ifp); } if (since) { if (make_sincetime()) { fprintf(stderr,"%s: invalid s argument: %s\n",__progname,since); exit(1); } if (vflag) { fprintf(ifp,"since time is %.24s\n",ctime(&sincetime)); } if (DEBUG(SINCE)) fprintf(debugf,"since time is %.24s\n",ctime(&sincetime)); } dirsp = -1; #if !defined(TAR_MOUNT_NONE) && !defined(NO_DEVICES) if ((iflag == I_FILE) && !Udisk) { mounts = 0; read_mounts(); } #endif if (Pdest) do_P(); else if (Cflag) do_C(); else if (cflag) do_c(); else if (xflag) do_x(); else if (tflag) do_t(); else { fprintf(stderr,"%s: INTERNAL ERROR: don't know what to do\n",__progname); exit(1); } exit(0); }