458 lines
9.3 KiB
C
458 lines
9.3 KiB
C
|
//
|
||
|
// Stuff to deal with tar-format files
|
||
|
//
|
||
|
|
||
|
#include <sys/stat.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <tar.h>
|
||
|
#include <stdio.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <dirent.h>
|
||
|
#include <string.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#include "buf.h"
|
||
|
#include "psxarc.h"
|
||
|
#include "tarhead.h"
|
||
|
#include "links.h"
|
||
|
|
||
|
static unsigned long round_up(int, int);
|
||
|
static int tarAtoi(char *);
|
||
|
static void tarItoa(long, char *, size_t);
|
||
|
static void tar_dodir(PBUF pb, char *pchfile, struct stat *psb);
|
||
|
static char *modestring(PTAR_HEAD);
|
||
|
extern int fVerbose;
|
||
|
|
||
|
void
|
||
|
TarRead(PBUF pb)
|
||
|
{
|
||
|
char name[155 + 1 + 100 + 1]; // (prefix '/' name '\0')
|
||
|
char linkname[TNAMSIZ + 1];
|
||
|
unsigned long size, i;
|
||
|
int fdout;
|
||
|
PTAR_HEAD buf = (PTAR_HEAD)pb->data;
|
||
|
|
||
|
|
||
|
if (0 != strcmp(buf->s.magic, TMAGIC)) {
|
||
|
fprintf(stderr, "%s: bad magic number\n", progname);
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
name[sizeof(name) - 1] = '\0';
|
||
|
|
||
|
do {
|
||
|
(void)strncpy(name, buf->s.prefix, 155);
|
||
|
// make sure 'name' is null-terminated.
|
||
|
name[155] = '\0';
|
||
|
if ('\0' != name[0]) {
|
||
|
(void)strcat(name, "/");
|
||
|
}
|
||
|
(void)strncat(name, buf->s.name, 100);
|
||
|
|
||
|
if (fVerbose) {
|
||
|
printf("%s\n", name);
|
||
|
}
|
||
|
|
||
|
if (DIRTYPE == buf->s.typeflag) {
|
||
|
if (-1 == mkdir(name, 0777)) {
|
||
|
fprintf(stderr, "%s: mkdir: ", progname);
|
||
|
perror(name);
|
||
|
}
|
||
|
bfill(pb);
|
||
|
continue;
|
||
|
}
|
||
|
if (FIFOTYPE == buf->s.typeflag) {
|
||
|
if (-1 == mkfifo(name, 0666)) {
|
||
|
fprintf(stderr, "%s: mkfifo: ", progname);
|
||
|
perror(name);
|
||
|
}
|
||
|
bfill(pb);
|
||
|
continue;
|
||
|
}
|
||
|
if (LNKTYPE == buf->s.typeflag) {
|
||
|
strncpy(linkname, buf->s.linkname, sizeof(linkname));
|
||
|
if (-1 == link(linkname, name)) {
|
||
|
fprintf(stderr, "%s: link %s, %s: ",
|
||
|
progname, linkname, name);
|
||
|
perror("");
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// regular file
|
||
|
if (-1 == (fdout = open(name, O_WRONLY | O_CREAT, 0666))) {
|
||
|
fprintf(stderr, "%s: open: ", progname);
|
||
|
perror(name);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
size = tarAtoi(buf->s.size);
|
||
|
|
||
|
if (size > 0) {
|
||
|
bfill(pb);
|
||
|
|
||
|
for (i = 0; i < size; ++i) {
|
||
|
int c;
|
||
|
|
||
|
c = bgetc(pb);
|
||
|
write(fdout, &c, 1);
|
||
|
}
|
||
|
}
|
||
|
(void)close(fdout);
|
||
|
|
||
|
bfill(pb);
|
||
|
} while (0 != buf->s.magic[0]);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// TarWrite -- calls tar_dodir for directories, which calls back here.
|
||
|
//
|
||
|
|
||
|
void
|
||
|
TarWrite(PBUF pb, char **ppchFiles, int count)
|
||
|
{
|
||
|
PTAR_HEAD pt;
|
||
|
auto struct stat statbuf;
|
||
|
int fdin;
|
||
|
int i;
|
||
|
unsigned chksum;
|
||
|
|
||
|
// We reach into the buffer routines until it's time to write.
|
||
|
// Brutal but effective.
|
||
|
|
||
|
pt = (PTAR_HEAD)pb->data;
|
||
|
|
||
|
while (count > 0) {
|
||
|
int len;
|
||
|
|
||
|
memset(pt, 0, sizeof(*pt));
|
||
|
strcpy(pt->s.magic, TMAGIC);
|
||
|
strncpy(pt->s.version, TVERSION, TVERSLEN);
|
||
|
|
||
|
if (fVerbose) {
|
||
|
fprintf(stderr, "%s\n", *ppchFiles);
|
||
|
}
|
||
|
if (-1 == stat(*ppchFiles, &statbuf)) {
|
||
|
fprintf(stderr, "%s: stat: ");
|
||
|
perror(*ppchFiles);
|
||
|
continue;
|
||
|
}
|
||
|
len = strlen(*ppchFiles);
|
||
|
if (len > TNAMSIZ + 155 + 1) {
|
||
|
// the filename just won't fit. Do something
|
||
|
// reasonable.
|
||
|
} else if (len <= TNAMSIZ) {
|
||
|
strncpy(pt->s.name, *ppchFiles, TNAMSIZ);
|
||
|
} else {
|
||
|
char *pch;
|
||
|
|
||
|
// We try to put as much of the filename as will fit
|
||
|
// into the 'name' portion, and the rest goes in
|
||
|
// the prefix. To do this, we start 101 characters
|
||
|
// from the end; if that character is a slash, we
|
||
|
// split the string there. If it's not, we split the
|
||
|
// string at the next slash to the right.
|
||
|
|
||
|
pch = *ppchFiles + (len - TNAMSIZ - 1);
|
||
|
if ('/' != *pch) {
|
||
|
pch = strchr(pch, '/');
|
||
|
if (NULL == pch) {
|
||
|
// XXX.mjb: This filename has a trailing
|
||
|
// component more than 100 chars
|
||
|
// long. Do something reasonable.
|
||
|
--count;
|
||
|
++ppchFiles;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
*pch = '\0';
|
||
|
strncpy(pt->s.name, pch + 1, TNAMSIZ);
|
||
|
strncpy(pt->s.prefix, *ppchFiles, 155);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// XXX.mjb: this assumes tar mode bits are the same as
|
||
|
// the POSIX implementation's mode bits. Should really
|
||
|
// call a function to convert between.
|
||
|
//
|
||
|
tarItoa(statbuf.st_mode & 0777, pt->s.mode,
|
||
|
sizeof(pt->s.mode));
|
||
|
|
||
|
#if 0
|
||
|
tarItoa(statbuf.st_uid, pt->s.uid, sizeof(pt->s.uid));
|
||
|
tarItoa(statbuf.st_gid, pt->s.gid, sizeof(pt->s.gid));
|
||
|
tarItoa(statbuf.st_mtime, pt->s.mtime, sizeof(pt->s.mtime));
|
||
|
#endif
|
||
|
|
||
|
if (S_ISDIR(statbuf.st_mode)) {
|
||
|
pt->s.typeflag = DIRTYPE;
|
||
|
memset(pt->s.size, '0', sizeof(pt->s.size));
|
||
|
// put the directory entry on the tape
|
||
|
bflush(pb);
|
||
|
|
||
|
// put the directory contents on tape
|
||
|
tar_dodir(pb, *ppchFiles, &statbuf);
|
||
|
|
||
|
++ppchFiles;
|
||
|
--count;
|
||
|
continue;
|
||
|
}
|
||
|
if (S_ISFIFO(statbuf.st_mode)) {
|
||
|
pt->s.typeflag = FIFOTYPE;
|
||
|
memset(pt->s.size, '0', sizeof(pt->s.size));
|
||
|
bflush(pb);
|
||
|
++ppchFiles;
|
||
|
--count;
|
||
|
continue;
|
||
|
}
|
||
|
if (statbuf.st_nlink > 1) {
|
||
|
PLINKFILE p;
|
||
|
|
||
|
if (NULL != (p = GetLinkByIno(statbuf.st_ino))) {
|
||
|
pt->s.typeflag = LNKTYPE;
|
||
|
memset(pt->s.size, '0', sizeof(pt->s.size));
|
||
|
strncpy(pt->s.linkname, p->name, TNAMSIZ);
|
||
|
bflush(pb);
|
||
|
|
||
|
++ppchFiles;
|
||
|
--count;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
AddLinkList(&statbuf, *ppchFiles);
|
||
|
}
|
||
|
|
||
|
pt->s.typeflag = REGTYPE;
|
||
|
tarItoa((int)statbuf.st_size, pt->s.size, sizeof(pt->s.size));
|
||
|
|
||
|
//
|
||
|
// compute the checksum for the header
|
||
|
//
|
||
|
|
||
|
memset(pt->s.chksum, ' ', sizeof(pt->s.chksum));
|
||
|
for (i = 0, chksum = 0; i < sizeof(pt->buf); ++i) {
|
||
|
chksum += pt->buf[i];
|
||
|
}
|
||
|
tarItoa(chksum, pt->s.chksum, sizeof(pt->s.chksum));
|
||
|
|
||
|
fdin = open(*ppchFiles, O_RDONLY, 0);
|
||
|
if (-1 == fdin) {
|
||
|
fprintf(stderr, "%s: open: ");
|
||
|
perror(*ppchFiles);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// write the header
|
||
|
bflush(pb);
|
||
|
|
||
|
if (0 == statbuf.st_size) {
|
||
|
//
|
||
|
// special case: don't write any data blocks.
|
||
|
//
|
||
|
close(fdin);
|
||
|
++ppchFiles;
|
||
|
--count;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// copy the file data
|
||
|
// XXX.mjb: what should happen here if we find that we can't
|
||
|
// read the number of bytes we thought we'd be able to
|
||
|
// read? (The file could change size, or some kind
|
||
|
// of error could occur.) We can't leave the data too
|
||
|
// small, or we'll hose the rest of the tar file. So
|
||
|
// we write extra blocks of zeroes. What if the file
|
||
|
// turns out to be longer than expected? Print a
|
||
|
// warning and continue?
|
||
|
|
||
|
memset(pb->data, 0, sizeof(pb->data));
|
||
|
while (statbuf.st_size > 0) {
|
||
|
int nbytes;
|
||
|
char c;
|
||
|
|
||
|
nbytes = read(fdin, &c, 1);
|
||
|
if (-1 == nbytes) {
|
||
|
// error occurs before we have all the data.
|
||
|
}
|
||
|
bputc(pb, c);
|
||
|
--statbuf.st_size;
|
||
|
}
|
||
|
bflush(pb);
|
||
|
close(fdin);
|
||
|
|
||
|
++ppchFiles;
|
||
|
--count;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
TarList(PBUF pb)
|
||
|
{
|
||
|
PTAR_HEAD pt;
|
||
|
char name[155 + 1 + 100 + 1]; // "prefix / name \0"
|
||
|
unsigned long size, i;
|
||
|
char *pch;
|
||
|
|
||
|
pt = (PTAR_HEAD)pb->data;
|
||
|
|
||
|
do {
|
||
|
(void)strncpy(name, pt->s.prefix, 155);
|
||
|
name[155] = '\0';
|
||
|
if ('\0' != name[0]) {
|
||
|
(void)strcat(name, "/");
|
||
|
}
|
||
|
(void)strncat(name, pt->s.name, 100);
|
||
|
|
||
|
size = tarAtoi(pt->s.size);
|
||
|
|
||
|
if (!fVerbose) {
|
||
|
printf("%s\n", name);
|
||
|
} else {
|
||
|
pch = modestring(pt);
|
||
|
printf("%s %6ld %s\n", pch, size, name);
|
||
|
}
|
||
|
|
||
|
size = round_up(size, 512);
|
||
|
for (i = 0; i < size; ++i) {
|
||
|
bfill(pb);
|
||
|
}
|
||
|
bfill(pb);
|
||
|
} while (0 != pt->s.magic[0]);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// tarAtof -- translate tar-style octal strings to decimal
|
||
|
//
|
||
|
static int
|
||
|
tarAtoi(char *pch)
|
||
|
{
|
||
|
int num = 0;
|
||
|
|
||
|
while ('\0' != *pch && ' ' != *pch) {
|
||
|
num = num * 8 + (*pch - '0');
|
||
|
++pch;
|
||
|
}
|
||
|
return num;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// tarItoa -- for writing numeric fields in tar headers.
|
||
|
void
|
||
|
tarItoa(long i, char *pch, size_t len)
|
||
|
{
|
||
|
// XXX.mjb: should check width < len
|
||
|
sprintf(pch, "%-o", i);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// round_up -- round num up to the nearest multiple of m.
|
||
|
//
|
||
|
unsigned long
|
||
|
round_up(int num, int m)
|
||
|
{
|
||
|
return (num + (m - 1))/m;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
tar_dodir(
|
||
|
PBUF pb, // the tar image file, being written
|
||
|
char *pchfile, // the directory file to be added to the archv
|
||
|
struct stat *psb // result of stat on the directory file.
|
||
|
)
|
||
|
{
|
||
|
DIR *dp;
|
||
|
struct dirent *dirent;
|
||
|
char *pch;
|
||
|
|
||
|
dp = opendir(pchfile);
|
||
|
if (NULL == dp) {
|
||
|
fprintf(stderr, "%s: opendir: ", progname);
|
||
|
perror(pchfile);
|
||
|
return;
|
||
|
}
|
||
|
while (NULL != (dirent = readdir(dp))) {
|
||
|
if ('.' == dirent->d_name[0] &&
|
||
|
('\0' == dirent->d_name[1] ||
|
||
|
0 == strcmp(dirent->d_name, ".."))) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Recurse. We append the file name read from the directory
|
||
|
// to the directory name we were given and call TarWrite to
|
||
|
// put it on the tape. It could be a directory, so we could
|
||
|
// end up back here. This means that we must allocate the
|
||
|
// space for the pathname dynamically. This seems like it
|
||
|
// will be a big time-waster.
|
||
|
//
|
||
|
|
||
|
// strlen + 2: one extra for '/', one extra for null.
|
||
|
pch = malloc(strlen(pchfile) + strlen(dirent->d_name) + 2);
|
||
|
if (NULL == pch) {
|
||
|
fprintf(stderr, "%s: virtual memory exhausted\n",
|
||
|
progname);
|
||
|
exit(4);
|
||
|
}
|
||
|
strcpy(pch, pchfile);
|
||
|
strcat(pch, "/");
|
||
|
strcat(pch, dirent->d_name);
|
||
|
|
||
|
TarWrite(pb, &pch, 1);
|
||
|
free(pch);
|
||
|
}
|
||
|
(void)closedir(dp);
|
||
|
}
|
||
|
|
||
|
static char *
|
||
|
modestring(PTAR_HEAD pt)
|
||
|
{
|
||
|
static char sb[11];
|
||
|
unsigned long mask, mode;
|
||
|
char *rwx = "rwxrwxrwx";
|
||
|
char *string;
|
||
|
|
||
|
sb[11] = '\0';
|
||
|
string = sb;
|
||
|
|
||
|
switch (pt->s.typeflag) {
|
||
|
case LNKTYPE:
|
||
|
case REGTYPE:
|
||
|
string[0] = '-';
|
||
|
break;
|
||
|
case DIRTYPE:
|
||
|
string[0] = 'd';
|
||
|
break;
|
||
|
case FIFOTYPE:
|
||
|
string[0] = 'f';
|
||
|
break;
|
||
|
case SYMTYPE:
|
||
|
string[0] = 'l';
|
||
|
break;
|
||
|
case BLKTYPE:
|
||
|
string[0] = 'b';
|
||
|
break;
|
||
|
case CHRTYPE:
|
||
|
string[0] = 'c';
|
||
|
break;
|
||
|
case CONTTYPE:
|
||
|
string[0] = '=';
|
||
|
break;
|
||
|
default:
|
||
|
fprintf(stderr, "modestring shouldn't get here\n");
|
||
|
}
|
||
|
string++;
|
||
|
|
||
|
mode = tarAtoi(pt->s.mode);
|
||
|
|
||
|
for (mask = 0400; mask != 0; mask >>= 1) {
|
||
|
if (mode & mask) {
|
||
|
*string++ = *rwx++;
|
||
|
} else {
|
||
|
*string++ = '-';
|
||
|
rwx++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return sb;
|
||
|
}
|