#ifndef lint
#if 0
static const char sccsid[] = "@(#)tar.c 8.2 (Berkeley) 4/18/94";
#else
static const char rcsid[] = "$OpenBSD: tar.c,v 1.41 2006/03/04 20:24:55 otto Exp $";
#endif
#endif
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include "pax.h"
#include "extern.h"
#include "tar.h"
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);
#ifndef LONG_OFF_T
static int uqd_oct(u_quad_t, char *, int, int);
#endif
static uid_t uid_nobody;
static uid_t uid_warn;
static gid_t gid_nobody;
static gid_t gid_warn;
static int tar_nodir;
char *gnu_name_string;
char *gnu_link_string;
int
tar_endwr(void)
{
return(wr_skip((off_t)(NULLCNT*BLKMULT)));
}
off_t
tar_endrd(void)
{
return((off_t)(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);
if ((val = val >> 3) == (u_long)0)
break;
}
while (pt >= str)
*pt-- = '0';
if (val != (u_long)0)
return(-1);
return(0);
}
#ifndef LONG_OFF_T
static int
uqd_oct(u_quad_t 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);
if ((val = val >> 3) == 0)
break;
}
while (pt >= str)
*pt-- = '0';
if (val != (u_quad_t)0)
return(-1);
return(0);
}
#endif
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;
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);
#ifdef LONG_OFF_T
arcn->sb.st_size = (off_t)asc_ul(hd->size, sizeof(hd->size), OCT);
#else
arcn->sb.st_size = (off_t)asc_uqd(hd->size, sizeof(hd->size), OCT);
#endif
arcn->sb.st_mtime = (time_t)asc_ul(hd->mtime, sizeof(hd->mtime), OCT);
arcn->sb.st_ctime = arcn->sb.st_atime = arcn->sb.st_mtime;
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;
arcn->ln_nlen = strlcpy(arcn->ln_name, hd->linkname, sizeof(arcn->ln_name));
break;
case LNKTYPE:
arcn->type = PAX_HLK;
arcn->sb.st_nlink = 2;
arcn->ln_nlen = strlcpy(arcn->ln_name, hd->linkname, sizeof(arcn->ln_name));
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;
HD_TAR hdblk;
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 (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 (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;
strlcpy(hd->name, arcn->name, sizeof(hd->name));
arcn->pad = 0;
if (arcn->type == PAX_DIR) {
hd->linkflag = AREGTYPE;
hd->name[len-1] = '/';
if (ul_oct((u_long)0L, hd->size, sizeof(hd->size), 1))
goto out;
} else if (arcn->type == PAX_SLK) {
hd->linkflag = SYMTYPE;
strlcpy(hd->linkname, arcn->ln_name, sizeof(hd->linkname));
if (ul_oct((u_long)0L, hd->size, sizeof(hd->size), 1))
goto out;
} else if ((arcn->type == PAX_HLK) || (arcn->type == PAX_HRG)) {
hd->linkflag = LNKTYPE;
strlcpy(hd->linkname, arcn->ln_name, sizeof(hd->linkname));
if (ul_oct((u_long)0L, hd->size, sizeof(hd->size), 1))
goto out;
} else {
hd->linkflag = AREGTYPE;
# ifdef LONG_OFF_T
if (ul_oct((u_long)arcn->sb.st_size, hd->size,
sizeof(hd->size), 1)) {
# else
if (uqd_oct((u_quad_t)arcn->sb.st_size, hd->size,
sizeof(hd->size), 1)) {
# endif
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((u_long)arcn->sb.st_mode, hd->mode, sizeof(hd->mode), 0) ||
ul_oct((u_long)arcn->sb.st_uid, hd->uid, sizeof(hd->uid), 0) ||
ul_oct((u_long)arcn->sb.st_gid, hd->gid, sizeof(hd->gid), 0) ||
ul_oct((u_long)arcn->sb.st_mtime, hd->mtime, sizeof(hd->mtime), 1))
goto out;
if (ul_oct(tar_chksm((char *)&hdblk, sizeof(HD_TAR)), hd->chksum,
sizeof(hd->chksum), 3))
goto out;
if (wr_rdbuf((char *)&hdblk, sizeof(HD_TAR)) < 0)
return(-1);
if (wr_skip((off_t)(BLKMULT - sizeof(HD_TAR))) < 0)
return(-1);
if ((arcn->type == PAX_CTG) || (arcn->type == PAX_REG))
return(0);
return(1);
out:
paxwarn(1, "Tar header field is too small for %s", arcn->org_name);
return(1);
}
int
ustar_strd(void)
{
if ((usrtb_start() < 0) || (grptb_start() < 0))
return(-1);
return(0);
}
int
ustar_stwr(void)
{
if ((uidtb_start() < 0) || (gidtb_start() < 0))
return(-1);
return(0);
}
int
ustar_id(char *blk, int size)
{
HD_USTAR *hd;
if (size < BLKMULT)
return(-1);
hd = (HD_USTAR *)blk;
if (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;
char *dest;
int cnt = 0;
dev_t devmajor;
dev_t devminor;
if (ustar_id(buf, BLKMULT) < 0)
return(-1);
memset(arcn, 0, sizeof(*arcn));
arcn->org_name = arcn->name;
arcn->sb.st_nlink = 1;
hd = (HD_USTAR *)buf;
dest = arcn->name;
if (*(hd->prefix) != '\0') {
cnt = strlcpy(dest, hd->prefix, sizeof(arcn->name) - 1);
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));
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);
#ifdef LONG_OFF_T
arcn->sb.st_size = (off_t)asc_ul(hd->size, sizeof(hd->size), OCT);
#else
arcn->sb.st_size = (off_t)asc_uqd(hd->size, sizeof(hd->size), OCT);
#endif
arcn->sb.st_mtime = (time_t)asc_ul(hd->mtime, sizeof(hd->mtime), OCT);
arcn->sb.st_ctime = arcn->sb.st_atime = arcn->sb.st_mtime;
hd->gname[sizeof(hd->gname) - 1] = '\0';
if (gid_name(hd->gname, &(arcn->sb.st_gid)) < 0)
arcn->sb.st_gid = (gid_t)asc_ul(hd->gid, sizeof(hd->gid), OCT);
hd->uname[sizeof(hd->uname) - 1] = '\0';
if (uid_name(hd->uname, &(arcn->sb.st_uid)) < 0)
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);
}
int
ustar_wr(ARCHD *arcn)
{
HD_USTAR *hd;
char *pt;
char hdblk[sizeof(HD_USTAR)];
mode_t mode12only;
int term_char=3;
term_char=1;
if (arcn->type == PAX_SCK) {
paxwarn(1, "Ustar cannot archive a socket %s", arcn->org_name);
return(1);
}
if (((arcn->type == PAX_SLK) || (arcn->type == PAX_HLK) ||
(arcn->type == PAX_HRG)) && (arcn->ln_nlen > sizeof(hd->linkname))){
paxwarn(1, "Link name too long for ustar %s", arcn->ln_name);
return(1);
}
if ((pt = name_split(arcn->name, arcn->nlen)) == NULL) {
paxwarn(1, "File name too long for ustar %s", arcn->name);
return(1);
}
memset(hdblk, 0, sizeof(hdblk));
hd = (HD_USTAR *)hdblk;
arcn->pad = 0L;
ul_oct(0, hd->devmajor, sizeof(hd->devmajor), term_char);
ul_oct(0, hd->devminor, sizeof(hd->devminor), term_char);
if (pt != arcn->name) {
*pt = '\0';
strlcpy(hd->prefix, arcn->name, sizeof(hd->prefix));
*pt++ = '/';
}
if (strlen(pt) == sizeof(hd->name)) {
strncpy(hd->name, pt, sizeof(hd->name));
} else {
strlcpy(hd->name, pt, sizeof(hd->name));
}
switch (arcn->type) {
case PAX_DIR:
hd->typeflag = DIRTYPE;
if (ul_oct((u_long)0L, hd->size, sizeof(hd->size), term_char))
goto out;
break;
case PAX_CHR:
case PAX_BLK:
if (arcn->type == PAX_CHR)
hd->typeflag = CHRTYPE;
else
hd->typeflag = BLKTYPE;
if (ul_oct((u_long)MAJOR(arcn->sb.st_rdev), hd->devmajor,
sizeof(hd->devmajor), term_char) ||
ul_oct((u_long)MINOR(arcn->sb.st_rdev), hd->devminor,
sizeof(hd->devminor), term_char) ||
ul_oct((u_long)0L, hd->size, sizeof(hd->size), term_char))
goto out;
break;
case PAX_FIF:
hd->typeflag = FIFOTYPE;
if (ul_oct((u_long)0L, hd->size, sizeof(hd->size), term_char))
goto out;
break;
case PAX_SLK:
case PAX_HLK:
case PAX_HRG:
if (arcn->type == PAX_SLK)
hd->typeflag = SYMTYPE;
else
hd->typeflag = LNKTYPE;
if (strlen(arcn->ln_name) == sizeof(hd->linkname)) {
strncpy(hd->linkname, arcn->ln_name, sizeof(hd->linkname));
} else {
strlcpy(hd->linkname, arcn->ln_name, sizeof(hd->linkname));
}
if (ul_oct((u_long)0L, hd->size, sizeof(hd->size), term_char))
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);
# ifdef LONG_OFF_T
if (ul_oct((u_long)arcn->sb.st_size, hd->size,
sizeof(hd->size), term_char)) {
# else
if (uqd_oct((u_quad_t)arcn->sb.st_size, hd->size,
sizeof(hd->size), term_char)) {
# endif
paxwarn(1,"File is too long for ustar %s",arcn->org_name);
return(1);
}
break;
}
strncpy(hd->magic, TMAGIC, TMAGLEN);
strncpy(hd->version, TVERSION, TVERSLEN);
if (ul_oct((u_long)arcn->sb.st_uid, hd->uid, sizeof(hd->uid), term_char)) {
if (uid_nobody == 0) {
if (uid_name("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((u_long)uid_nobody, hd->uid, sizeof(hd->uid), term_char))
goto out;
}
if (ul_oct((u_long)arcn->sb.st_gid, hd->gid, sizeof(hd->gid), term_char)) {
if (gid_nobody == 0) {
if (gid_name("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((u_long)gid_nobody, hd->gid, sizeof(hd->gid), term_char))
goto out;
}
mode12only = ((u_long)arcn->sb.st_mode) & 0x00000fff;
if (ul_oct((u_long)mode12only, hd->mode, sizeof(hd->mode), term_char) ||
ul_oct((u_long)arcn->sb.st_mtime,hd->mtime,sizeof(hd->mtime),term_char))
goto out;
strncpy(hd->uname, name_uid(arcn->sb.st_uid, 0), sizeof(hd->uname));
strncpy(hd->gname, name_gid(arcn->sb.st_gid, 0), sizeof(hd->gname));
if (ul_oct(tar_chksm(hdblk, sizeof(HD_USTAR)), hd->chksum,
sizeof(hd->chksum), term_char))
goto out;
if (wr_rdbuf((char *)&hdblk, sizeof(HD_USTAR)) < 0)
return(-1);
if (wr_skip((off_t)(BLKMULT - sizeof(HD_USTAR))) < 0)
return(-1);
if ((arcn->type == PAX_CTG) || (arcn->type == PAX_REG))
return(0);
return(1);
out:
paxwarn(1, "Ustar header field is too small for %s", arcn->org_name);
return(1);
}
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 == '/') && (start == name))
++start;
while ((*start != '\0') && (*start != '/'))
++start;
if (*start == '\0')
return(NULL);
len = start - name;
if ((len > TPFSZ) || (len == 0))
return(NULL);
return(start);
}
static size_t
expandname(char *buf, size_t len, char **gnu_name, const char *name,
size_t name_len)
{
size_t nlen;
if (*gnu_name) {
if ((nlen = strlcpy(buf, *gnu_name, len)) >= len)
nlen = len - 1;
free(*gnu_name);
*gnu_name = NULL;
} else {
if (name_len < len) {
if ((nlen = strlcpy(buf, name, name_len+1)) >= name_len+1)
nlen = name_len;
} else {
if ((nlen = strlcpy(buf, name, len)) >= len)
nlen = len - 1;
}
}
return(nlen);
}