/* vi: set sw=4 ts=4: */ /* * Mini tar implementation for busybox based on code taken from sash. * * Copyright (c) 1999 by David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. * * Permission to distribute this code under the GPL has been granted. * * Modified for busybox by Erik Andersen * Adjusted to grok stdin/stdout options. * * Modified to handle device special files by Matt Porter * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "internal.h" #include #include #include #include #include #include #include #include #include #include /* for PATH_MAX */ #ifdef BB_FEATURE_TAR_CREATE static const char tar_usage[] = "tar -[cxtvOf] [tarFileName] [FILE] ...\n\n" "Create, extract, or list files from a tar file.\n\n" "Options:\n" "\tc=create, x=extract, t=list contents, v=verbose,\n" "\tO=extract to stdout, f=tarfile or \"-\" for stdin\n"; #else static const char tar_usage[] = "tar -[xtvOf] [tarFileName] [FILE] ...\n\n" "Extract, or list files stored in a tar file. This\n" "version of tar does not support creation of tar files.\n\n" "Options:\n" "\tx=extract, t=list contents, v=verbose,\n" "\tO=extract to stdout, f=tarfile or \"-\" for stdin\n"; #endif /* Tar file constants */ /* POSIX tar Header Block, from POSIX 1003.1-1990 */ struct TarHeader { /* byte offset */ char name[100]; /* 0 */ char mode[8]; /* 100 */ char uid[8]; /* 108 */ char gid[8]; /* 116 */ char size[12]; /* 124 */ char mtime[12]; /* 136 */ char chksum[8]; /* 148 */ char typeflag; /* 156 */ char linkname[100]; /* 157 */ char magic[6]; /* 257 */ char version[2]; /* 263 */ char uname[32]; /* 265 */ char gname[32]; /* 297 */ char devmajor[8]; /* 329 */ char devminor[8]; /* 337 */ char prefix[155]; /* 345 */ /* padding 500 */ }; typedef struct TarHeader TarHeader; /* A few useful constants */ #define TAR_MAGIC "ustar" /* ustar and a null */ #define TAR_VERSION "00" /* 00 and no null */ #define TAR_MAGIC_LEN 6 #define TAR_VERSION_LEN 2 #define TAR_NAME_LEN 100 #define TAR_BLOCK_SIZE 512 /* A nice enum with all the possible tar file content types */ enum TarFileType { REGTYPE = '0', /* regular file */ REGTYPE0 = '\0', /* regular file (ancient bug compat)*/ LNKTYPE = '1', /* hard link */ SYMTYPE = '2', /* symbolic link */ CHRTYPE = '3', /* character special */ BLKTYPE = '4', /* block special */ DIRTYPE = '5', /* directory */ FIFOTYPE = '6', /* FIFO special */ CONTTYPE = '7', /* reserved */ }; 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 */ dev_t device; /* Special device for mknod() */ }; typedef struct TarInfo TarInfo; /* Static data */ static const unsigned long TarChecksumOffset = (const unsigned long)&(((TarHeader *)0)->chksum); /* Local procedures to restore files from a tar file. */ static int readTarFile(const char* tarName, int extractFlag, int listFlag, int tostdoutFlag, int verboseFlag); static long getOctal(const char *cp, int len); static int parseTarHeader(struct TarHeader *rawHeader, struct TarInfo *header); #ifdef BB_FEATURE_TAR_CREATE /* * Local procedures to save files into a tar file. */ static void saveFile(const char *fileName, int seeLinks); static void saveRegularFile(const char *fileName, const struct stat *statbuf); static void saveDirectory(const char *fileName, const struct stat *statbuf); static void writeHeader(const char *fileName, const struct stat *statbuf); static void writeTarFile(int argc, char **argv); static void writeTarBlock(const char *buf, int len); static int putOctal(char *cp, int len, long value); #endif extern int tar_main(int argc, char **argv) { const char *tarName=NULL; const char *options; int listFlag = FALSE; int extractFlag = FALSE; int createFlag = FALSE; int verboseFlag = FALSE; int tostdoutFlag = FALSE; argc--; argv++; if (argc < 1) usage(tar_usage); /* Parse options */ if (**argv == '-') options = (*argv++) + 1; else options = (*argv++); argc--; for (; *options; options++) { switch (*options) { case 'f': if (tarName != NULL) fatalError( "Only one 'f' option allowed\n"); tarName = *argv++; if (tarName == NULL) fatalError( "Option requires an argument: No file specified\n"); argc--; 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; break; case '-': usage(tar_usage); break; default: fatalError( "Unknown tar flag '%c'\n" "Try `tar --help' for more information\n", *options); } } /* * 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(argc, argv)); #endif } else { exit(readTarFile(tarName, extractFlag, listFlag, tostdoutFlag, verboseFlag)); } flagError: fatalError( "Exactly one of 'c', 'x' or 't' must be specified\n"); } static void tarExtractRegularFile(TarInfo *header, int extractFlag, int listFlag, int tostdoutFlag, int verboseFlag) { return; } static void tarExtractDirectory(TarInfo *header, int extractFlag, int listFlag, int tostdoutFlag, int verboseFlag) { return; } static void tarExtractHardLink(TarInfo *header, int extractFlag, int listFlag, int tostdoutFlag, int verboseFlag) { return; } static void tarExtractSymLink(TarInfo *header, int extractFlag, int listFlag, int tostdoutFlag, int verboseFlag) { return; } static void tarExtractSpecial(TarInfo *header, int extractFlag, int listFlag, int tostdoutFlag, int verboseFlag) { 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) { int status, tarFd=0; int errorFlag=FALSE; TarHeader rawHeader; TarInfo header; /* 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", tarName, strerror(errno)); return ( FALSE); } /* Read the tar file */ while ( (status = fullRead(tarFd, (char*)&rawHeader, TAR_BLOCK_SIZE)) == TAR_BLOCK_SIZE ) { /* Now see if the header looks ok */ if ( parseTarHeader(&rawHeader, &header) == FALSE ) { close( tarFd); if ( *(header.name) == '\0' ) { goto endgame; } else { errorFlag=TRUE; errorMsg("Bad tar header, skipping\n"); continue; } } if ( *(header.name) == '\0' ) goto endgame; if (extractFlag == FALSE) { if (verboseFlag == TRUE) { printf("%s %3d/%-d ", modeString(header.mode), header.uid, header.gid); if (header.type==CHRTYPE || header.type==BLKTYPE) printf("%4d,%4d %s ", MAJOR(header.device), MINOR(header.device), timeString(header.mtime)); else printf("%9ld %s ", header.size, timeString(header.mtime)); } printf("%s", header.name); if (header.type==LNKTYPE) printf(" (link to \"%s\")", hp->linkName); else if (header.type==SYMTYPE) printf(" (symlink to \"%s\")", hp->linkName); printf("\n"); continue; } /* 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] != '/') { tarExtractRegularFile(&header, extractFlag, listFlag, tostdoutFlag, verboseFlag); break; } case DIRTYPE: tarExtractDirectory( &header, extractFlag, listFlag, tostdoutFlag, verboseFlag); break; case LNKTYPE: tarExtractHardLink( &header, extractFlag, listFlag, tostdoutFlag, verboseFlag); break; case SYMTYPE: tarExtractSymLink( &header, extractFlag, listFlag, tostdoutFlag, verboseFlag); break; case CHRTYPE: case BLKTYPE: case FIFOTYPE: tarExtractSpecial( &header, extractFlag, listFlag, tostdoutFlag, verboseFlag); break; default: close( tarFd); return( FALSE); } } close(tarFd); if (status > 0) { /* Bummer - we read a partial header */ errorMsg( "Error reading '%s': %s", tarName, strerror(errno)); return ( FALSE); } else return( status); /* Stuff we do when we know we are done with the file */ endgame: close( tarFd); if ( *(header.name) == '\0' ) { if (errorFlag==FALSE) return( TRUE); } return( FALSE); } /* * 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 parseTarHeader(struct TarHeader *rawHeader, struct TarInfo *header) { int i; long chksum, sum; unsigned char *s = (unsigned char *)rawHeader; header->name = rawHeader->name; 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->device = MAJOR(getOctal(rawHeader->devmajor, sizeof(rawHeader->devmajor))) | MINOR(getOctal(rawHeader->devminor, sizeof(rawHeader->devminor))); /* Check the checksum */ sum = ' ' * sizeof(rawHeader->chksum); for ( i = TarChecksumOffset; i > 0; i-- ) sum += *s++; s += sizeof(rawHeader->chksum); for ( i = (512 - TarChecksumOffset - sizeof(rawHeader->chksum)); i > 0; i-- ) sum += *s++; if (sum == chksum ) return ( TRUE); return( FALSE); } #if 0 if ((header->mode < 0) || (header->uid < 0) || (header->gid < 0) || (header->size < 0)) { errorMsg(stderr, "Bad tar header, skipping\n"); return( FALSE); } badHeader = FALSE; skipFileFlag = FALSE; devFileFlag = FALSE; /* * Check for the file modes. */ hardLink = ((hp->typeFlag == TAR_TYPE_HARD_LINK) || (hp->typeFlag == TAR_TYPE_HARD_LINK - '0')); softLink = ((hp->typeFlag == TAR_TYPE_SOFT_LINK) || (hp->typeFlag == TAR_TYPE_SOFT_LINK - '0')); /* * Check for a directory. */ if (outName[strlen(outName) - 1] == '/') mode |= S_IFDIR; /* * Check for absolute paths in the file. * If we find any, then warn the user and make them relative. */ if (*outName == '/') { while (*outName == '/') outName++; if (warnedRoot == FALSE) { fprintf(stderr, "Absolute path detected, removing leading slashes\n"); } warnedRoot = TRUE; } /* * See if we want this file to be restored. * If not, then set up to skip it. */ if (wantFileName(outName, argc, argv) == FALSE) { if (!hardLink && !softLink && (S_ISREG(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISSOCK(mode) || S_ISFIFO(mode))) { inHeader = (size == 0) ? TRUE : FALSE; dataCc = size; } skipFileFlag = TRUE; return; } /* * This file is to be handled. * If we aren't extracting then just list information about the file. */ if (extractFlag == FALSE) { if (verboseFlag == TRUE) { printf("%s %3d/%-d ", modeString(mode), uid, gid); if (S_ISCHR(mode) || S_ISBLK(mode)) printf("%4d,%4d %s ", major, minor, timeString(mtime)); else printf("%9ld %s ", size, timeString(mtime)); } printf("%s", outName); if (hardLink) printf(" (link to \"%s\")", hp->linkName); else if (softLink) printf(" (symlink to \"%s\")", hp->linkName); else if (S_ISREG(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISSOCK(mode) || S_ISFIFO(mode)) { inHeader = (size == 0) ? TRUE : FALSE; dataCc = size; } printf("\n"); return; } /* * We really want to extract the file. */ if (verboseFlag == TRUE) printf("x %s\n", outName); if (hardLink) { if (link(hp->linkName, outName) < 0) { perror(outName); return; } /* Set the file time */ utb.actime = mtime; utb.modtime = mtime; utime(outName, &utb); /* Set the file permissions */ chown(outName, uid, gid); chmod(outName, mode); return; } if (softLink) { #ifdef S_ISLNK if (symlink(hp->linkName, outName) < 0) { perror(outName); return; } /* 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(outName, uid, gid); #endif /* Do not change permissions or date on symlink, * since it changes the pointed to file instead. duh. */ #else fprintf(stderr, "Cannot create symbolic links\n"); #endif return; } /* Set the umask for this process so it doesn't * screw things up. */ umask(0); /* * If the file is a directory, then just create the path. */ if (S_ISDIR(mode)) { if (createPath(outName, mode) == TRUE) { /* make the final component, just in case it was * omitted by createPath() (which will skip the * directory if it doesn't have a terminating '/') */ mkdir(outName, mode); /* Set the file time */ utb.actime = mtime; utb.modtime = mtime; utime(outName, &utb); /* Set the file permissions */ chown(outName, uid, gid); chmod(outName, mode); return; } return; } /* * There is a file to write. * First create the path to it if necessary with default permissions. */ createPath(outName, 0777); inHeader = (size == 0) ? TRUE : FALSE; dataCc = size; /* * Start the output file. */ if (tostdoutFlag == TRUE) outFd = fileno(stdout); else { if (S_ISCHR(mode) || S_ISBLK(mode) || S_ISSOCK(mode)) { devFileFlag = TRUE; outFd = mknod(outName, mode, makedev(major, minor)); } else if (S_ISFIFO(mode)) { devFileFlag = TRUE; outFd = mkfifo(outName, mode); } else { outFd = open(outName, O_WRONLY | O_CREAT | O_TRUNC, mode); } if (outFd < 0) { perror(outName); skipFileFlag = TRUE; return; } /* Set the file time */ utb.actime = mtime; utb.modtime = mtime; utime(outName, &utb); /* Set the file permissions */ chown(outName, uid, gid); chmod(outName, mode); } /* * If the file is empty, then that's all we need to do. */ if (size == 0 && (tostdoutFlag == FALSE) && (devFileFlag == FALSE)) { close(outFd); outFd = -1; } } /* * Handle a data block of some specified size that was read. */ static void readData(const char *cp, int count) { /* * Reduce the amount of data left in this file. * If there is no more data left, then we need to read * the header again. */ dataCc -= count; if (dataCc <= 0) inHeader = TRUE; /* * If we aren't extracting files or this file is being * skipped then do nothing more. */ if (extractFlag == FALSE || skipFileFlag == TRUE) return; /* * Write the data to the output file. */ if (fullWrite(outFd, cp, count) < 0) { perror(outName); if (tostdoutFlag == FALSE) { close(outFd); outFd = -1; } skipFileFlag = TRUE; return; } /* * Check if we are done writing to the file now. */ if (dataCc <= 0 && tostdoutFlag == FALSE) { struct utimbuf utb; if (close(outFd)) perror(outName); /* Set the file time */ utb.actime = mtime; utb.modtime = mtime; utime(outName, &utb); /* Set the file permissions */ chown(outName, uid, gid); chmod(outName, mode); outFd = -1; } } /* * See if the specified file name belongs to one of the specified list * of path prefixes. An empty list implies that all files are wanted. * Returns TRUE if the file is selected. */ static int wantFileName(const char *fileName, int argc, char **argv) { const char *pathName; int fileLength; int pathLength; /* * If there are no files in the list, then the file is wanted. */ if (argc == 0) return TRUE; fileLength = strlen(fileName); /* * Check each of the test paths. */ while (argc-- > 0) { pathName = *argv++; pathLength = strlen(pathName); if (fileLength < pathLength) continue; if (memcmp(fileName, pathName, pathLength) != 0) continue; if ((fileLength == pathLength) || (fileName[pathLength] == '/')) { return TRUE; } } return FALSE; } /* From here to the end of the file is the tar writing stuff. * If you do not have BB_FEATURE_TAR_CREATE defined, this will * not be built. * */ #ifdef BB_FEATURE_TAR_CREATE /* * Write a tar file containing the specified files. */ static void writeTarFile(int argc, char **argv) { struct stat statbuf; /* * Make sure there is at least one file specified. */ if (argc <= 0) { fprintf(stderr, "No files specified to be saved\n"); errorFlag = TRUE; } /* * Create the tar file for writing. */ if ((tarName == NULL) || !strcmp(tarName, "-")) { tostdoutFlag = TRUE; tarFd = fileno(stdout); } else tarFd = open(tarName, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (tarFd < 0) { perror(tarName); errorFlag = TRUE; return; } /* * Get the device and inode of the tar file for checking later. */ if (fstat(tarFd, &statbuf) < 0) { perror(tarName); errorFlag = TRUE; goto done; } tarDev = statbuf.st_dev; tarInode = statbuf.st_ino; /* * Append each file name into the archive file. * Follow symbolic links for these top level file names. */ while (errorFlag == FALSE && (argc-- > 0)) { saveFile(*argv++, FALSE); } /* * Now write an empty block of zeroes to end the archive. */ writeTarBlock("", 1); done: /* * Close the tar file and check for errors if it was opened. */ if ((tostdoutFlag == FALSE) && (tarFd >= 0) && (close(tarFd) < 0)) perror(tarName); } /* * Save one file into the tar file. * If the file is a directory, then this will recursively save all of * the files and directories within the directory. The seeLinks * flag indicates whether or not we want to see symbolic links as * they really are, instead of blindly following them. */ static void saveFile(const char *fileName, int seeLinks) { int status; struct stat statbuf; if (verboseFlag == TRUE) printf("a %s\n", fileName); /* * Check that the file name will fit in the header. */ if (strlen(fileName) >= TAR_NAME_SIZE) { fprintf(stderr, "%s: File name is too long\n", fileName); return; } /* * Find out about the file. */ #ifdef S_ISLNK if (seeLinks == TRUE) status = lstat(fileName, &statbuf); else #endif status = stat(fileName, &statbuf); if (status < 0) { perror(fileName); return; } /* * Make sure we aren't trying to save our file into itself. */ if ((statbuf.st_dev == tarDev) && (statbuf.st_ino == tarInode)) { fprintf(stderr, "Skipping saving of archive file itself\n"); return; } /* * Check the type of file. */ mode = statbuf.st_mode; if (S_ISDIR(mode)) { saveDirectory(fileName, &statbuf); return; } if (S_ISREG(mode)) { saveRegularFile(fileName, &statbuf); return; } /* Some day add support for tarring these up... but not today. :) */ // if (S_ISLNK(mode) || S_ISFIFO(mode) || S_ISBLK(mode) || S_ISCHR (mode) ) { // fprintf (stderr, "%s: This version of tar can't store this type of file\n", fileName); // } /* * The file is a strange type of file, ignore it. */ fprintf(stderr, "%s: not a directory or regular file\n", fileName); } /* * Save a regular file to the tar file. */ static void saveRegularFile(const char *fileName, const struct stat *statbuf) { int sawEof; int fileFd; int cc; int dataCount; long fullDataCount; char data[TAR_BLOCK_SIZE * 16]; /* * Open the file for reading. */ fileFd = open(fileName, O_RDONLY); if (fileFd < 0) { perror(fileName); return; } /* * Write out the header for the file. */ writeHeader(fileName, statbuf); /* * Write the data blocks of the file. * We must be careful to write the amount of data that the stat * buffer indicated, even if the file has changed size. Otherwise * the tar file will be incorrect. */ fullDataCount = statbuf->st_size; sawEof = FALSE; while (fullDataCount > 0) { /* * Get the amount to write this iteration which is * the minumum of the amount left to write and the * buffer size. */ dataCount = sizeof(data); if (dataCount > fullDataCount) dataCount = (int) fullDataCount; /* * Read the data from the file if we haven't seen the * end of file yet. */ cc = 0; if (sawEof == FALSE) { cc = fullRead(fileFd, data, dataCount); if (cc < 0) { perror(fileName); (void) close(fileFd); errorFlag = TRUE; return; } /* * If the file ended too soon, complain and set * a flag so we will zero fill the rest of it. */ if (cc < dataCount) { fprintf(stderr, "%s: Short read - zero filling", fileName); sawEof = TRUE; } } /* * Zero fill the rest of the data if necessary. */ if (cc < dataCount) memset(data + cc, 0, dataCount - cc); /* * Write the buffer to the TAR file. */ writeTarBlock(data, dataCount); fullDataCount -= dataCount; } /* * Close the file. */ if ((tostdoutFlag == FALSE) && close(fileFd) < 0) fprintf(stderr, "%s: close: %s\n", fileName, strerror(errno)); } /* * Save a directory and all of its files to the tar file. */ static void saveDirectory(const char *dirName, const struct stat *statbuf) { DIR *dir; struct dirent *entry; int needSlash; char fullName[PATH_MAX + 1]; /* * Construct the directory name as used in the tar file by appending * a slash character to it. */ strcpy(fullName, dirName); strcat(fullName, "/"); /* * Write out the header for the directory entry. */ writeHeader(fullName, statbuf); /* * Open the directory. */ dir = opendir(dirName); if (dir == NULL) { fprintf(stderr, "Cannot read directory \"%s\": %s\n", dirName, strerror(errno)); return; } /* * See if a slash is needed. */ needSlash = (*dirName && (dirName[strlen(dirName) - 1] != '/')); /* * Read all of the directory entries and check them, * except for the current and parent directory entries. */ while (errorFlag == FALSE && ((entry = readdir(dir)) != NULL)) { if ((strcmp(entry->d_name, ".") == 0) || (strcmp(entry->d_name, "..") == 0)) { continue; } /* * Build the full path name to the file. */ strcpy(fullName, dirName); if (needSlash) strcat(fullName, "/"); strcat(fullName, entry->d_name); /* * Write this file to the tar file, noticing whether or not * the file is a symbolic link. */ saveFile(fullName, TRUE); } /* * All done, close the directory. */ closedir(dir); } /* * Write a tar header for the specified file name and status. * It is assumed that the file name fits. */ static void writeHeader(const char *fileName, const struct stat *statbuf) { long checkSum; const unsigned char *cp; int len; TarHeader header; /* * Zero the header block in preparation for filling it in. */ memset((char *) &header, 0, sizeof(header)); /* * Fill in the header. */ strcpy(header.name, fileName); strncpy(header.magic, TAR_MAGIC, sizeof(header.magic)); strncpy(header.version, TAR_VERSION, sizeof(header.version)); putOctal(header.mode, sizeof(header.mode), statbuf->st_mode & 0777); putOctal(header.uid, sizeof(header.uid), statbuf->st_uid); putOctal(header.gid, sizeof(header.gid), statbuf->st_gid); putOctal(header.size, sizeof(header.size), statbuf->st_size); putOctal(header.mtime, sizeof(header.mtime), statbuf->st_mtime); header.typeFlag = TAR_TYPE_REGULAR; /* * Calculate and store the checksum. * This is the sum of all of the bytes of the header, * with the checksum field itself treated as blanks. */ memset(header.checkSum, ' ', sizeof(header.checkSum)); cp = (const unsigned char *) &header; len = sizeof(header); checkSum = 0; while (len-- > 0) checkSum += *cp++; putOctal(header.checkSum, sizeof(header.checkSum), checkSum); /* * Write the tar header. */ writeTarBlock((const char *) &header, sizeof(header)); } /* * Write data to one or more blocks of the tar file. * The data is always padded out to a multiple of TAR_BLOCK_SIZE. * The errorFlag static variable is set on an error. */ static void writeTarBlock(const char *buf, int len) { int partialLength; int completeLength; char fullBlock[TAR_BLOCK_SIZE]; /* * If we had a write error before, then do nothing more. */ if (errorFlag == TRUE) return; /* * Get the amount of complete and partial blocks. */ partialLength = len % TAR_BLOCK_SIZE; completeLength = len - partialLength; /* * Write all of the complete blocks. */ if ((completeLength > 0) && !fullWrite(tarFd, buf, completeLength)) { perror(tarName); errorFlag = TRUE; return; } /* * If there are no partial blocks left, we are done. */ if (partialLength == 0) return; /* * Copy the partial data into a complete block, and pad the rest * of it with zeroes. */ memcpy(fullBlock, buf + completeLength, partialLength); memset(fullBlock + partialLength, 0, TAR_BLOCK_SIZE - partialLength); /* * Write the last complete block. */ if (!fullWrite(tarFd, fullBlock, TAR_BLOCK_SIZE)) { perror(tarName); errorFlag = TRUE; } } /* * Put an octal string into the specified buffer. * The number is zero and space padded and possibly null padded. * Returns TRUE if successful. */ static int putOctal(char *cp, int len, long value) { int tempLength; char *tempString; char tempBuffer[32]; /* * Create a string of the specified length with an initial space, * leading zeroes and the octal number, and a trailing null. */ tempString = tempBuffer; sprintf(tempString, " %0*lo", len - 2, value); tempLength = strlen(tempString) + 1; /* * If the string is too large, suppress the leading space. */ if (tempLength > len) { tempLength--; tempString++; } /* * If the string is still too large, suppress the trailing null. */ if (tempLength > len) tempLength--; /* * If the string is still too large, fail. */ if (tempLength > len) return FALSE; /* * Copy the string to the field. */ memcpy(cp, tempString, len); return TRUE; } #endif /* END CODE */ #endif