X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=archival%2Ftar.c;h=2aa5f07888e70267b6794477b6c2f87ec6d45344;hb=d9768d7cb97bbf6d065919907370cb8fbc9f9fa5;hp=611bbd9a015271d52ec63829f639d5e6981aed3a;hpb=1c314ad655af140b8ef4a271f4e657bf50218236;p=oweals%2Fbusybox.git diff --git a/archival/tar.c b/archival/tar.c index 611bbd9a0..2aa5f0788 100644 --- a/archival/tar.c +++ b/archival/tar.c @@ -2,12 +2,15 @@ /* * Mini tar implementation for busybox * + * Modifed to use common extraction code used by ar, cpio, dpkg-deb, dpkg + * Glenn McGrath + * * Note, that as of BusyBox-0.43, tar has been completely rewritten from the * ground up. It still has remnents of the old code lying about, but it is - * very different now (i.e. cleaner, less global variables, etc) + * very different now (i.e., cleaner, less global variables, etc.) * - * Copyright (C) 2000 by Lineo, inc. - * Written by Erik Andersen , + * Copyright (C) 1999,2000 by Lineo, inc. and Erik Andersen + * Copyright (C) 1999-2002 by Erik Andersen * * Based in part in the tar implementation in sash * Copyright (c) 1999 by David I. Bell @@ -35,64 +38,41 @@ * */ - -#include "internal.h" -#define BB_DECLARE_EXTERN -#define bb_need_io_error -#include "messages.c" +#include +#include +#include #include -#include +#include +#include +#include +#include #include -#include #include -#include -#include -#include -#include - +#include +#include +#include "unarchive.h" +#include "busybox.h" -static const char tar_usage[] = -#ifdef BB_FEATURE_TAR_CREATE - "tar -[cxtvO] " -#else - "tar -[xtvO] " -#endif -#if defined BB_FEATURE_TAR_EXCLUDE - "[--exclude File] " -#endif - "[-f tarFile] [FILE] ...\n" -#ifndef BB_FEATURE_TRIVIAL_HELP - "\nCreate, extract, or list files from a tar file. Note that\n" - "this version of tar treats hard links as separate files.\n\n" - "Main operation mode:\n" -#ifdef BB_FEATURE_TAR_CREATE - "\tc\t\tcreate\n" -#endif - "\tx\t\textract\n" - "\tt\t\tlist\n" - "\nFile selection:\n" - "\tf\t\tname of tarfile or \"-\" for stdin\n" - "\tO\t\textract to stdout\n" -#if defined BB_FEATURE_TAR_EXCLUDE - "\t--exclude\tfile to exclude\n" -#endif - "\nInformative output:\n" - "\tv\t\tverbosely list files processed\n" -#endif - ; +#ifdef CONFIG_FEATURE_TAR_CREATE /* Tar file constants */ -#ifndef MAJOR -#define MAJOR(dev) (((dev)>>8)&0xff) -#define MINOR(dev) ((dev)&0xff) -#endif +# define TAR_MAGIC "ustar" /* ustar and a null */ +# define TAR_VERSION " " /* Be compatable with GNU tar format */ + +# ifndef MAJOR +# define MAJOR(dev) (((dev)>>8)&0xff) +# define MINOR(dev) ((dev)&0xff) +# endif +static const int TAR_BLOCK_SIZE = 512; +static const int TAR_MAGIC_LEN = 6; +static const int TAR_VERSION_LEN = 2; /* POSIX tar Header Block, from POSIX 1003.1-1990 */ +enum { NAME_SIZE = 100 }; /* because gcc won't let me use 'static const int' */ struct TarHeader -{ - /* byte offset */ - char name[100]; /* 0-99 */ +{ /* byte offset */ + char name[NAME_SIZE]; /* 0-99 */ char mode[8]; /* 100-107 */ char uid[8]; /* 108-115 */ char gid[8]; /* 116-123 */ @@ -100,7 +80,7 @@ struct TarHeader char mtime[12]; /* 136-147 */ char chksum[8]; /* 148-155 */ char typeflag; /* 156-156 */ - char linkname[100]; /* 157-256 */ + char linkname[NAME_SIZE]; /* 157-256 */ char magic[6]; /* 257-262 */ char version[2]; /* 263-264 */ char uname[32]; /* 265-296 */ @@ -112,13 +92,37 @@ struct TarHeader }; typedef struct TarHeader TarHeader; +/* +** writeTarFile(), writeFileToTarball(), and writeTarHeader() are +** the only functions that deal with the HardLinkInfo structure. +** Even these functions use the xxxHardLinkInfo() functions. +*/ +typedef struct HardLinkInfo HardLinkInfo; +struct HardLinkInfo +{ + HardLinkInfo *next; /* Next entry in list */ + dev_t dev; /* Device number */ + ino_t ino; /* Inode number */ + short linkCount; /* (Hard) Link Count */ + char name[1]; /* Start of filename (must be last) */ +}; -/* A few useful constants */ -#define TAR_MAGIC "ustar" /* ustar and a null */ -#define TAR_VERSION " " /* Be compatable with GNU tar format */ -#define TAR_MAGIC_LEN 6 -#define TAR_VERSION_LEN 2 -#define TAR_BLOCK_SIZE 512 +/* Some info to be carried along when creating a new tarball */ +struct TarBallInfo +{ + char* fileName; /* File name of the tarball */ + int tarFd; /* Open-for-write file descriptor + for the tarball */ + struct stat statBuf; /* Stat info for the tarball, letting + us know the inode and device that the + tarball lives, so we can avoid trying + to include the tarball into itself */ + int verboseFlag; /* Whether to print extra stuff or not */ + char** excludeList; /* List of files to not include */ + HardLinkInfo *hlInfoHead; /* Hard Link Tracking Information */ + HardLinkInfo *hlInfo; /* Hard Link Info for the current file */ +}; +typedef struct TarBallInfo TarBallInfo; /* A nice enum with all the possible tar file content types */ enum TarFileType @@ -132,603 +136,61 @@ enum TarFileType DIRTYPE = '5', /* directory */ FIFOTYPE = '6', /* FIFO special */ CONTTYPE = '7', /* reserved */ + GNULONGLINK = 'K', /* GNU long (>100 chars) link name */ + GNULONGNAME = 'L', /* GNU long (>100 chars) file name */ }; typedef enum TarFileType TarFileType; -/* This struct ignores magic, non-numeric user name, - * non-numeric group name, and the checksum, since - * these are all ignored by BusyBox tar. */ -struct TarInfo -{ - int tarFd; /* An open file descriptor for reading from the tarball */ - char * name; /* File name */ - mode_t mode; /* Unix mode, including device bits. */ - uid_t uid; /* Numeric UID */ - gid_t gid; /* Numeric GID */ - size_t size; /* Size of file */ - time_t mtime; /* Last-modified time */ - enum TarFileType type; /* Regular, directory, link, etc */ - char * linkname; /* Name for symbolic and hard links */ - long devmajor; /* Major number for special device */ - long devminor; /* Minor number for special device */ -}; -typedef struct TarInfo TarInfo; - -/* Local procedures to restore files from a tar file. */ -static int readTarFile(const char* tarName, int extractFlag, int listFlag, - int tostdoutFlag, int verboseFlag, char** excludeList); - - - -#ifdef BB_FEATURE_TAR_CREATE -/* Local procedures to save files into a tar file. */ -static int writeTarFile(const char* tarName, int tostdoutFlag, - int verboseFlag, int argc, char **argv, char** excludeList); -#endif - - -extern int tar_main(int argc, char **argv) -{ - char** excludeList=NULL; -#if defined BB_FEATURE_TAR_EXCLUDE - int excludeListSize=0; -#endif - const char *tarName="-"; - int listFlag = FALSE; - int extractFlag = FALSE; - int createFlag = FALSE; - int verboseFlag = FALSE; - int tostdoutFlag = FALSE; - int stopIt; - - if (argc <= 1) - usage(tar_usage); - - /* Parse any options */ - while (--argc > 0 && strspn(*(++argv), "-cxt") >0 ) { - stopIt=FALSE; - while (stopIt==FALSE && *argv && **argv) { - switch (**argv) { - case 'f': - if (--argc == 0) { - fatalError( "Option requires an argument: No file specified\n"); - } - if (*tarName != '-') - fatalError( "Only one 'f' option allowed\n"); - tarName = *(++argv); - if (tarName == NULL) - fatalError( "Option requires an argument: No file specified\n"); - stopIt=TRUE; - break; - - case 't': - if (extractFlag == TRUE || createFlag == TRUE) - goto flagError; - listFlag = TRUE; - break; - - case 'x': - if (listFlag == TRUE || createFlag == TRUE) - goto flagError; - extractFlag = TRUE; - break; - case 'c': - if (extractFlag == TRUE || listFlag == TRUE) - goto flagError; - createFlag = TRUE; - break; - - case 'v': - verboseFlag = TRUE; - break; - - case 'O': - tostdoutFlag = TRUE; - tarName = "-"; - break; - case '-': -#if defined BB_FEATURE_TAR_EXCLUDE - if (strcmp(*argv, "-exclude")==0) { - if (--argc == 0) { - fatalError( "Option requires an argument: No file specified\n"); - } - excludeList=realloc( excludeList, sizeof(char**) * (excludeListSize+2)); - excludeList[excludeListSize] = *(++argv); - /* Remove leading "/"s */ - if (*excludeList[excludeListSize] =='/') { - excludeList[excludeListSize] = (excludeList[excludeListSize])+1; - } - if (excludeList[excludeListSize++] == NULL) - fatalError( "Option requires an argument: No file specified\n"); - /* Tack a NULL onto the end of the list */ - excludeList[excludeListSize] = NULL; - stopIt=TRUE; - break; - } -#endif - if (strcmp(*argv, "-help")==0) { - usage(tar_usage); - } - break; - - default: - fatalError( "Unknown tar flag '%c'\n" - "Try `tar --help' for more information\n", **argv); - } - ++(*argv); - } - } - - /* - * Do the correct type of action supplying the rest of the - * command line arguments as the list of files to process. - */ - if (createFlag == TRUE) { -#ifndef BB_FEATURE_TAR_CREATE - fatalError( "This version of tar was not compiled with tar creation support.\n"); -#else - exit(writeTarFile(tarName, tostdoutFlag, verboseFlag, argc, argv, excludeList)); -#endif - } - if (listFlag == TRUE || extractFlag == TRUE) { - exit(readTarFile(tarName, extractFlag, listFlag, tostdoutFlag, verboseFlag, excludeList)); - } - - flagError: - fatalError( "Exactly one of 'c', 'x' or 't' must be specified\n"); -} - +/* Might be faster (and bigger) if the dev/ino were stored in numeric order;) */ static void -fixUpPermissions(TarInfo *header) +addHardLinkInfo (HardLinkInfo **hlInfoHeadPtr, dev_t dev, ino_t ino, + short linkCount, const char *name) { - struct utimbuf t; - /* Now set permissions etc for the new file */ - chown(header->name, header->uid, header->gid); - chmod(header->name, header->mode); - /* Reset the time */ - t.actime = time(0); - t.modtime = header->mtime; - utime(header->name, &t); -} - -static int -tarExtractRegularFile(TarInfo *header, int extractFlag, int tostdoutFlag) -{ - size_t writeSize; - size_t readSize; - size_t actualWriteSz; - char buffer[BUFSIZ]; - size_t size = header->size; - int outFd=fileno(stdout); - - /* Open the file to be written, if a file is supposed to be written */ - if (extractFlag==TRUE && tostdoutFlag==FALSE) { - /* Create the path to the file, just in case it isn't there... - * This should not screw up path permissions or anything. */ - createPath(header->name, 0777); - if ((outFd=open(header->name, O_CREAT|O_TRUNC|O_WRONLY, - header->mode & ~S_IFMT)) < 0) { - errorMsg(io_error, header->name, strerror(errno)); - return( FALSE); - } - } - - /* Write out the file, if we are supposed to be doing that */ - while ( size > 0 ) { - actualWriteSz=0; - if ( size > sizeof(buffer) ) - writeSize = readSize = sizeof(buffer); - else { - int mod = size % 512; - if ( mod != 0 ) - readSize = size + (512 - mod); - else - readSize = size; - writeSize = size; - } - if ( (readSize = fullRead(header->tarFd, buffer, readSize)) <= 0 ) { - /* Tarball seems to have a problem */ - errorMsg("tar: Unexpected EOF in archive\n"); - return( FALSE); - } - if ( readSize < writeSize ) - writeSize = readSize; - - /* Write out the file, if we are supposed to be doing that */ - if (extractFlag==TRUE) { - - if ((actualWriteSz=fullWrite(outFd, buffer, writeSize)) != writeSize ) { - /* Output file seems to have a problem */ - errorMsg(io_error, header->name, strerror(errno)); - return( FALSE); - } - } else { - actualWriteSz=writeSize; - } - - size -= actualWriteSz; - } - - /* Now we are done writing the file out, so try - * and fix up the permissions and whatnot */ - if (extractFlag==TRUE && tostdoutFlag==FALSE) { - close(outFd); - fixUpPermissions(header); - } - return( TRUE); + /* Note: hlInfoHeadPtr can never be NULL! */ + HardLinkInfo *hlInfo; + + hlInfo = (HardLinkInfo *)xmalloc(sizeof(HardLinkInfo)+strlen(name)+1); + if (hlInfo) { + hlInfo->next = *hlInfoHeadPtr; + *hlInfoHeadPtr = hlInfo; + hlInfo->dev = dev; + hlInfo->ino = ino; + hlInfo->linkCount = linkCount; + strcpy(hlInfo->name, name); + } + return; } -static int -tarExtractDirectory(TarInfo *header, int extractFlag, int tostdoutFlag) -{ - - if (extractFlag==FALSE || tostdoutFlag==TRUE) - return( TRUE); - - if (createPath(header->name, header->mode) != TRUE) { - errorMsg("tar: %s: Cannot mkdir: %s\n", - header->name, strerror(errno)); - return( FALSE); - } - /* make the final component, just in case it was - * omitted by createPath() (which will skip the - * directory if it doesn't have a terminating '/') */ - if (mkdir(header->name, header->mode) == 0) { - fixUpPermissions(header); - } - return( TRUE); -} - -static int -tarExtractHardLink(TarInfo *header, int extractFlag, int tostdoutFlag) -{ - if (extractFlag==FALSE || tostdoutFlag==TRUE) - return( TRUE); - - if (link(header->linkname, header->name) < 0) { - errorMsg("tar: %s: Cannot create hard link to '%s': %s\n", - header->name, header->linkname, strerror(errno)); - return( FALSE); - } - - /* Now set permissions etc for the new directory */ - fixUpPermissions(header); - return( TRUE); -} - -static int -tarExtractSymLink(TarInfo *header, int extractFlag, int tostdoutFlag) -{ - if (extractFlag==FALSE || tostdoutFlag==TRUE) - return( TRUE); - -#ifdef S_ISLNK - if (symlink(header->linkname, header->name) < 0) { - errorMsg("tar: %s: Cannot create symlink to '%s': %s\n", - header->name, header->linkname, strerror(errno)); - return( FALSE); - } - /* Try to change ownership of the symlink. - * If libs doesn't support that, don't bother. - * Changing the pointed-to-file is the Wrong Thing(tm). - */ -#if (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 1) - lchown(header->name, header->uid, header->gid); -#endif - - /* Do not change permissions or date on symlink, - * since it changes the pointed to file instead. duh. */ -#else - errorMsg("tar: %s: Cannot create symlink to '%s': %s\n", - header->name, header->linkname, - "symlinks not supported"); -#endif - return( TRUE); -} - -static int -tarExtractSpecial(TarInfo *header, int extractFlag, int tostdoutFlag) -{ - if (extractFlag==FALSE || tostdoutFlag==TRUE) - return( TRUE); - - if (S_ISCHR(header->mode) || S_ISBLK(header->mode) || S_ISSOCK(header->mode)) { - if (mknod(header->name, header->mode, makedev(header->devmajor, header->devminor)) < 0) { - errorMsg("tar: %s: Cannot mknod: %s\n", - header->name, strerror(errno)); - return( FALSE); - } - } else if (S_ISFIFO(header->mode)) { - if (mkfifo(header->name, header->mode) < 0) { - errorMsg("tar: %s: Cannot mkfifo: %s\n", - header->name, strerror(errno)); - return( FALSE); - } - } - - /* Now set permissions etc for the new directory */ - fixUpPermissions(header); - return( TRUE); -} - -/* Read an octal value in a field of the specified width, with optional - * spaces on both sides of the number and with an optional null character - * at the end. Returns -1 on an illegal format. */ -static long getOctal(const char *cp, int size) -{ - long val = 0; - - for(;(size > 0) && (*cp == ' '); cp++, size--); - if ((size == 0) || !isOctal(*cp)) - return -1; - for(; (size > 0) && isOctal(*cp); size--) { - val = val * 8 + *cp++ - '0'; - } - for (;(size > 0) && (*cp == ' '); cp++, size--); - if ((size > 0) && *cp) - return -1; - return val; -} - - -/* Parse the tar header and fill in the nice struct with the details */ -static int -readTarHeader(struct TarHeader *rawHeader, struct TarInfo *header) +static void +freeHardLinkInfo (HardLinkInfo **hlInfoHeadPtr) { - int i; - long chksum, sum=0; - unsigned char *s = (unsigned char *)rawHeader; - - header->name = rawHeader->name; - /* Check for and relativify any absolute paths */ - if ( *(header->name) == '/' ) { - static int alreadyWarned=FALSE; - - while (*(header->name) == '/') - ++*(header->name); - - if (alreadyWarned == FALSE) { - errorMsg("tar: Removing leading '/' from member names\n"); - alreadyWarned = TRUE; + HardLinkInfo *hlInfo = NULL; + HardLinkInfo *hlInfoNext = NULL; + + if (hlInfoHeadPtr) { + hlInfo = *hlInfoHeadPtr; + while (hlInfo) { + hlInfoNext = hlInfo->next; + free(hlInfo); + hlInfo = hlInfoNext; } + *hlInfoHeadPtr = NULL; } - - header->mode = getOctal(rawHeader->mode, sizeof(rawHeader->mode)); - header->uid = getOctal(rawHeader->uid, sizeof(rawHeader->uid)); - header->gid = getOctal(rawHeader->gid, sizeof(rawHeader->gid)); - header->size = getOctal(rawHeader->size, sizeof(rawHeader->size)); - header->mtime = getOctal(rawHeader->mtime, sizeof(rawHeader->mtime)); - chksum = getOctal(rawHeader->chksum, sizeof(rawHeader->chksum)); - header->type = rawHeader->typeflag; - header->linkname = rawHeader->linkname; - header->devmajor = getOctal(rawHeader->devmajor, sizeof(rawHeader->devmajor)); - header->devminor = getOctal(rawHeader->devminor, sizeof(rawHeader->devminor)); - - /* Check the checksum */ - for (i = sizeof(*rawHeader); i-- != 0;) { - sum += *s++; - } - /* Remove the effects of the checksum field (replace - * with blanks for the purposes of the checksum) */ - s = rawHeader->chksum; - for (i = sizeof(rawHeader->chksum) ; i-- != 0;) { - sum -= *s++; - } - sum += ' ' * sizeof(rawHeader->chksum); - if (sum == chksum ) - return ( TRUE); - return( FALSE); + return; } - -/* - * Read a tar file and extract or list the specified files within it. - * If the list is empty than all files are extracted or listed. - */ -static int readTarFile(const char* tarName, int extractFlag, int listFlag, - int tostdoutFlag, int verboseFlag, char** excludeList) +/* Might be faster (and bigger) if the dev/ino were stored in numeric order;) */ +static HardLinkInfo * +findHardLinkInfo (HardLinkInfo *hlInfo, dev_t dev, ino_t ino) { - int status, tarFd=-1; - int errorFlag=FALSE; - TarHeader rawHeader; - TarInfo header; -#if defined BB_FEATURE_TAR_EXCLUDE - char** tmpList; -#endif - - /* Open the tar file for reading. */ - if (!strcmp(tarName, "-")) - tarFd = fileno(stdin); - else - tarFd = open(tarName, O_RDONLY); - if (tarFd < 0) { - errorMsg( "Error opening '%s': %s\n", tarName, strerror(errno)); - return ( FALSE); - } - - /* Set the umask for this process so it doesn't - * screw up permission setting for us later. */ - umask(0); - - /* Read the tar file, and iterate over it one file at a time */ - while ( (status = fullRead(tarFd, (char*)&rawHeader, TAR_BLOCK_SIZE)) == TAR_BLOCK_SIZE ) { - - /* First, try to read the header */ - if ( readTarHeader(&rawHeader, &header) == FALSE ) { - if ( *(header.name) == '\0' ) { - goto endgame; - } else { - errorFlag=TRUE; - errorMsg("Bad tar header, skipping\n"); - continue; - } - } - if ( *(header.name) == '\0' ) - goto endgame; - header.tarFd = tarFd; - -#if defined BB_FEATURE_TAR_EXCLUDE - { - int skipFlag=FALSE; - /* Check for excluded files.... */ - for (tmpList=excludeList; tmpList && *tmpList; tmpList++) { - /* Do some extra hoop jumping for when directory names - * end in '/' but the entry in tmpList doesn't */ - if (strncmp( *tmpList, header.name, strlen(*tmpList))==0 || ( - header.name[strlen(header.name)-1]=='/' - && strncmp( *tmpList, header.name, - MIN(strlen(header.name)-1, strlen(*tmpList)))==0)) { - /* If it is a regular file, pretend to extract it with - * the extractFlag set to FALSE, so the junk in the tarball - * is properly skipped over */ - if ( header.type==REGTYPE || header.type==REGTYPE0 ) { - tarExtractRegularFile(&header, FALSE, FALSE); - } - skipFlag=TRUE; - break; - } - } - /* There are not the droids you're looking for, move along */ - if (skipFlag==TRUE) - continue; - } -#endif - /* Special treatment if the list (-t) flag is on */ - if (verboseFlag == TRUE && extractFlag == FALSE) { - int len, len1; - char buf[35]; - struct tm *tm = localtime (&(header.mtime)); - - len=printf("%s ", modeString(header.mode)); - memset(buf, 0, 8*sizeof(char)); - my_getpwuid(buf, header.uid); - if (! *buf) - len+=printf("%d", header.uid); - else - len+=printf("%s", buf); - memset(buf, 0, 8*sizeof(char)); - my_getgrgid(buf, header.gid); - if (! *buf) - len+=printf("/%-d ", header.gid); - else - len+=printf("/%-s ", buf); - - if (header.type==CHRTYPE || header.type==BLKTYPE) { - len1=snprintf(buf, sizeof(buf), "%ld,%-ld ", - header.devmajor, header.devminor); - } else { - len1=snprintf(buf, sizeof(buf), "%lu ", (long)header.size); - } - /* Jump through some hoops to make the columns match up */ - for(;(len+len1)<31;len++) - printf(" "); - printf(buf); - - /* Use ISO 8610 time format */ - if (tm) { - printf ("%04d-%02d-%02d %02d:%02d:%02d ", - tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec); - } - } - /* List contents if we are supposed to do that */ - if (verboseFlag == TRUE || listFlag == TRUE) { - /* Now the normal listing */ - printf("%s", header.name); - } - if (verboseFlag == TRUE && listFlag == TRUE) { - /* If this is a link, say so */ - if (header.type==LNKTYPE) - printf(" link to %s", header.linkname); - else if (header.type==SYMTYPE) - printf(" -> %s", header.linkname); - } - if (verboseFlag == TRUE || listFlag == TRUE) { - printf("\n"); - } - - /* Remove any clutter lying in our way */ - if (extractFlag == TRUE) /* .. but only if we are extracting (as */ - unlink( header.name); /* opposed to listing) (rob@sysgo.de) */ - - /* If we got here, we can be certain we have a legitimate - * header to work with. So work with it. */ - switch ( header.type ) { - case REGTYPE: - case REGTYPE0: - /* If the name ends in a '/' then assume it is - * supposed to be a directory, and fall through */ - if (header.name[strlen(header.name)-1] != '/') { - if (tarExtractRegularFile(&header, extractFlag, tostdoutFlag)==FALSE) - errorFlag=TRUE; - break; - } - case DIRTYPE: - if (tarExtractDirectory( &header, extractFlag, tostdoutFlag)==FALSE) - errorFlag=TRUE; - break; - case LNKTYPE: - if (tarExtractHardLink( &header, extractFlag, tostdoutFlag)==FALSE) - errorFlag=TRUE; - break; - case SYMTYPE: - if (tarExtractSymLink( &header, extractFlag, tostdoutFlag)==FALSE) - errorFlag=TRUE; - break; - case CHRTYPE: - case BLKTYPE: - case FIFOTYPE: - if (tarExtractSpecial( &header, extractFlag, tostdoutFlag)==FALSE) - errorFlag=TRUE; - break; - default: - close( tarFd); - return( FALSE); - } + while(hlInfo) { + if ((ino == hlInfo->ino) && (dev == hlInfo->dev)) + break; + hlInfo = hlInfo->next; } - close(tarFd); - if (status > 0) { - /* Bummer - we read a partial header */ - errorMsg( "Error reading '%s': %s\n", tarName, strerror(errno)); - return ( FALSE); - } - else if (errorFlag==TRUE) { - errorMsg( "tar: Error exit delayed from previous errors\n"); - return( FALSE); - } else - return( status); - - /* Stuff to do when we are done */ -endgame: - close( tarFd); - if ( *(header.name) == '\0' ) { - if (errorFlag==TRUE) - errorMsg( "tar: Error exit delayed from previous errors\n"); - else - return( TRUE); - } - return( FALSE); + return(hlInfo); } - -#ifdef BB_FEATURE_TAR_CREATE - -/* Some info to be carried along when creating a new tarball */ -struct TarBallInfo -{ - char* fileName; /* File name of the tarball */ - int tarFd; /* Open-for-write file descriptor - for the tarball */ - struct stat statBuf; /* Stat info for the tarball, letting - us know the inode and device that the - tarball lives, so we can avoid trying - to include the tarball into itself */ - int verboseFlag; /* Whether to print extra stuff or not */ - char** excludeList; /* List of files to not include */ -}; -typedef struct TarBallInfo TarBallInfo; - - /* Put an octal string into the specified buffer. * The number is zero and space padded and possibly null padded. * Returns TRUE if successful. */ @@ -765,47 +227,17 @@ static int putOctal (char *cp, int len, long value) /* Write out a tar header for the specified file/directory/whatever */ static int -writeTarHeader(struct TarBallInfo *tbInfo, const char *fileName, struct stat *statbuf) +writeTarHeader(struct TarBallInfo *tbInfo, const char *header_name, + const char *real_name, struct stat *statbuf) { long chksum=0; struct TarHeader header; -#if defined BB_FEATURE_TAR_EXCLUDE - char** tmpList; -#endif const unsigned char *cp = (const unsigned char *) &header; ssize_t size = sizeof(struct TarHeader); - + memset( &header, 0, size); - if (*fileName=='/') { - static int alreadyWarned=FALSE; - if (alreadyWarned==FALSE) { - errorMsg("tar: Removing leading '/' from member names\n"); - alreadyWarned=TRUE; - } - strncpy(header.name, fileName+1, sizeof(header.name)); - } - else { - strncpy(header.name, fileName, sizeof(header.name)); - } - -#if defined BB_FEATURE_TAR_EXCLUDE - /* Check for excluded files.... */ - for (tmpList=tbInfo->excludeList; tmpList && *tmpList; tmpList++) { - /* Do some extra hoop jumping for when directory names - * end in '/' but the entry in tmpList doesn't */ - if (strncmp( *tmpList, header.name, strlen(*tmpList))==0 || ( - header.name[strlen(header.name)-1]=='/' - && strncmp( *tmpList, header.name, - MIN(strlen(header.name)-1, strlen(*tmpList)))==0)) { - /* Set the mode to something that is not a regular file, thereby - * faking out writeTarFile into thinking that nothing further need - * be done for this file. Yes, I know this is ugly, but it works. */ - statbuf->st_mode = 0; - return( TRUE); - } - } -#endif + strncpy(header.name, header_name, sizeof(header.name)); putOctal(header.mode, sizeof(header.mode), statbuf->st_mode); putOctal(header.uid, sizeof(header.uid), statbuf->st_uid); @@ -823,18 +255,17 @@ writeTarHeader(struct TarBallInfo *tbInfo, const char *fileName, struct stat *st if (! *header.uname) strcpy(header.uname, "root"); - /* WARNING/NOTICE: I break Hard Links */ - if (S_ISLNK(statbuf->st_mode)) { - int link_size=0; - char buffer[BUFSIZ]; - header.typeflag = SYMTYPE; - link_size = readlink(fileName, buffer, sizeof(buffer) - 1); - if ( link_size < 0) { - errorMsg("Error reading symlink '%s': %s\n", header.name, strerror(errno)); + if (tbInfo->hlInfo) { + /* This is a hard link */ + header.typeflag = LNKTYPE; + strncpy(header.linkname, tbInfo->hlInfo->name, sizeof(header.linkname)); + } else if (S_ISLNK(statbuf->st_mode)) { + char *lpath = xreadlink(real_name); + if (!lpath) /* Already printed err msg inside xreadlink() */ return ( FALSE); - } - buffer[link_size] = '\0'; - strncpy(header.linkname, buffer, sizeof(header.linkname)); + header.typeflag = SYMTYPE; + strncpy(header.linkname, lpath, sizeof(header.linkname)); + free(lpath); } else if (S_ISDIR(statbuf->st_mode)) { header.typeflag = DIRTYPE; strncat(header.name, "/", sizeof(header.name)); @@ -852,11 +283,11 @@ writeTarHeader(struct TarBallInfo *tbInfo, const char *fileName, struct stat *st header.typeflag = REGTYPE; putOctal(header.size, sizeof(header.size), statbuf->st_size); } else { - errorMsg("tar: %s: Unknown file type\n", fileName); + error_msg("%s: Unknown file type", real_name); return ( FALSE); } - /* Calculate and store the checksum (i.e. the sum of all of the bytes of + /* Calculate and store the checksum (i.e., the sum of all of the bytes of * the header). The checksum field must be filled with blanks for the * calculation. The checksum field is formatted differently from the * other fields: it has [6] digits, a null, then a space -- rather than @@ -868,8 +299,8 @@ writeTarHeader(struct TarBallInfo *tbInfo, const char *fileName, struct stat *st putOctal(header.chksum, 7, chksum); /* Now write the header out to disk */ - if ((size=fullWrite(tbInfo->tarFd, (char*)&header, sizeof(struct TarHeader))) < 0) { - errorMsg(io_error, fileName, strerror(errno)); + if ((size=full_write(tbInfo->tarFd, (char*)&header, sizeof(struct TarHeader))) < 0) { + error_msg(io_error, real_name, strerror(errno)); return ( FALSE); } /* Pad the header up to the tar block size */ @@ -877,20 +308,70 @@ writeTarHeader(struct TarBallInfo *tbInfo, const char *fileName, struct stat *st write(tbInfo->tarFd, "\0", 1); } /* Now do the verbose thing (or not) */ - if (tbInfo->verboseFlag==TRUE) - fprintf(stdout, "%s\n", header.name); + + if (tbInfo->verboseFlag) { + FILE *vbFd = stdout; + if (tbInfo->verboseFlag == 2) // If the archive goes to stdout, verbose to stderr + vbFd = stderr; + fprintf(vbFd, "%s\n", header.name); + } return ( TRUE); } +# if defined CONFIG_FEATURE_TAR_EXCLUDE +static int exclude_file(char **excluded_files, const char *file) +{ + int i; + + if (excluded_files == NULL) + return 0; + + for (i = 0; excluded_files[i] != NULL; i++) { + if (excluded_files[i][0] == '/') { + if (fnmatch(excluded_files[i], file, + FNM_PATHNAME | FNM_LEADING_DIR) == 0) + return 1; + } else { + const char *p; + + for (p = file; p[0] != '\0'; p++) { + if ((p == file || p[-1] == '/') && p[0] != '/' && + fnmatch(excluded_files[i], p, + FNM_PATHNAME | FNM_LEADING_DIR) == 0) + return 1; + } + } + } + + return 0; +} +#endif static int writeFileToTarball(const char *fileName, struct stat *statbuf, void* userData) { struct TarBallInfo *tbInfo = (struct TarBallInfo *)userData; + const char *header_name; + + /* + ** Check to see if we are dealing with a hard link. + ** If so - + ** Treat the first occurance of a given dev/inode as a file while + ** treating any additional occurances as hard links. This is done + ** by adding the file information to the HardLinkInfo linked list. + */ + tbInfo->hlInfo = NULL; + if (statbuf->st_nlink > 1) { + tbInfo->hlInfo = findHardLinkInfo(tbInfo->hlInfoHead, statbuf->st_dev, + statbuf->st_ino); + if (tbInfo->hlInfo == NULL) + addHardLinkInfo (&tbInfo->hlInfoHead, statbuf->st_dev, + statbuf->st_ino, statbuf->st_nlink, fileName); + } /* It is against the rules to archive a socket */ if (S_ISSOCK(statbuf->st_mode)) { - errorMsg("tar: %s: socket ignored\n", fileName); + error_msg("%s: socket ignored", fileName); return( TRUE); } @@ -899,37 +380,62 @@ static int writeFileToTarball(const char *fileName, struct stat *statbuf, void* * the new tarball */ if (tbInfo->statBuf.st_dev == statbuf->st_dev && tbInfo->statBuf.st_ino == statbuf->st_ino) { - errorMsg("tar: %s: file is the archive; skipping\n", fileName); + error_msg("%s: file is the archive; skipping", fileName); return( TRUE); } - if (writeTarHeader(tbInfo, fileName, statbuf)==FALSE) { + header_name = fileName; + while (header_name[0] == '/') { + static int alreadyWarned=FALSE; + if (alreadyWarned==FALSE) { + error_msg("Removing leading '/' from member names"); + alreadyWarned=TRUE; + } + header_name++; + } + + if (strlen(fileName) >= NAME_SIZE) { + error_msg(name_longer_than_foo, NAME_SIZE); + return ( TRUE); + } + + if (header_name[0] == '\0') + return TRUE; + +# if defined CONFIG_FEATURE_TAR_EXCLUDE + if (exclude_file(tbInfo->excludeList, header_name)) { + return SKIP; + } +# endif //CONFIG_FEATURE_TAR_EXCLUDE + + if (writeTarHeader(tbInfo, header_name, fileName, statbuf)==FALSE) { return( FALSE); } /* Now, if the file is a regular file, copy it out to the tarball */ - if (S_ISREG(statbuf->st_mode)) { + if ((tbInfo->hlInfo == NULL) + && (S_ISREG(statbuf->st_mode))) { int inputFileFd; char buffer[BUFSIZ]; ssize_t size=0, readSize=0; /* open the file we want to archive, and make sure all is well */ if ((inputFileFd = open(fileName, O_RDONLY)) < 0) { - errorMsg("tar: %s: Cannot open: %s\n", fileName, strerror(errno)); + error_msg("%s: Cannot open: %s", fileName, strerror(errno)); return( FALSE); } /* write the file to the archive */ - while ( (size = fullRead(inputFileFd, buffer, sizeof(buffer))) > 0 ) { - if (fullWrite(tbInfo->tarFd, buffer, size) != size ) { + while ( (size = full_read(inputFileFd, buffer, sizeof(buffer))) > 0 ) { + if (full_write(tbInfo->tarFd, buffer, size) != size ) { /* Output file seems to have a problem */ - errorMsg(io_error, fileName, strerror(errno)); + error_msg(io_error, fileName, strerror(errno)); return( FALSE); } readSize+=size; } if (size == -1) { - errorMsg(io_error, fileName, strerror(errno)); + error_msg(io_error, fileName, strerror(errno)); return( FALSE); } /* Pad the file up to the tar block size */ @@ -942,43 +448,102 @@ static int writeFileToTarball(const char *fileName, struct stat *statbuf, void* return( TRUE); } -static int writeTarFile(const char* tarName, int tostdoutFlag, - int verboseFlag, int argc, char **argv, char** excludeList) +static int writeTarFile(const char* tarName, int verboseFlag, char **argv, + char** excludeList, int gzip) { - int tarFd=-1; +#ifdef CONFIG_FEATURE_TAR_GZIP + int gzipDataPipe [2] = { -1, -1 }; + int gzipStatusPipe [2] = { -1, -1 }; + pid_t gzipPid = 0; +#endif + int errorFlag=FALSE; ssize_t size; struct TarBallInfo tbInfo; - tbInfo.verboseFlag = verboseFlag; + tbInfo.hlInfoHead = NULL; /* Make sure there is at least one file to tar up. */ - if (argc <= 0) - fatalError("tar: Cowardly refusing to create an empty archive\n"); + if (*argv == NULL) + error_msg_and_die("Cowardly refusing to create an empty archive"); /* Open the tar file for writing. */ - if (tostdoutFlag == TRUE) + if (tarName == NULL) { tbInfo.tarFd = fileno(stdout); - else + tbInfo.verboseFlag = verboseFlag ? 2 : 0; + } + else { tbInfo.tarFd = open (tarName, O_WRONLY | O_CREAT | O_TRUNC, 0644); + tbInfo.verboseFlag = verboseFlag ? 1 : 0; + } + if (tbInfo.tarFd < 0) { - errorMsg( "tar: Error opening '%s': %s\n", tarName, strerror(errno)); + perror_msg( "Error opening '%s'", tarName); + freeHardLinkInfo(&tbInfo.hlInfoHead); return ( FALSE); } - tbInfo.excludeList=excludeList; + /* Store the stat info for the tarball's file, so * can avoid including the tarball into itself.... */ if (fstat(tbInfo.tarFd, &tbInfo.statBuf) < 0) - fatalError(io_error, tarName, strerror(errno)); + error_msg_and_die(io_error, tarName, strerror(errno)); + +#ifdef CONFIG_FEATURE_TAR_GZIP + if ( gzip ) { + if ( socketpair ( AF_UNIX, SOCK_STREAM, 0, gzipDataPipe ) < 0 || pipe ( gzipStatusPipe ) < 0 ) + perror_msg_and_die ( "Failed to create gzip pipe" ); + + signal ( SIGPIPE, SIG_IGN ); // we only want EPIPE on errors + + gzipPid = fork ( ); + + if ( gzipPid == 0 ) { + dup2 ( gzipDataPipe [0], 0 ); + close ( gzipDataPipe [1] ); - /* Set the umask for this process so it doesn't - * screw up permission setting for us later. */ - umask(0); + if ( tbInfo. tarFd != 1 ); + dup2 ( tbInfo. tarFd, 1 ); + + close ( gzipStatusPipe [0] ); + fcntl( gzipStatusPipe [1], F_SETFD, FD_CLOEXEC ); // close on exec shows sucess + execl ( "/bin/gzip", "gzip", "-f", 0 ); + + write ( gzipStatusPipe [1], "", 1 ); + close ( gzipStatusPipe [1] ); + + exit ( -1 ); + } + else if ( gzipPid > 0 ) { + close ( gzipDataPipe [0] ); + close ( gzipStatusPipe [1] ); + + while ( 1 ) { + char buf; + + int n = read ( gzipStatusPipe [0], &buf, 1 ); + if ( n == 1 ) + error_msg_and_die ( "Could not exec gzip process" ); // socket was not closed => error + else if (( n < 0 ) && ( errno==EAGAIN || errno==EINTR )) + continue; // try it again + break; + } + close ( gzipStatusPipe [0] ); + + tbInfo. tarFd = gzipDataPipe [1]; + } + else { + perror_msg_and_die ( "Failed to fork gzip process" ); + } + } +#endif + + tbInfo.excludeList=excludeList; + /* Read the directory/files and iterate over them one at a time */ - while (argc-- > 0) { - if (recursiveAction(*argv++, TRUE, FALSE, FALSE, + while (*argv != NULL) { + if (! recursive_action(*argv++, TRUE, FALSE, FALSE, writeFileToTarball, writeFileToTarball, - (void*) &tbInfo) == FALSE) { + (void*) &tbInfo)) { errorFlag = TRUE; } } @@ -988,19 +553,258 @@ static int writeTarFile(const char* tarName, int tostdoutFlag, } /* To be pedantically correct, we would check if the tarball - * is smaller then 20 tar blocks, and pad it if it was smaller, + * is smaller than 20 tar blocks, and pad it if it was smaller, * but that isn't necessary for GNU tar interoperability, and * so is considered a waste of space */ /* Hang up the tools, close up shop, head home */ - close(tarFd); - if (errorFlag == TRUE) { - errorMsg("tar: Error exit delayed from previous errors\n"); - return(FALSE); + close(tbInfo.tarFd); + if (errorFlag) + error_msg("Error exit delayed from previous errors"); + + freeHardLinkInfo(&tbInfo.hlInfoHead); + +#ifdef CONFIG_FEATURE_TAR_GZIP + if ( gzip && gzipPid ) { + if ( waitpid ( gzipPid, NULL, 0 ) == -1 ) + printf ( "Couldnt wait ?" ); } - return( TRUE); +#endif + + return !errorFlag; +} +#endif //tar_create + +void append_file_to_list(const char *new_name, char ***list, int *list_count) +{ + *list = realloc(*list, sizeof(char *) * (*list_count + 2)); + (*list)[*list_count] = xstrdup(new_name); + (*list_count)++; + (*list)[*list_count] = NULL; } +void append_file_list_to_list(char *filename, char ***name_list, int *num_of_entries) +{ + FILE *src_stream; + char *line; + + src_stream = xfopen(filename, "r"); + while ((line = get_line_from_file(src_stream)) != NULL) { + chomp (line); + append_file_to_list(line, name_list, num_of_entries); + free(line); + } + fclose(src_stream); +} +#ifdef CONFIG_FEATURE_TAR_EXCLUDE +/* + * Create a list of names that are in the include list AND NOT in the exclude lists + */ +char **list_and_not_list(char **include_list, char **exclude_list) +{ + char **new_include_list = NULL; + int new_include_count = 0; + int include_count = 0; + int exclude_count; + + if (include_list == NULL) { + return(NULL); + } + + while (include_list[include_count] != NULL) { + int found = FALSE; + exclude_count = 0; + while (exclude_list[exclude_count] != NULL) { + if (strcmp(include_list[include_count], exclude_list[exclude_count]) == 0) { + found = TRUE; + break; + } + exclude_count++; + } + + if (! found) { + new_include_list = realloc(new_include_list, sizeof(char *) * (include_count + 2)); + new_include_list[new_include_count] = include_list[include_count]; + new_include_count++; + } else { + free(include_list[include_count]); + } + include_count++; + } + new_include_list[new_include_count] = NULL; + return(new_include_list); +} #endif +int tar_main(int argc, char **argv) +{ + enum untar_funct_e { + /* This is optional */ + untar_unzip = 1, + /* Require one and only one of these */ + untar_list = 2, + untar_create = 4, + untar_extract = 8 + }; + + FILE *src_stream = NULL; + FILE *uncompressed_stream = NULL; + char **include_list = NULL; + char **exclude_list = NULL; + char *src_filename = NULL; + char *dst_prefix = NULL; + int opt; + unsigned short untar_funct = 0; + unsigned short untar_funct_required = 0; + unsigned short extract_function = 0; + int include_list_count = 0; +#ifdef CONFIG_FEATURE_TAR_EXCLUDE + int exclude_list_count = 0; +#endif +#ifdef CONFIG_FEATURE_TAR_GZIP + int gunzip_pid; + int gz_fd = 0; +#endif + + if (argc < 2) { + show_usage(); + } + + /* Prepend '-' to the first argument if required */ + if (argv[1][0] != '-') { + char *tmp = xmalloc(strlen(argv[1]) + 2); + tmp[0] = '-'; + strcpy(tmp + 1, argv[1]); + argv[1] = tmp; + } + + while ((opt = getopt(argc, argv, "ctxT:X:C:f:Opvz")) != -1) { + switch (opt) { + + /* One and only one of these is required */ + case 'c': + untar_funct_required |= untar_create; + break; + case 't': + untar_funct_required |= untar_list; + extract_function |= extract_list |extract_unconditional; + break; + case 'x': + untar_funct_required |= untar_extract; + extract_function |= (extract_all_to_fs | extract_unconditional | extract_create_leading_dirs); + break; + + /* These are optional */ + /* Exclude or Include files listed in */ +#ifdef CONFIG_FEATURE_TAR_EXCLUDE + case 'X': + append_file_list_to_list(optarg, &exclude_list, &exclude_list_count); + break; +#endif + case 'T': + // by default a list is an include list + append_file_list_to_list(optarg, &include_list, &include_list_count); + break; + + case 'C': // Change to dir + /* Make sure dst_prefix ends in a '/' */ + dst_prefix = concat_path_file(optarg, "/"); + break; + case 'f': // archive filename + if (strcmp(optarg, "-") == 0) { + src_filename = NULL; + } else { + src_filename = xstrdup(optarg); + } + break; + case 'O': + extract_function |= extract_to_stdout; + break; + case 'p': + break; + case 'v': + extract_function |= extract_verbose_list; + break; +#ifdef CONFIG_FEATURE_TAR_GZIP + case 'z': + untar_funct |= untar_unzip; + break; +#endif + default: + show_usage(); + } + } + + /* Make sure the valid arguments were passed */ + if (untar_funct_required == 0) { + error_msg_and_die("You must specify one of the `-ctx' options"); + } + if ((untar_funct_required != untar_create) && + (untar_funct_required != untar_extract) && + (untar_funct_required != untar_list)) { + error_msg_and_die("You may not specify more than one `ctx' option."); + } + untar_funct |= untar_funct_required; + + /* Setup an array of filenames to work with */ + while (optind < argc) { + append_file_to_list(argv[optind], &include_list, &include_list_count); + optind++; + } + if (extract_function & (extract_list | extract_all_to_fs)) { + if (dst_prefix == NULL) { + dst_prefix = xstrdup("./"); + } + + /* Setup the source of the tar data */ + if (src_filename != NULL) { + src_stream = xfopen(src_filename, "r"); + } else { + src_stream = stdin; + } +#ifdef CONFIG_FEATURE_TAR_GZIP + /* Get a binary tree of all the tar file headers */ + if (untar_funct & untar_unzip) { + uncompressed_stream = gz_open(src_stream, &gunzip_pid); + } else +#endif // CONFIG_FEATURE_TAR_GZIP + uncompressed_stream = src_stream; + + /* extract or list archive */ + unarchive(uncompressed_stream, stdout, &get_header_tar, extract_function, dst_prefix, include_list, exclude_list); + fclose(uncompressed_stream); + } +#ifdef CONFIG_FEATURE_TAR_CREATE + /* create an archive */ + else if (untar_funct & untar_create) { + int verboseFlag = FALSE; + int gzipFlag = FALSE; + +#ifdef CONFIG_FEATURE_TAR_GZIP + if (untar_funct & untar_unzip) + gzipFlag = TRUE; + +#endif // CONFIG_FEATURE_TAR_GZIP + if (extract_function & extract_verbose_list) + verboseFlag = TRUE; + + writeTarFile(src_filename, verboseFlag, include_list, exclude_list, gzipFlag); + } +#endif // CONFIG_FEATURE_TAR_CREATE + + /* Cleanups */ +#ifdef CONFIG_FEATURE_TAR_GZIP + if ( !( untar_funct & untar_create ) && ( untar_funct & untar_unzip )) { + fclose(src_stream); + close(gz_fd); + gz_close(gunzip_pid); + } +#endif // CONFIG_FEATURE_TAR_GZIP +#ifdef CONFIG_FEATURE_CLEAN_UP + if (src_filename) { + free(src_filename); + } +#endif + return(EXIT_SUCCESS); +}