#include <sys/types.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include <grp.h>
#include <libgen.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "pax.h"
#include "extern.h"
#include "tar.h"
SLIST_HEAD(xheader, xheader_record);
struct xheader_record {
SLIST_ENTRY(xheader_record) entry;
size_t reclen;
char *record;
};
#define MINXHDRSZ 5
static size_t expandname(char *, size_t, char **, const char *, size_t);
static u_long tar_chksm(char *, int);
static char *name_split(char *, int);
static int ul_oct(u_long, char *, int, int);
static int ull_oct(unsigned long long, char *, int, int);
static int rd_xheader(ARCHD *, int, off_t);
#ifndef SMALL
static int wr_xheader(char *, HD_USTAR *, struct xheader *);
#endif
static uid_t uid_nobody;
static uid_t uid_warn;
static gid_t gid_nobody;
static gid_t gid_warn;
int tar_nodir;
char *gnu_name_string;
char *gnu_link_string;
int
tar_endwr(void)
{
return wr_skip(NULLCNT * BLKMULT);
}
off_t
tar_endrd(void)
{
return NULLCNT * BLKMULT;
}
int
tar_trail(ARCHD *ignore, char *buf, int in_resync, int *cnt)
{
int i;
for (i = 0; i < BLKMULT; ++i) {
if (buf[i] != '\0')
break;
}
if (i != BLKMULT)
return(-1);
if (!in_resync && (++*cnt >= NULLCNT))
return(0);
return(1);
}
static int
ul_oct(u_long val, char *str, int len, int term)
{
char *pt;
pt = str + len - 1;
switch (term) {
case 3:
*pt-- = '\0';
break;
case 2:
*pt-- = ' ';
*pt-- = '\0';
break;
case 1:
*pt-- = ' ';
break;
case 0:
default:
*pt-- = '\0';
*pt-- = ' ';
break;
}
while (pt >= str) {
*pt-- = '0' + (char)(val & 0x7);
val >>= 3;
if (val == 0)
break;
}
while (pt >= str)
*pt-- = '0';
if (val != 0)
return(-1);
return(0);
}
static int
ull_oct(unsigned long long val, char *str, int len, int term)
{
char *pt;
pt = str + len - 1;
switch (term) {
case 3:
*pt-- = '\0';
break;
case 2:
*pt-- = ' ';
*pt-- = '\0';
break;
case 1:
*pt-- = ' ';
break;
case 0:
default:
*pt-- = '\0';
*pt-- = ' ';
break;
}
while (pt >= str) {
*pt-- = '0' + (char)(val & 0x7);
val >>= 3;
if (val == 0)
break;
}
while (pt >= str)
*pt-- = '0';
if (val != 0)
return(-1);
return(0);
}
static u_long
tar_chksm(char *blk, int len)
{
char *stop;
char *pt;
u_long chksm = BLNKSUM;
pt = blk;
stop = blk + CHK_OFFSET;
while (pt < stop)
chksm += (u_long)(*pt++ & 0xff);
pt += CHK_LEN;
stop = blk + len;
while (pt < stop)
chksm += (u_long)(*pt++ & 0xff);
return(chksm);
}
int
tar_id(char *blk, int size)
{
HD_TAR *hd;
HD_USTAR *uhd;
if (size < BLKMULT)
return(-1);
hd = (HD_TAR *)blk;
uhd = (HD_USTAR *)blk;
if (hd->name[0] == '\0')
return(-1);
if (strncmp(uhd->magic, TMAGIC, TMAGLEN - 1) == 0)
return(-1);
if (asc_ul(hd->chksum,sizeof(hd->chksum),OCT) != tar_chksm(blk,BLKMULT))
return(-1);
force_one_volume = 1;
return(0);
}
int
tar_opt(void)
{
OPLIST *opt;
while ((opt = opt_next()) != NULL) {
if (strcmp(opt->name, TAR_OPTION) ||
strcmp(opt->value, TAR_NODIR)) {
paxwarn(1, "Unknown tar format -o option/value pair %s=%s",
opt->name, opt->value);
paxwarn(1,"%s=%s is the only supported tar format option",
TAR_OPTION, TAR_NODIR);
return(-1);
}
if ((act != APPND) && (act != ARCHIVE)) {
paxwarn(1, "%s=%s is only supported when writing.",
opt->name, opt->value);
return(-1);
}
tar_nodir = 1;
}
return(0);
}
int
tar_rd(ARCHD *arcn, char *buf)
{
HD_TAR *hd;
unsigned long long val;
char *pt;
if (tar_id(buf, BLKMULT) < 0)
return(-1);
memset(arcn, 0, sizeof(*arcn));
arcn->org_name = arcn->name;
arcn->sb.st_nlink = 1;
hd = (HD_TAR *)buf;
if (hd->linkflag != LONGLINKTYPE && hd->linkflag != LONGNAMETYPE) {
arcn->nlen = expandname(arcn->name, sizeof(arcn->name),
&gnu_name_string, hd->name, sizeof(hd->name));
arcn->ln_nlen = expandname(arcn->ln_name, sizeof(arcn->ln_name),
&gnu_link_string, hd->linkname, sizeof(hd->linkname));
}
arcn->sb.st_mode = (mode_t)(asc_ul(hd->mode,sizeof(hd->mode),OCT) &
0xfff);
arcn->sb.st_uid = (uid_t)asc_ul(hd->uid, sizeof(hd->uid), OCT);
arcn->sb.st_gid = (gid_t)asc_ul(hd->gid, sizeof(hd->gid), OCT);
arcn->sb.st_size = (off_t)asc_ull(hd->size, sizeof(hd->size), OCT);
val = asc_ull(hd->mtime, sizeof(hd->mtime), OCT);
if (val > MAX_TIME_T)
arcn->sb.st_mtime = MAX_TIME_T;
else
arcn->sb.st_mtime = val;
arcn->sb.st_ctim = arcn->sb.st_atim = arcn->sb.st_mtim;
pt = &(arcn->name[arcn->nlen - 1]);
arcn->pad = 0;
arcn->skip = 0;
switch (hd->linkflag) {
case SYMTYPE:
arcn->type = PAX_SLK;
arcn->sb.st_mode |= S_IFLNK;
break;
case LNKTYPE:
arcn->type = PAX_HLK;
arcn->sb.st_nlink = 2;
arcn->sb.st_mode |= S_IFREG;
break;
case LONGLINKTYPE:
case LONGNAMETYPE:
arcn->type =
hd->linkflag == LONGLINKTYPE ? PAX_GLL : PAX_GLF;
arcn->pad = TAR_PAD(arcn->sb.st_size);
arcn->skip = arcn->sb.st_size;
break;
case DIRTYPE:
arcn->type = PAX_DIR;
arcn->sb.st_mode |= S_IFDIR;
arcn->sb.st_nlink = 2;
break;
case AREGTYPE:
case REGTYPE:
default:
arcn->ln_name[0] = '\0';
arcn->ln_nlen = 0;
if (*pt == '/') {
arcn->type = PAX_DIR;
arcn->sb.st_mode |= S_IFDIR;
arcn->sb.st_nlink = 2;
} else {
arcn->type = PAX_REG;
arcn->sb.st_mode |= S_IFREG;
arcn->pad = TAR_PAD(arcn->sb.st_size);
arcn->skip = arcn->sb.st_size;
}
break;
}
if (*pt == '/') {
*pt = '\0';
--arcn->nlen;
}
return(0);
}
int
tar_wr(ARCHD *arcn)
{
HD_TAR *hd;
int len;
char hdblk[sizeof(HD_TAR)];
switch (arcn->type) {
case PAX_DIR:
if (tar_nodir)
return(1);
break;
case PAX_CHR:
paxwarn(1, "Tar cannot archive a character device %s",
arcn->org_name);
return(1);
case PAX_BLK:
paxwarn(1, "Tar cannot archive a block device %s", arcn->org_name);
return(1);
case PAX_SCK:
paxwarn(1, "Tar cannot archive a socket %s", arcn->org_name);
return(1);
case PAX_FIF:
paxwarn(1, "Tar cannot archive a fifo %s", arcn->org_name);
return(1);
case PAX_SLK:
case PAX_HLK:
case PAX_HRG:
if ((size_t)arcn->ln_nlen > sizeof(hd->linkname)) {
paxwarn(1, "Link name too long for tar %s",
arcn->ln_name);
return(1);
}
break;
case PAX_REG:
case PAX_CTG:
default:
break;
}
len = arcn->nlen;
if (arcn->type == PAX_DIR)
++len;
if ((size_t)len > sizeof(hd->name)) {
paxwarn(1, "File name too long for tar %s", arcn->name);
return(1);
}
memset(hdblk, 0, sizeof(hdblk));
hd = (HD_TAR *)hdblk;
fieldcpy(hd->name, sizeof(hd->name), arcn->name, sizeof(arcn->name));
arcn->pad = 0;
if (arcn->type == PAX_DIR) {
hd->linkflag = AREGTYPE;
hd->name[len-1] = '/';
if (ul_oct(0, hd->size, sizeof(hd->size), 1))
goto out;
} else if (arcn->type == PAX_SLK) {
hd->linkflag = SYMTYPE;
fieldcpy(hd->linkname, sizeof(hd->linkname), arcn->ln_name,
sizeof(arcn->ln_name));
if (ul_oct(0, hd->size, sizeof(hd->size), 1))
goto out;
} else if (PAX_IS_HARDLINK(arcn->type)) {
hd->linkflag = LNKTYPE;
fieldcpy(hd->linkname, sizeof(hd->linkname), arcn->ln_name,
sizeof(arcn->ln_name));
if (ul_oct(0, hd->size, sizeof(hd->size), 1))
goto out;
} else {
hd->linkflag = AREGTYPE;
if (ull_oct(arcn->sb.st_size, hd->size, sizeof(hd->size), 1)) {
paxwarn(1, "File is too large for tar %s",
arcn->org_name);
return(1);
}
arcn->pad = TAR_PAD(arcn->sb.st_size);
}
if (ul_oct(arcn->sb.st_mode, hd->mode, sizeof(hd->mode), 0) ||
ull_oct(arcn->sb.st_mtime < 0 ? 0 : arcn->sb.st_mtime, hd->mtime,
sizeof(hd->mtime), 1) ||
ul_oct(arcn->sb.st_uid, hd->uid, sizeof(hd->uid), 0) ||
ul_oct(arcn->sb.st_gid, hd->gid, sizeof(hd->gid), 0))
goto out;
if (ul_oct(tar_chksm(hdblk, sizeof(HD_TAR)), hd->chksum,
sizeof(hd->chksum), 3))
goto out;
if (wr_rdbuf(hdblk, sizeof(HD_TAR)) < 0 ||
wr_skip(BLKMULT - sizeof(HD_TAR)) < 0) {
paxwarn(1,"Could not write tar header for %s", arcn->org_name);
return(-1);
}
if (PAX_IS_REG(arcn->type))
return(0);
return(1);
out:
paxwarn(1, "Tar header field is too small for %s", arcn->org_name);
return(1);
}
int
ustar_id(char *blk, int size)
{
HD_USTAR *hd;
if (size < BLKMULT)
return(-1);
hd = (HD_USTAR *)blk;
if (hd->prefix[0] == '\0' && hd->name[0] == '\0')
return(-1);
if (strncmp(hd->magic, TMAGIC, TMAGLEN - 1) != 0)
return(-1);
if (asc_ul(hd->chksum,sizeof(hd->chksum),OCT) != tar_chksm(blk,BLKMULT))
return(-1);
return(0);
}
int
ustar_rd(ARCHD *arcn, char *buf)
{
HD_USTAR *hd = (HD_USTAR *)buf;
char *dest;
int cnt = 0;
dev_t devmajor;
dev_t devminor;
unsigned long long val;
if (ustar_id(buf, BLKMULT) < 0)
return(-1);
reset:
memset(arcn, 0, sizeof(*arcn));
arcn->org_name = arcn->name;
arcn->sb.st_nlink = 1;
arcn->sb.st_size = (off_t)-1;
if (hd->typeflag == XHDRTYPE || hd->typeflag == GHDRTYPE) {
if (rd_xheader(arcn, hd->typeflag == GHDRTYPE,
(off_t)asc_ull(hd->size, sizeof(hd->size), OCT)) < 0)
return (-1);
if (rd_wrbuf(buf, BLKMULT) != BLKMULT)
return (-1);
if (ustar_id(buf, BLKMULT) < 0)
return(-1);
if (hd->typeflag == XHDRTYPE || hd->typeflag == GHDRTYPE)
goto reset;
}
if (!arcn->nlen) {
dest = arcn->name;
if (*(hd->prefix) != '\0') {
cnt = fieldcpy(dest, sizeof(arcn->name) - 1,
hd->prefix, sizeof(hd->prefix));
dest += cnt;
*dest++ = '/';
cnt++;
} else
cnt = 0;
if (hd->typeflag != LONGLINKTYPE &&
hd->typeflag != LONGNAMETYPE) {
arcn->nlen = cnt + expandname(dest,
sizeof(arcn->name) - cnt, &gnu_name_string,
hd->name, sizeof(hd->name));
}
}
if (!arcn->ln_nlen &&
hd->typeflag != LONGLINKTYPE && hd->typeflag != LONGNAMETYPE) {
arcn->ln_nlen = expandname(arcn->ln_name, sizeof(arcn->ln_name),
&gnu_link_string, hd->linkname, sizeof(hd->linkname));
}
arcn->sb.st_mode = (mode_t)(asc_ul(hd->mode, sizeof(hd->mode), OCT) &
0xfff);
if (arcn->sb.st_size == (off_t)-1) {
arcn->sb.st_size =
(off_t)asc_ull(hd->size, sizeof(hd->size), OCT);
}
if (arcn->sb.st_mtime == 0) {
val = asc_ull(hd->mtime, sizeof(hd->mtime), OCT);
if (val > MAX_TIME_T)
arcn->sb.st_mtime = MAX_TIME_T;
else
arcn->sb.st_mtime = val;
}
if (arcn->sb.st_ctime == 0) {
arcn->sb.st_ctim = arcn->sb.st_mtim;
}
if (arcn->sb.st_atime == 0) {
arcn->sb.st_atim = arcn->sb.st_mtim;
}
hd->gname[sizeof(hd->gname) - 1] = '\0';
if (Nflag || gid_from_group(hd->gname, &(arcn->sb.st_gid)) == -1)
arcn->sb.st_gid = (gid_t)asc_ul(hd->gid, sizeof(hd->gid), OCT);
hd->uname[sizeof(hd->uname) - 1] = '\0';
if (Nflag || uid_from_user(hd->uname, &(arcn->sb.st_uid)) == -1)
arcn->sb.st_uid = (uid_t)asc_ul(hd->uid, sizeof(hd->uid), OCT);
arcn->pad = 0;
arcn->skip = 0;
arcn->sb.st_rdev = (dev_t)0;
switch (hd->typeflag) {
case FIFOTYPE:
arcn->type = PAX_FIF;
arcn->sb.st_mode |= S_IFIFO;
break;
case DIRTYPE:
arcn->type = PAX_DIR;
arcn->sb.st_mode |= S_IFDIR;
arcn->sb.st_nlink = 2;
if (arcn->name[arcn->nlen - 1] == '/')
arcn->name[--arcn->nlen] = '\0';
break;
case BLKTYPE:
case CHRTYPE:
if (hd->typeflag == BLKTYPE) {
arcn->type = PAX_BLK;
arcn->sb.st_mode |= S_IFBLK;
} else {
arcn->type = PAX_CHR;
arcn->sb.st_mode |= S_IFCHR;
}
devmajor = (dev_t)asc_ul(hd->devmajor,sizeof(hd->devmajor),OCT);
devminor = (dev_t)asc_ul(hd->devminor,sizeof(hd->devminor),OCT);
arcn->sb.st_rdev = TODEV(devmajor, devminor);
break;
case SYMTYPE:
case LNKTYPE:
if (hd->typeflag == SYMTYPE) {
arcn->type = PAX_SLK;
arcn->sb.st_mode |= S_IFLNK;
} else {
arcn->type = PAX_HLK;
arcn->sb.st_mode |= S_IFREG;
arcn->sb.st_nlink = 2;
}
break;
case LONGLINKTYPE:
case LONGNAMETYPE:
arcn->type =
hd->typeflag == LONGLINKTYPE ? PAX_GLL : PAX_GLF;
arcn->pad = TAR_PAD(arcn->sb.st_size);
arcn->skip = arcn->sb.st_size;
break;
case CONTTYPE:
case AREGTYPE:
case REGTYPE:
default:
arcn->type = PAX_REG;
arcn->pad = TAR_PAD(arcn->sb.st_size);
arcn->skip = arcn->sb.st_size;
arcn->sb.st_mode |= S_IFREG;
break;
}
return(0);
}
#ifndef SMALL
static int
xheader_add(struct xheader *xhdr, const char *keyword,
const char *value)
{
struct xheader_record *rec;
int reclen, tmplen;
char *s;
tmplen = MINXHDRSZ;
do {
reclen = tmplen;
tmplen = snprintf(NULL, 0, "%d %s=%s\n", reclen, keyword,
value);
} while (tmplen >= 0 && tmplen != reclen);
if (tmplen < 0)
return -1;
rec = calloc(1, sizeof(*rec));
if (rec == NULL)
return -1;
rec->reclen = reclen;
if (asprintf(&s, "%d %s=%s\n", reclen, keyword, value) < 0) {
free(rec);
return -1;
}
rec->record = s;
SLIST_INSERT_HEAD(xhdr, rec, entry);
return 0;
}
static int
xheader_add_ull(struct xheader *xhdr, const char *keyword,
unsigned long long value)
{
struct xheader_record *rec;
int reclen, tmplen;
char *s;
tmplen = MINXHDRSZ;
do {
reclen = tmplen;
tmplen = snprintf(NULL, 0, "%d %s=%llu\n", reclen, keyword,
value);
} while (tmplen >= 0 && tmplen != reclen);
if (tmplen < 0)
return -1;
rec = calloc(1, sizeof(*rec));
if (rec == NULL)
return -1;
rec->reclen = reclen;
if (asprintf(&s, "%d %s=%llu\n", reclen, keyword, value) < 0) {
free(rec);
return -1;
}
rec->record = s;
SLIST_INSERT_HEAD(xhdr, rec, entry);
return 0;
}
static int
xheader_add_ts(struct xheader *xhdr, const char *keyword,
const struct timespec *value)
{
struct xheader_record *rec;
int reclen, tmplen;
char frac[sizeof(".111222333")] = "";
char *s;
if (value->tv_nsec != 0) {
int n;
n = snprintf(frac, sizeof(frac), ".%09ld",
(long)value->tv_nsec);
if (n <= 0)
return -1;
for (n--; n > 1 && frac[n] == '0'; n--)
frac[n] = '\0';
}
tmplen = MINXHDRSZ;
do {
reclen = tmplen;
tmplen = snprintf(NULL, 0, "%d %s=%lld%s\n", reclen,
keyword, (long long)value->tv_sec, frac);
} while (tmplen >= 0 && tmplen != reclen);
if (tmplen < 0)
return -1;
rec = calloc(1, sizeof(*rec));
if (rec == NULL)
return -1;
rec->reclen = reclen;
if (asprintf(&s, "%d %s=%lld%s\n", reclen, keyword,
(long long)value->tv_sec, frac) < 0) {
free(rec);
return -1;
}
rec->record = s;
SLIST_INSERT_HEAD(xhdr, rec, entry);
return 0;
}
static void
xheader_free(struct xheader *xhdr)
{
struct xheader_record *rec;
while (!SLIST_EMPTY(xhdr)) {
rec = SLIST_FIRST(xhdr);
SLIST_REMOVE_HEAD(xhdr, entry);
free(rec->record);
free(rec);
}
}
static int
wr_xheader(char *fname, HD_USTAR *fhd, struct xheader *xhdr)
{
char hdblk[sizeof(HD_USTAR)];
HD_USTAR *hd;
char buf[sizeof(hd->name) + 1];
struct xheader_record *rec;
size_t size;
size = 0;
SLIST_FOREACH(rec, xhdr, entry)
size += rec->reclen;
memset(hdblk, 0, sizeof(hdblk));
hd = (HD_USTAR *)hdblk;
hd->typeflag = XHDRTYPE;
strncpy(hd->magic, TMAGIC, TMAGLEN);
strncpy(hd->version, TVERSION, TVERSLEN);
if (ul_oct(size, hd->size, sizeof(hd->size), 3))
goto out;
(void)snprintf(buf, sizeof(buf), "%s/PaxHeaders.%ld/%s",
dirname(fname), (long)getpid(), basename(fname));
fieldcpy(hd->name, sizeof(hd->name), buf, sizeof(buf));
memcpy(hd->mode, fhd->mode, sizeof(hd->mode));
memcpy(hd->mtime, fhd->mtime, sizeof(hd->mtime));
memcpy(hd->uid, fhd->uid, sizeof(hd->uid));
memcpy(hd->gid, fhd->gid, sizeof(hd->gid));
if (ul_oct(tar_chksm(hdblk, sizeof(HD_USTAR)), hd->chksum,
sizeof(hd->chksum), 3))
goto out;
if (wr_rdbuf(hdblk, sizeof(HD_USTAR)) < 0 ||
wr_skip(BLKMULT - sizeof(HD_USTAR)) < 0)
goto err;
SLIST_FOREACH(rec, xhdr, entry)
if (wr_rdbuf(rec->record, rec->reclen) < 0)
goto err;
if (wr_skip(TAR_PAD(size)) < 0)
goto err;
return 0;
out:
paxwarn(1, "Pax header field is too small for %s", fname);
return 1;
err:
paxwarn(1,"Could not write pax extended header for %s", fname);
return -1;
}
#endif
static int
wr_ustar_or_pax(ARCHD *arcn, int ustar)
{
HD_USTAR *hd;
const char *name;
char *pt, hdblk[sizeof(HD_USTAR)];
#ifndef SMALL
struct xheader xhdr = SLIST_HEAD_INITIALIZER(xhdr);
#endif
int bad_mtime;
if (arcn->type == PAX_SCK) {
paxwarn(1, "Ustar cannot archive a socket %s", arcn->org_name);
return(1);
}
if (arcn->type == PAX_DIR && tar_nodir)
return (1);
if (PAX_IS_LINK(arcn->type) &&
((size_t)arcn->ln_nlen > sizeof(hd->linkname))) {
if (ustar) {
paxwarn(1, "Link name too long for ustar %s",
arcn->ln_name);
return(1);
}
#ifndef SMALL
else if (xheader_add(&xhdr, "linkpath", arcn->ln_name) == -1) {
paxwarn(1, "Link name too long for pax %s",
arcn->ln_name);
xheader_free(&xhdr);
return(1);
}
#endif
}
if ((pt = name_split(arcn->name, arcn->nlen)) == NULL) {
if (ustar) {
paxwarn(1, "File name too long for ustar %s",
arcn->name);
return(1);
}
#ifndef SMALL
else if (xheader_add(&xhdr, "path", arcn->name) == -1) {
paxwarn(1, "File name too long for pax %s",
arcn->name);
xheader_free(&xhdr);
return(1);
}
pt = arcn->name;
#endif
}
memset(hdblk, 0, sizeof(hdblk));
hd = (HD_USTAR *)hdblk;
arcn->pad = 0;
if (pt != arcn->name) {
*pt = '\0';
fieldcpy(hd->prefix, sizeof(hd->prefix), arcn->name,
sizeof(arcn->name));
*pt++ = '/';
}
fieldcpy(hd->name, sizeof(hd->name), pt,
sizeof(arcn->name) - (pt - arcn->name));
switch (arcn->type) {
case PAX_DIR:
hd->typeflag = DIRTYPE;
if (ul_oct(0, hd->size, sizeof(hd->size), 3))
goto out;
break;
case PAX_CHR:
case PAX_BLK:
if (arcn->type == PAX_CHR)
hd->typeflag = CHRTYPE;
else
hd->typeflag = BLKTYPE;
if (ul_oct(MAJOR(arcn->sb.st_rdev), hd->devmajor,
sizeof(hd->devmajor), 3) ||
ul_oct(MINOR(arcn->sb.st_rdev), hd->devminor,
sizeof(hd->devminor), 3) ||
ul_oct(0, hd->size, sizeof(hd->size), 3))
goto out;
break;
case PAX_FIF:
hd->typeflag = FIFOTYPE;
if (ul_oct(0, hd->size, sizeof(hd->size), 3))
goto out;
break;
case PAX_SLK:
case PAX_HLK:
case PAX_HRG:
if (arcn->type == PAX_SLK)
hd->typeflag = SYMTYPE;
else
hd->typeflag = LNKTYPE;
fieldcpy(hd->linkname, sizeof(hd->linkname), arcn->ln_name,
sizeof(arcn->ln_name));
if (ul_oct(0, hd->size, sizeof(hd->size), 3))
goto out;
break;
case PAX_REG:
case PAX_CTG:
default:
if (arcn->type == PAX_CTG)
hd->typeflag = CONTTYPE;
else
hd->typeflag = REGTYPE;
arcn->pad = TAR_PAD(arcn->sb.st_size);
if (ull_oct(arcn->sb.st_size, hd->size, sizeof(hd->size), 3)) {
if (ustar) {
paxwarn(1, "File is too long for ustar %s",
arcn->org_name);
return(1);
}
#ifndef SMALL
else if (xheader_add_ull(&xhdr, "size",
arcn->sb.st_size) == -1) {
paxwarn(1, "File is too long for pax %s",
arcn->org_name);
xheader_free(&xhdr);
return(1);
}
#endif
}
break;
}
strncpy(hd->magic, TMAGIC, TMAGLEN);
strncpy(hd->version, TVERSION, TVERSLEN);
if (ul_oct(arcn->sb.st_uid, hd->uid, sizeof(hd->uid), 3)) {
if (uid_nobody == 0) {
if (uid_from_user("nobody", &uid_nobody) == -1)
goto out;
}
if (uid_warn != arcn->sb.st_uid) {
uid_warn = arcn->sb.st_uid;
paxwarn(1,
"Ustar header field is too small for uid %lu, "
"using nobody", (u_long)arcn->sb.st_uid);
}
if (ul_oct(uid_nobody, hd->uid, sizeof(hd->uid), 3))
goto out;
}
if (ul_oct(arcn->sb.st_gid, hd->gid, sizeof(hd->gid), 3)) {
if (gid_nobody == 0) {
if (gid_from_group("nobody", &gid_nobody) == -1)
goto out;
}
if (gid_warn != arcn->sb.st_gid) {
gid_warn = arcn->sb.st_gid;
paxwarn(1,
"Ustar header field is too small for gid %lu, "
"using nobody", (u_long)arcn->sb.st_gid);
}
if (ul_oct(gid_nobody, hd->gid, sizeof(hd->gid), 3))
goto out;
}
bad_mtime = ull_oct(arcn->sb.st_mtime < 0 ? 0 : arcn->sb.st_mtime,
hd->mtime, sizeof(hd->mtime), 3);
if (bad_mtime && ustar)
goto out;
#ifndef SMALL
if (!ustar) {
if (xheader_add_ts(&xhdr, "atime", &arcn->sb.st_atim) == -1) {
paxwarn(1, "Couldn't preserve %s in pax format for %s",
"atime", arcn->org_name);
xheader_free(&xhdr);
return (1);
}
if ((bad_mtime || arcn->sb.st_mtime < 0 ||
arcn->sb.st_mtim.tv_nsec != 0) &&
xheader_add_ts(&xhdr, "mtime", &arcn->sb.st_mtim) == -1) {
paxwarn(1, "Couldn't preserve %s in pax format for %s",
"mtime", arcn->org_name);
xheader_free(&xhdr);
return (1);
}
}
#endif
if (ul_oct(arcn->sb.st_mode, hd->mode, sizeof(hd->mode), 3))
goto out;
if (!Nflag) {
if ((name = user_from_uid(arcn->sb.st_uid, 1)) != NULL)
strncpy(hd->uname, name, sizeof(hd->uname));
if ((name = group_from_gid(arcn->sb.st_gid, 1)) != NULL)
strncpy(hd->gname, name, sizeof(hd->gname));
}
#ifndef SMALL
if (!SLIST_EMPTY(&xhdr)) {
int ret;
ret = wr_xheader(arcn->name, hd, &xhdr);
xheader_free(&xhdr);
if (ret)
return(ret);
}
#endif
if (ul_oct(tar_chksm(hdblk, sizeof(HD_USTAR)), hd->chksum,
sizeof(hd->chksum), 3))
goto out;
if (wr_rdbuf(hdblk, sizeof(HD_USTAR)) < 0 ||
wr_skip(BLKMULT - sizeof(HD_USTAR)) < 0) {
paxwarn(1,"Could not write ustar header for %s",
arcn->org_name);
return(-1);
}
if (PAX_IS_REG(arcn->type))
return(0);
return(1);
out:
#ifndef SMALL
xheader_free(&xhdr);
#endif
paxwarn(1, "Ustar header field is too small for %s", arcn->org_name);
return(1);
}
int
ustar_wr(ARCHD *arcn)
{
return wr_ustar_or_pax(arcn, 1);
}
#ifndef SMALL
int
pax_id(char *blk, int size)
{
HD_USTAR *hd;
if (size < BLKMULT)
return(-1);
hd = (HD_USTAR *)blk;
if (hd->prefix[0] == '\0' && hd->name[0] == '\0')
return(-1);
if (strncmp(hd->magic, TMAGIC, TMAGLEN - 1) != 0)
return(-1);
if (asc_ul(hd->chksum,sizeof(hd->chksum),OCT) != tar_chksm(blk,BLKMULT))
return(-1);
if (hd->typeflag == XHDRTYPE || hd->typeflag == GHDRTYPE)
return(0);
return (-1);
}
#endif
#ifndef SMALL
int
pax_wr(ARCHD *arcn)
{
return wr_ustar_or_pax(arcn, 0);
}
#endif
#ifndef SMALL
int
pax_opt(void)
{
OPLIST *opt;
while ((opt = opt_next()) != NULL) {
if (1) {
paxwarn(1, "Unknown pax format -o option/value pair %s=%s",
opt->name, opt->value);
return(-1);
}
}
return 0;
}
#endif
static char *
name_split(char *name, int len)
{
char *start;
if (len <= TNMSZ)
return(name);
if (len > (TPFSZ + TNMSZ + 1))
return(NULL);
start = name + len - TNMSZ - 1;
if (start == name)
++start;
while ((*start != '\0') && (*start != '/'))
++start;
if (*start == '\0')
return(NULL);
if ((start - name) > TPFSZ)
return(NULL);
return(start);
}
static size_t
expandname(char *buf, size_t len, char **gnu_name, const char *name,
size_t limit)
{
size_t nlen;
if (*gnu_name) {
if ((nlen = strlcpy(buf, *gnu_name, len)) >= len)
nlen = len - 1;
free(*gnu_name);
*gnu_name = NULL;
} else
nlen = fieldcpy(buf, len, name, limit);
return(nlen);
}
static int
rd_time(struct timespec *ts, const char *keyword, char *p)
{
const char *errstr;
char *q;
int multiplier;
if ((q = strchr(p, '.')) != NULL)
*q = '\0';
ts->tv_sec = strtonum(p, 0, MAX_TIME_T, &errstr);
if (errstr != NULL) {
paxwarn(1, "%s is %s: %s", keyword, errstr, p);
return -1;
}
ts->tv_nsec = 0;
if (q == NULL)
return 0;
multiplier = 100000000;
for (q++; *q != '\0'; q++) {
if (!isdigit((unsigned char)*q)) {
paxwarn(1, "%s contains non-digit", keyword);
return -1;
}
ts->tv_nsec += (*q - '0') * multiplier;
multiplier /= 10;
}
return 0;
}
static int
rd_size(off_t *size, const char *keyword, char *p)
{
const char *errstr;
*size = strtonum(p, 0, LLONG_MAX, &errstr);
if (errstr != NULL) {
paxwarn(1, "%s is %s: %s", keyword, errstr, p);
return -1;
}
return 0;
}
static int
rd_xheader(ARCHD *arcn, int global, off_t size)
{
char buf[sizeof("30xx linkpath=") - 1 + PAXPATHLEN + sizeof("\n")];
long len;
char *delim, *keyword;
char *nextp, *p, *end;
int pad, ret = 0;
pad = TAR_PAD((unsigned)size);
p = end = buf;
while (size > 0 || p < end) {
if (size > 0) {
int rdlen;
if (p > buf) {
memmove(buf, p, end - p);
end -= p - buf;
p = buf;
}
rdlen = MINIMUM(size, (buf + sizeof buf) - end);
if (rd_wrbuf(end, rdlen) != rdlen) {
ret = -1;
break;
}
size -= rdlen;
end += rdlen;
}
if (memchr(p, ' ', end - p) == NULL ||
!isdigit((unsigned char)*p)) {
paxwarn(1, "Invalid extended header record");
ret = -1;
break;
}
errno = 0;
len = strtol(p, &delim, 10);
if (*delim != ' ' || (errno == ERANGE && len == LONG_MAX) ||
len < MINXHDRSZ) {
paxwarn(1, "Invalid extended header record length");
ret = -1;
break;
}
if (len > end - p) {
paxwarn(1, "Extended header record length %lu is "
"out of range", len);
len -= end - p;
if (len <= size && rd_skip(len) == 0) {
size -= len;
p = end = buf;
continue;
}
ret = -1;
break;
}
nextp = p + len;
keyword = p = delim + 1;
p = memchr(p, '=', len);
if (!p || nextp[-1] != '\n') {
paxwarn(1, "Malformed extended header record");
ret = -1;
break;
}
*p++ = nextp[-1] = '\0';
if (!global) {
if (!strcmp(keyword, "path")) {
arcn->nlen = strlcpy(arcn->name, p,
sizeof(arcn->name));
} else if (!strcmp(keyword, "linkpath")) {
arcn->ln_nlen = strlcpy(arcn->ln_name, p,
sizeof(arcn->ln_name));
} else if (!strcmp(keyword, "mtime")) {
ret = rd_time(&arcn->sb.st_mtim, keyword, p);
if (ret < 0)
break;
} else if (!strcmp(keyword, "atime")) {
ret = rd_time(&arcn->sb.st_atim, keyword, p);
if (ret < 0)
break;
} else if (!strcmp(keyword, "ctime")) {
ret = rd_time(&arcn->sb.st_ctim, keyword, p);
if (ret < 0)
break;
} else if (!strcmp(keyword, "size")) {
ret = rd_size(&arcn->sb.st_size, keyword, p);
if (ret < 0)
break;
}
}
p = nextp;
}
if (rd_skip(size + pad) < 0)
return (-1);
return (ret);
}