hush/tar.c

1240 lines
29 KiB
C
Raw Normal View History

/* vi: set sw=4 ts=4: */
1999-10-05 16:24:54 +00:00
/*
1999-10-20 22:08:37 +00:00
* Mini tar implementation for busybox based on code taken from sash.
*
1999-10-05 16:24:54 +00:00
* 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.
1999-10-20 22:08:37 +00:00
*
1999-10-12 22:26:06 +00:00
* Modified for busybox by Erik Andersen <andersee@debian.org>
1999-10-20 22:08:37 +00:00
* Adjusted to grok stdin/stdout options.
*
1999-11-12 01:30:18 +00:00
* Modified to handle device special files by Matt Porter
* <porter@debian.org>
*
1999-10-20 22:08:37 +00:00
* 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
*
1999-10-05 16:24:54 +00:00
*/
#include "internal.h"
#include <stdio.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <utime.h>
1999-11-12 01:30:18 +00:00
#include <sys/types.h>
1999-11-19 02:38:58 +00:00
#include <sys/sysmacros.h>
#include <sys/param.h> /* for PATH_MAX */
1999-10-05 16:24:54 +00:00
#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";
1999-10-05 16:24:54 +00:00
#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
1999-10-05 16:24:54 +00:00
2000-03-23 01:09:18 +00:00
/* Tar file constants */
1999-10-05 16:24:54 +00:00
2000-03-23 01:09:18 +00:00
/* 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);
1999-10-05 16:24:54 +00:00
/* Local procedures to restore files from a tar file. */
2000-03-23 01:09:18 +00:00
static int readTarFile(const char* tarName, int extractFlag, int listFlag,
int tostdoutFlag, int verboseFlag);
static long getOctal(const char *cp, int len);
2000-03-23 01:09:18 +00:00
static int parseTarHeader(struct TarHeader *rawHeader, struct TarInfo *header);
1999-10-05 16:24:54 +00:00
#ifdef BB_FEATURE_TAR_CREATE
1999-10-05 16:24:54 +00:00
/*
* 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);
2000-03-23 01:09:18 +00:00
static void writeTarFile(int argc, char **argv);
static void writeTarBlock(const char *buf, int len);
static int putOctal(char *cp, int len, long value);
1999-10-05 16:24:54 +00:00
#endif
1999-10-05 16:24:54 +00:00
extern int tar_main(int argc, char **argv)
1999-10-05 16:24:54 +00:00
{
2000-03-23 01:09:18 +00:00
const char *tarName=NULL;
const char *options;
2000-03-23 01:09:18 +00:00
int listFlag = FALSE;
int extractFlag = FALSE;
int createFlag = FALSE;
int verboseFlag = FALSE;
int tostdoutFlag = FALSE;
argc--;
argv++;
if (argc < 1)
usage(tar_usage);
2000-03-23 01:09:18 +00:00
/* Parse options */
if (**argv == '-')
options = (*argv++) + 1;
else
options = (*argv++);
argc--;
for (; *options; options++) {
switch (*options) {
case 'f':
2000-03-23 01:09:18 +00:00
if (tarName != NULL)
fatalError( "Only one 'f' option allowed\n");
tarName = *argv++;
2000-03-23 01:09:18 +00:00
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:
2000-03-23 01:09:18 +00:00
fatalError( "Unknown tar flag '%c'\n"
"Try `tar --help' for more information\n", *options);
}
}
1999-10-12 22:26:06 +00:00
/*
* 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
2000-03-23 01:09:18 +00:00
fatalError( "This version of tar was not compiled with tar creation support.\n");
#else
2000-03-23 01:09:18 +00:00
exit(writeTarFile(argc, argv));
#endif
} else {
2000-03-23 01:09:18 +00:00
exit(readTarFile(tarName, extractFlag, listFlag, tostdoutFlag, verboseFlag));
}
flagError:
2000-03-23 01:09:18 +00:00
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;
}
2000-03-23 01:09:18 +00:00
static void
tarExtractSpecial(TarInfo *header, int extractFlag, int listFlag, int tostdoutFlag, int verboseFlag)
{
return;
1999-10-05 16:24:54 +00:00
}
1999-10-05 16:24:54 +00:00
/*
* 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.
*/
2000-03-23 01:09:18 +00:00
static int readTarFile(const char* tarName, int extractFlag, int listFlag,
int tostdoutFlag, int verboseFlag)
1999-10-05 16:24:54 +00:00
{
2000-03-23 01:09:18 +00:00
int status, tarFd=0;
int errorFlag=FALSE;
TarHeader rawHeader;
TarInfo header;
2000-03-23 01:09:18 +00:00
/* Open the tar file for reading. */
if (!strcmp(tarName, "-"))
tarFd = fileno(stdin);
2000-03-23 01:09:18 +00:00
else
tarFd = open(tarName, O_RDONLY);
if (tarFd < 0) {
2000-03-23 01:09:18 +00:00
errorMsg( "Error opening '%s': %s", tarName, strerror(errno));
return ( FALSE);
1999-10-05 16:24:54 +00:00
}
2000-03-23 01:09:18 +00:00
/* 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;
}
}
2000-03-23 01:09:18 +00:00
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;
}
2000-03-23 01:09:18 +00:00
/* 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:
2000-03-23 01:09:18 +00:00
tarExtractDirectory( &header, extractFlag, listFlag, tostdoutFlag, verboseFlag);
break;
case LNKTYPE:
2000-03-23 01:09:18 +00:00
tarExtractHardLink( &header, extractFlag, listFlag, tostdoutFlag, verboseFlag);
break;
case SYMTYPE:
2000-03-23 01:09:18 +00:00
tarExtractSymLink( &header, extractFlag, listFlag, tostdoutFlag, verboseFlag);
break;
case CHRTYPE:
case BLKTYPE:
case FIFOTYPE:
2000-03-23 01:09:18 +00:00
tarExtractSpecial( &header, extractFlag, listFlag, tostdoutFlag, verboseFlag);
break;
default:
close( tarFd);
return( FALSE);
}
1999-10-05 16:24:54 +00:00
}
2000-03-23 01:09:18 +00:00
close(tarFd);
if (status > 0) {
/* Bummer - we read a partial header */
errorMsg( "Error reading '%s': %s", tarName, strerror(errno));
return ( FALSE);
}
2000-03-23 01:09:18 +00:00
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);
1999-10-05 16:24:54 +00:00
}
/*
2000-03-23 01:09:18 +00:00
* 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.
1999-10-05 16:24:54 +00:00
*/
2000-03-23 01:09:18 +00:00
static long getOctal(const char *cp, int size)
1999-10-05 16:24:54 +00:00
{
2000-03-23 01:09:18 +00:00
long val = 0;
2000-03-23 01:09:18 +00:00
for(;(size > 0) && (*cp == ' '); cp++, size--);
if ((size == 0) || !isOctal(*cp))
return -1;
for(; (size > 0) && isOctal(*cp); size--) {
val = val * 8 + *cp++ - '0';
1999-10-05 16:24:54 +00:00
}
2000-03-23 01:09:18 +00:00
for (;(size > 0) && (*cp == ' '); cp++, size--);
if ((size > 0) && *cp)
return -1;
return val;
}
1999-10-05 16:24:54 +00:00
2000-03-23 01:09:18 +00:00
/* 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;
2000-03-23 01:09:18 +00:00
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-- )
2000-03-23 01:09:18 +00:00
sum += *s++;
if (sum == chksum )
2000-03-23 01:09:18 +00:00
return ( TRUE);
return( FALSE);
}
2000-03-23 01:09:18 +00:00
#if 0
if ((header->mode < 0) || (header->uid < 0) ||
(header->gid < 0) || (header->size < 0)) {
errorMsg(stderr, "Bad tar header, skipping\n");
return( FALSE);
1999-10-05 16:24:54 +00:00
}
badHeader = FALSE;
skipFileFlag = FALSE;
devFileFlag = FALSE;
1999-10-05 16:24:54 +00:00
/*
* 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++;
1999-10-05 16:24:54 +00:00
if (warnedRoot == FALSE) {
fprintf(stderr,
"Absolute path detected, removing leading slashes\n");
}
1999-10-05 16:24:54 +00:00
warnedRoot = TRUE;
}
/*
* See if we want this file to be restored.
* If not, then set up to skip it.
*/
2000-03-23 01:09:18 +00:00
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. */
1999-10-12 22:26:06 +00:00
#else
fprintf(stderr, "Cannot create symbolic links\n");
1999-10-12 22:26:06 +00:00
#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) {
2000-02-18 21:34:17 +00:00
/* 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;
1999-11-12 01:30:18 +00:00
}
/*
* 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);
1999-11-12 01:30:18 +00:00
}
/*
* If the file is empty, then that's all we need to do.
*/
if (size == 0 && (tostdoutFlag == FALSE) && (devFileFlag == FALSE)) {
close(outFd);
outFd = -1;
}
1999-10-05 16:24:54 +00:00
}
/*
* Handle a data block of some specified size that was read.
*/
static void readData(const char *cp, int count)
1999-10-05 16:24:54 +00:00
{
/*
* 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;
1999-10-05 16:24:54 +00:00
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;
1999-10-05 16:24:54 +00:00
}
/*
* 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;
}
1999-10-05 16:24:54 +00:00
}
/*
* 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
2000-03-23 01:09:18 +00:00
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.
*/
2000-03-23 01:09:18 +00:00
if (argc == 0)
return TRUE;
fileLength = strlen(fileName);
/*
* Check each of the test paths.
*/
2000-03-23 01:09:18 +00:00
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
1999-10-05 16:24:54 +00:00
/*
* Write a tar file containing the specified files.
*/
2000-03-23 01:09:18 +00:00
static void writeTarFile(int argc, char **argv)
1999-10-05 16:24:54 +00:00
{
struct stat statbuf;
/*
* Make sure there is at least one file specified.
*/
2000-03-23 01:09:18 +00:00
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.
*/
2000-03-23 01:09:18 +00:00
while (errorFlag == FALSE && (argc-- > 0)) {
saveFile(*argv++, FALSE);
}
/*
* Now write an empty block of zeroes to end the archive.
*/
writeTarBlock("", 1);
1999-10-12 22:26:06 +00:00
done:
/*
* Close the tar file and check for errors if it was opened.
*/
if ((tostdoutFlag == FALSE) && (tarFd >= 0) && (close(tarFd) < 0))
perror(tarName);
1999-10-05 16:24:54 +00:00
}
/*
* 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)
1999-10-05 16:24:54 +00:00
{
int status;
struct stat statbuf;
1999-10-05 16:24:54 +00:00
if (verboseFlag == TRUE)
printf("a %s\n", fileName);
1999-10-05 16:24:54 +00:00
/*
* 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);
1999-10-05 16:24:54 +00:00
return;
}
1999-10-05 16:24:54 +00:00
/*
* Find out about the file.
*/
1999-10-05 16:24:54 +00:00
#ifdef S_ISLNK
if (seeLinks == TRUE)
status = lstat(fileName, &statbuf);
else
1999-10-05 16:24:54 +00:00
#endif
status = stat(fileName, &statbuf);
1999-10-05 16:24:54 +00:00
if (status < 0) {
perror(fileName);
return;
}
1999-10-05 16:24:54 +00:00
/*
* 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");
1999-10-05 16:24:54 +00:00
return;
}
1999-10-05 16:24:54 +00:00
/*
* Check the type of file.
*/
mode = statbuf.st_mode;
1999-10-05 16:24:54 +00:00
if (S_ISDIR(mode)) {
saveDirectory(fileName, &statbuf);
1999-10-05 16:24:54 +00:00
return;
}
if (S_ISREG(mode)) {
saveRegularFile(fileName, &statbuf);
1999-10-05 16:24:54 +00:00
return;
}
1999-10-05 16:24:54 +00:00
/* Some day add support for tarring these up... but not today. :) */
1999-11-12 01:30:18 +00:00
// 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);
1999-11-12 01:30:18 +00:00
// }
1999-10-05 16:24:54 +00:00
/*
* The file is a strange type of file, ignore it.
*/
fprintf(stderr, "%s: not a directory or regular file\n", fileName);
1999-10-05 16:24:54 +00:00
}
/*
* Save a regular file to the tar file.
*/
static void
saveRegularFile(const char *fileName, const struct stat *statbuf)
1999-10-05 16:24:54 +00:00
{
int sawEof;
int fileFd;
int cc;
int dataCount;
long fullDataCount;
char data[TAR_BLOCK_SIZE * 16];
1999-10-05 16:24:54 +00:00
1999-10-12 22:26:06 +00:00
/*
* Open the file for reading.
1999-10-05 16:24:54 +00:00
*/
fileFd = open(fileName, O_RDONLY);
1999-10-05 16:24:54 +00:00
if (fileFd < 0) {
perror(fileName);
1999-10-05 16:24:54 +00:00
1999-10-12 22:26:06 +00:00
return;
1999-10-05 16:24:54 +00:00
}
1999-10-12 22:26:06 +00:00
/*
* Write out the header for the file.
1999-10-12 22:26:06 +00:00
*/
writeHeader(fileName, statbuf);
1999-10-12 22:26:06 +00:00
/*
* 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.
1999-10-05 16:24:54 +00:00
*/
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;
}
1999-10-12 22:26:06 +00:00
/*
* Close the file.
*/
if ((tostdoutFlag == FALSE) && close(fileFd) < 0)
fprintf(stderr, "%s: close: %s\n", fileName, strerror(errno));
1999-10-05 16:24:54 +00:00
}
/*
* Save a directory and all of its files to the tar file.
*/
static void saveDirectory(const char *dirName, const struct stat *statbuf)
1999-10-05 16:24:54 +00:00
{
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);
1999-10-05 16:24:54 +00:00
1999-10-12 22:26:06 +00:00
/*
* Open the directory.
1999-10-05 16:24:54 +00:00
*/
dir = opendir(dirName);
if (dir == NULL) {
fprintf(stderr, "Cannot read directory \"%s\": %s\n",
dirName, strerror(errno));
1999-10-05 16:24:54 +00:00
return;
}
1999-10-05 16:24:54 +00:00
/*
* See if a slash is needed.
*/
needSlash = (*dirName && (dirName[strlen(dirName) - 1] != '/'));
1999-10-05 16:24:54 +00:00
1999-10-12 22:26:06 +00:00
/*
* Read all of the directory entries and check them,
* except for the current and parent directory entries.
1999-10-05 16:24:54 +00:00
*/
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);
}
1999-10-12 22:26:06 +00:00
/*
* All done, close the directory.
*/
closedir(dir);
1999-10-05 16:24:54 +00:00
}
/*
* 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)
1999-10-05 16:24:54 +00:00
{
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));
1999-10-05 16:24:54 +00:00
}
/*
* 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)
1999-10-05 16:24:54 +00:00
{
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;
}
1999-10-05 16:24:54 +00:00
}
/*
* 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)
1999-10-05 16:24:54 +00:00
{
int tempLength;
char *tempString;
char tempBuffer[32];
1999-10-05 16:24:54 +00:00
/*
* Create a string of the specified length with an initial space,
* leading zeroes and the octal number, and a trailing null.
*/
tempString = tempBuffer;
1999-10-05 16:24:54 +00:00
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;
1999-10-05 16:24:54 +00:00
}
#endif
1999-10-05 16:24:54 +00:00
/* END CODE */
2000-03-23 01:09:18 +00:00
#endif