mirror of
https://github.com/openbsd/src.git
synced 2025-01-04 15:25:38 -08:00
8df7613330
The existing tar_opt() implements support for -o write_opt=nodir for the old tar and ustar formats. We don't really want to support it for the pax format, and we want to be able to implement pax format specific options (even if there are none right now). ok millert@
1715 lines
41 KiB
C
1715 lines
41 KiB
C
/* $OpenBSD: tar.c,v 1.85 2024/04/17 18:12:12 jca Exp $ */
|
|
/* $NetBSD: tar.c,v 1.5 1995/03/21 09:07:49 cgd Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 1992 Keith Muller.
|
|
* Copyright (c) 1992, 1993
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to Berkeley by
|
|
* Keith Muller of the University of California, San Diego.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#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;
|
|
};
|
|
|
|
/* shortest possible extended record: "5 a=\n" */
|
|
#define MINXHDRSZ 5
|
|
|
|
/*
|
|
* Routines for reading, writing and header identify of various versions of tar
|
|
*/
|
|
|
|
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 *arcn, int, off_t);
|
|
#ifndef SMALL
|
|
static int wr_xheader(ARCHD *, struct xheader *);
|
|
#endif
|
|
|
|
static uid_t uid_nobody;
|
|
static uid_t uid_warn;
|
|
static gid_t gid_nobody;
|
|
static gid_t gid_warn;
|
|
|
|
/*
|
|
* Routines common to all versions of tar
|
|
*/
|
|
|
|
int tar_nodir; /* do not write dirs under old tar */
|
|
char *gnu_name_string; /* GNU ././@LongLink hackery name */
|
|
char *gnu_link_string; /* GNU ././@LongLink hackery link */
|
|
|
|
/*
|
|
* tar_endwr()
|
|
* add the tar trailer of two null blocks
|
|
* Return:
|
|
* 0 if ok, -1 otherwise (what wr_skip returns)
|
|
*/
|
|
|
|
int
|
|
tar_endwr(void)
|
|
{
|
|
return wr_skip(NULLCNT * BLKMULT);
|
|
}
|
|
|
|
/*
|
|
* tar_endrd()
|
|
* no cleanup needed here, just return size of trailer (for append)
|
|
* Return:
|
|
* size of trailer (2 * BLKMULT)
|
|
*/
|
|
|
|
off_t
|
|
tar_endrd(void)
|
|
{
|
|
return NULLCNT * BLKMULT;
|
|
}
|
|
|
|
/*
|
|
* tar_trail()
|
|
* Called to determine if a header block is a valid trailer. We are passed
|
|
* the block, the in_sync flag (which tells us we are in resync mode;
|
|
* looking for a valid header), and cnt (which starts at zero) which is
|
|
* used to count the number of empty blocks we have seen so far.
|
|
* Return:
|
|
* 0 if a valid trailer, -1 if not a valid trailer, or 1 if the block
|
|
* could never contain a header.
|
|
*/
|
|
|
|
int
|
|
tar_trail(ARCHD *ignore, char *buf, int in_resync, int *cnt)
|
|
{
|
|
int i;
|
|
|
|
/*
|
|
* look for all zero, trailer is two consecutive blocks of zero
|
|
*/
|
|
for (i = 0; i < BLKMULT; ++i) {
|
|
if (buf[i] != '\0')
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* if not all zero it is not a trailer, but MIGHT be a header.
|
|
*/
|
|
if (i != BLKMULT)
|
|
return(-1);
|
|
|
|
/*
|
|
* When given a zero block, we must be careful!
|
|
* If we are not in resync mode, check for the trailer. Have to watch
|
|
* out that we do not mis-identify file data as the trailer, so we do
|
|
* NOT try to id a trailer during resync mode. During resync mode we
|
|
* might as well throw this block out since a valid header can NEVER be
|
|
* a block of all 0 (we must have a valid file name).
|
|
*/
|
|
if (!in_resync && (++*cnt >= NULLCNT))
|
|
return(0);
|
|
return(1);
|
|
}
|
|
|
|
/*
|
|
* ul_oct()
|
|
* convert an unsigned long to an octal string. many oddball field
|
|
* termination characters are used by the various versions of tar in the
|
|
* different fields. term selects which kind to use. str is '0' padded
|
|
* at the front to len. we are unable to use only one format as many old
|
|
* tar readers are very cranky about this.
|
|
* Return:
|
|
* 0 if the number fit into the string, -1 otherwise
|
|
*/
|
|
|
|
static int
|
|
ul_oct(u_long val, char *str, int len, int term)
|
|
{
|
|
char *pt;
|
|
|
|
/*
|
|
* term selects the appropriate character(s) for the end of the string
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* convert and blank pad if there is space
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* ull_oct()
|
|
* Convert an unsigned long long to an octal string. One of many oddball
|
|
* field termination characters are used by the various versions of tar
|
|
* in the different fields. term selects which kind to use. str is
|
|
* '0' padded at the front to len. We are unable to use only one format
|
|
* as many old tar readers are very cranky about this.
|
|
* Return:
|
|
* 0 if the number fit into the string, -1 otherwise
|
|
*/
|
|
|
|
static int
|
|
ull_oct(unsigned long long val, char *str, int len, int term)
|
|
{
|
|
char *pt;
|
|
|
|
/*
|
|
* term selects the appropriate character(s) for the end of the string
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* convert and blank pad if there is space
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* tar_chksm()
|
|
* calculate the checksum for a tar block counting the checksum field as
|
|
* all blanks (BLNKSUM is that value pre-calculated, the sum of 8 blanks).
|
|
* NOTE: we use len to short circuit summing 0's on write since we ALWAYS
|
|
* pad headers with 0.
|
|
* Return:
|
|
* unsigned long checksum
|
|
*/
|
|
|
|
static u_long
|
|
tar_chksm(char *blk, int len)
|
|
{
|
|
char *stop;
|
|
char *pt;
|
|
u_long chksm = BLNKSUM; /* initial value is checksum field sum */
|
|
|
|
/*
|
|
* add the part of the block before the checksum field
|
|
*/
|
|
pt = blk;
|
|
stop = blk + CHK_OFFSET;
|
|
while (pt < stop)
|
|
chksm += (u_long)(*pt++ & 0xff);
|
|
/*
|
|
* move past the checksum field and keep going, spec counts the
|
|
* checksum field as the sum of 8 blanks (which is pre-computed as
|
|
* BLNKSUM).
|
|
* ASSUMED: len is greater than CHK_OFFSET. (len is where our 0 padding
|
|
* starts, no point in summing zero's)
|
|
*/
|
|
pt += CHK_LEN;
|
|
stop = blk + len;
|
|
while (pt < stop)
|
|
chksm += (u_long)(*pt++ & 0xff);
|
|
return(chksm);
|
|
}
|
|
|
|
/*
|
|
* Routines for old BSD style tar (also made portable to sysV tar)
|
|
*/
|
|
|
|
/*
|
|
* tar_id()
|
|
* determine if a block given to us is a valid tar header (and not a USTAR
|
|
* header). We have to be on the lookout for those pesky blocks of all
|
|
* zero's.
|
|
* Return:
|
|
* 0 if a tar header, -1 otherwise
|
|
*/
|
|
|
|
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;
|
|
|
|
/*
|
|
* check for block of zero's first, a simple and fast test, then make
|
|
* sure this is not a ustar header by looking for the ustar magic
|
|
* cookie. We should use TMAGLEN, but some USTAR archive programs are
|
|
* wrong and create archives missing the \0. Last we check the
|
|
* checksum. If this is ok we have to assume it is a valid header.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* tar_opt()
|
|
* handle tar format specific -o options
|
|
* Return:
|
|
* 0 if ok -1 otherwise
|
|
*/
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* we only support one option, and only when writing
|
|
*/
|
|
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);
|
|
}
|
|
|
|
|
|
/*
|
|
* tar_rd()
|
|
* extract the values out of block already determined to be a tar header.
|
|
* store the values in the ARCHD parameter.
|
|
* Return:
|
|
* 0
|
|
*/
|
|
|
|
int
|
|
tar_rd(ARCHD *arcn, char *buf)
|
|
{
|
|
HD_TAR *hd;
|
|
unsigned long long val;
|
|
char *pt;
|
|
|
|
/*
|
|
* we only get proper sized buffers passed to us
|
|
*/
|
|
if (tar_id(buf, BLKMULT) < 0)
|
|
return(-1);
|
|
memset(arcn, 0, sizeof(*arcn));
|
|
arcn->org_name = arcn->name;
|
|
arcn->sb.st_nlink = 1;
|
|
|
|
/*
|
|
* copy out the name and values in the stat buffer
|
|
*/
|
|
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;
|
|
|
|
/*
|
|
* have to look at the last character, it may be a '/' and that is used
|
|
* to encode this as a directory
|
|
*/
|
|
pt = &(arcn->name[arcn->nlen - 1]);
|
|
arcn->pad = 0;
|
|
arcn->skip = 0;
|
|
switch (hd->linkflag) {
|
|
case SYMTYPE:
|
|
/*
|
|
* symbolic link, need to get the link name and set the type in
|
|
* the st_mode so -v printing will look correct.
|
|
*/
|
|
arcn->type = PAX_SLK;
|
|
arcn->sb.st_mode |= S_IFLNK;
|
|
break;
|
|
case LNKTYPE:
|
|
/*
|
|
* hard link, need to get the link name, set the type in the
|
|
* st_mode and st_nlink so -v printing will look better.
|
|
*/
|
|
arcn->type = PAX_HLK;
|
|
arcn->sb.st_nlink = 2;
|
|
|
|
/*
|
|
* no idea of what type this thing really points at, but
|
|
* we set something for printing only.
|
|
*/
|
|
arcn->sb.st_mode |= S_IFREG;
|
|
break;
|
|
case LONGLINKTYPE:
|
|
case LONGNAMETYPE:
|
|
/*
|
|
* GNU long link/file; we tag these here and let the
|
|
* pax internals deal with it -- too ugly otherwise.
|
|
*/
|
|
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:
|
|
/*
|
|
* It is a directory, set the mode for -v printing
|
|
*/
|
|
arcn->type = PAX_DIR;
|
|
arcn->sb.st_mode |= S_IFDIR;
|
|
arcn->sb.st_nlink = 2;
|
|
break;
|
|
case AREGTYPE:
|
|
case REGTYPE:
|
|
default:
|
|
/*
|
|
* If we have a trailing / this is a directory and NOT a file.
|
|
*/
|
|
arcn->ln_name[0] = '\0';
|
|
arcn->ln_nlen = 0;
|
|
if (*pt == '/') {
|
|
/*
|
|
* it is a directory, set the mode for -v printing
|
|
*/
|
|
arcn->type = PAX_DIR;
|
|
arcn->sb.st_mode |= S_IFDIR;
|
|
arcn->sb.st_nlink = 2;
|
|
} else {
|
|
/*
|
|
* have a file that will be followed by data. Set the
|
|
* skip value to the size field and calculate the size
|
|
* of the padding.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* strip off any trailing slash.
|
|
*/
|
|
if (*pt == '/') {
|
|
*pt = '\0';
|
|
--arcn->nlen;
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* tar_wr()
|
|
* write a tar header for the file specified in the ARCHD to the archive.
|
|
* Have to check for file types that cannot be stored and file names that
|
|
* are too long. Be careful of the term (last arg) to ul_oct, each field
|
|
* of tar has it own spec for the termination character(s).
|
|
* ASSUMED: space after header in header block is zero filled
|
|
* Return:
|
|
* 0 if file has data to be written after the header, 1 if file has NO
|
|
* data to write after the header, -1 if archive write failed
|
|
*/
|
|
|
|
int
|
|
tar_wr(ARCHD *arcn)
|
|
{
|
|
HD_TAR *hd;
|
|
int len;
|
|
char hdblk[sizeof(HD_TAR)];
|
|
|
|
/*
|
|
* check for those file system types which tar cannot store
|
|
*/
|
|
switch (arcn->type) {
|
|
case PAX_DIR:
|
|
/*
|
|
* user asked that dirs not be written to the archive
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* check file name len, remember extra char for dirs (the / at the end)
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* Copy the data out of the ARCHD into the tar header based on the type
|
|
* of the file. Remember, many tar readers want all fields to be
|
|
* padded with zero so we zero the header first. We then set the
|
|
* linkflag field (type), the linkname, the size, and set the padding
|
|
* (if any) to be added after the file data (0 for all other types,
|
|
* as they only have a header).
|
|
*/
|
|
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) {
|
|
/*
|
|
* directories are the same as files, except have a filename
|
|
* that ends with a /, we add the slash here. No data follows
|
|
* dirs, so no pad.
|
|
*/
|
|
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) {
|
|
/*
|
|
* no data follows this file, so no pad
|
|
*/
|
|
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)) {
|
|
/*
|
|
* no data follows this file, so no pad
|
|
*/
|
|
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 {
|
|
/*
|
|
* data follows this file, so set the pad
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* copy those fields that are independent of the type
|
|
*/
|
|
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;
|
|
|
|
/*
|
|
* calculate and add the checksum, then write the header. A return of
|
|
* 0 tells the caller to now write the file data, 1 says no data needs
|
|
* to be written
|
|
*/
|
|
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)
|
|
return(-1);
|
|
if (wr_skip(BLKMULT - sizeof(HD_TAR)) < 0)
|
|
return(-1);
|
|
if (PAX_IS_REG(arcn->type))
|
|
return(0);
|
|
return(1);
|
|
|
|
out:
|
|
/*
|
|
* header field is out of range
|
|
*/
|
|
paxwarn(1, "Tar header field is too small for %s", arcn->org_name);
|
|
return(1);
|
|
}
|
|
|
|
/*
|
|
* Routines for POSIX ustar
|
|
*/
|
|
|
|
/*
|
|
* ustar_id()
|
|
* determine if a block given to us is a valid ustar header. We have to
|
|
* be on the lookout for those pesky blocks of all zero's
|
|
* Return:
|
|
* 0 if a ustar header, -1 otherwise
|
|
*/
|
|
|
|
int
|
|
ustar_id(char *blk, int size)
|
|
{
|
|
HD_USTAR *hd;
|
|
|
|
if (size < BLKMULT)
|
|
return(-1);
|
|
hd = (HD_USTAR *)blk;
|
|
|
|
/*
|
|
* check for block of zero's first, a simple and fast test then check
|
|
* ustar magic cookie. We should use TMAGLEN, but some USTAR archive
|
|
* programs are fouled up and create archives missing the \0. Last we
|
|
* check the checksum. If ok we have to assume it is a valid header.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* ustar_rd()
|
|
* extract the values out of block already determined to be a ustar header.
|
|
* store the values in the ARCHD parameter.
|
|
* 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;
|
|
|
|
/*
|
|
* we only get proper sized buffers
|
|
*/
|
|
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;
|
|
|
|
/* Process Extended headers. */
|
|
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);
|
|
|
|
/* Update and check the ustar header. */
|
|
if (rd_wrbuf(buf, BLKMULT) != BLKMULT)
|
|
return (-1);
|
|
if (ustar_id(buf, BLKMULT) < 0)
|
|
return(-1);
|
|
|
|
/* if the next block is another extension, reset the values */
|
|
if (hd->typeflag == XHDRTYPE || hd->typeflag == GHDRTYPE)
|
|
goto reset;
|
|
}
|
|
|
|
if (!arcn->nlen) {
|
|
/*
|
|
* See if the filename is split into two parts. if, so join
|
|
* the parts. We copy the prefix first and add a / between
|
|
* the prefix and name.
|
|
*/
|
|
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));
|
|
}
|
|
|
|
/*
|
|
* follow the spec to the letter. we should only have mode bits, strip
|
|
* off all other crud we may be passed.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* If we can find the ascii names for gname and uname in the password
|
|
* and group files we will use the uid's and gid they bind. Otherwise
|
|
* we use the uid and gid values stored in the header. (This is what
|
|
* the posix spec wants).
|
|
*/
|
|
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);
|
|
|
|
/*
|
|
* set the defaults, these may be changed depending on the file type
|
|
*/
|
|
arcn->pad = 0;
|
|
arcn->skip = 0;
|
|
arcn->sb.st_rdev = (dev_t)0;
|
|
|
|
/*
|
|
* set the mode and PAX type according to the typeflag in the header
|
|
*/
|
|
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;
|
|
|
|
/*
|
|
* Some programs that create ustar archives append a '/'
|
|
* to the pathname for directories. This clearly violates
|
|
* ustar specs, but we will silently strip it off anyway.
|
|
*/
|
|
if (arcn->name[arcn->nlen - 1] == '/')
|
|
arcn->name[--arcn->nlen] = '\0';
|
|
break;
|
|
case BLKTYPE:
|
|
case CHRTYPE:
|
|
/*
|
|
* this type requires the rdev field to be set.
|
|
*/
|
|
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;
|
|
/*
|
|
* so printing looks better
|
|
*/
|
|
arcn->sb.st_mode |= S_IFREG;
|
|
arcn->sb.st_nlink = 2;
|
|
}
|
|
break;
|
|
case LONGLINKTYPE:
|
|
case LONGNAMETYPE:
|
|
/*
|
|
* GNU long link/file; we tag these here and let the
|
|
* pax internals deal with it -- too ugly otherwise.
|
|
*/
|
|
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:
|
|
/*
|
|
* these types have file data that follows. Set the skip and
|
|
* pad fields.
|
|
*/
|
|
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;
|
|
|
|
/* Only write subsecond part if non-zero */
|
|
if (value->tv_nsec != 0) {
|
|
int n;
|
|
|
|
n = snprintf(frac, sizeof(frac), ".%09ld",
|
|
(long)value->tv_nsec);
|
|
if (n <= 0)
|
|
return -1;
|
|
|
|
/* Zap trailing zeros */
|
|
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(ARCHD *arcn, 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))
|
|
return -1;
|
|
|
|
/*
|
|
* Best effort attempt at providing a useful file name for
|
|
* implementations that don't support pax format. Don't bother
|
|
* with truncation if the resulting file name doesn't fit.
|
|
* XXX dirname/basename portability (check return value?)
|
|
*/
|
|
(void)snprintf(buf, sizeof(buf), "%s/PaxHeaders.%ld/%s",
|
|
dirname(arcn->name), (long)getpid(), basename(arcn->name));
|
|
fieldcpy(hd->name, sizeof(hd->name), buf, sizeof(buf));
|
|
|
|
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))
|
|
return -1;
|
|
|
|
if (ul_oct(tar_chksm(hdblk, sizeof(HD_USTAR)), hd->chksum,
|
|
sizeof(hd->chksum), 3))
|
|
return -1;
|
|
|
|
/* write out extended header */
|
|
if (wr_rdbuf(hdblk, sizeof(HD_USTAR)) < 0)
|
|
return -1;
|
|
if (wr_skip(BLKMULT - sizeof(HD_USTAR)) < 0)
|
|
return -1;
|
|
|
|
/* write out extended header records */
|
|
SLIST_FOREACH(rec, xhdr, entry)
|
|
if (wr_rdbuf(rec->record, rec->reclen) < 0)
|
|
return -1;
|
|
|
|
if (wr_skip(TAR_PAD(size)) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
#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;
|
|
|
|
/*
|
|
* check for those file system types ustar cannot store
|
|
*/
|
|
if (arcn->type == PAX_SCK) {
|
|
paxwarn(1, "Ustar cannot archive a socket %s", arcn->org_name);
|
|
return(1);
|
|
}
|
|
|
|
/*
|
|
* user asked that dirs not be written to the archive
|
|
*/
|
|
if (arcn->type == PAX_DIR && tar_nodir)
|
|
return (1);
|
|
|
|
/*
|
|
* check the length of the linkname
|
|
*/
|
|
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
|
|
}
|
|
|
|
/*
|
|
* split the path name into prefix and name fields (if needed). if
|
|
* pt != arcn->name, the name has to be split
|
|
*/
|
|
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);
|
|
}
|
|
/* PAX format, we don't need to split the path */
|
|
pt = arcn->name;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* zero out the header so we don't have to worry about zero fill below
|
|
*/
|
|
memset(hdblk, 0, sizeof(hdblk));
|
|
hd = (HD_USTAR *)hdblk;
|
|
arcn->pad = 0;
|
|
|
|
/*
|
|
* split the name, or zero out the prefix
|
|
*/
|
|
if (pt != arcn->name) {
|
|
/*
|
|
* name was split, pt points at the / where the split is to
|
|
* occur, we remove the / and copy the first part to the prefix
|
|
*/
|
|
*pt = '\0';
|
|
fieldcpy(hd->prefix, sizeof(hd->prefix), arcn->name,
|
|
sizeof(arcn->name));
|
|
*pt++ = '/';
|
|
}
|
|
|
|
/*
|
|
* copy the name part. this may be the whole path or the part after
|
|
* the prefix
|
|
*/
|
|
fieldcpy(hd->name, sizeof(hd->name), pt,
|
|
sizeof(arcn->name) - (pt - arcn->name));
|
|
|
|
/*
|
|
* set the fields in the header that are type dependent
|
|
*/
|
|
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:
|
|
/*
|
|
* file data with this type, set the padding
|
|
*/
|
|
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);
|
|
|
|
/*
|
|
* set the remaining fields. Some versions want all 16 bits of mode
|
|
* we better humor them (they really do not meet spec though)....
|
|
*/
|
|
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) {
|
|
/*
|
|
* The pax format can preserve atime and store
|
|
* a possibly more accurate mtime.
|
|
*
|
|
* ctime isn't specified by POSIX so omit it.
|
|
*/
|
|
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
|
|
/* write out a pax extended header if needed */
|
|
if (!SLIST_EMPTY(&xhdr)) {
|
|
int ret;
|
|
|
|
ret = wr_xheader(arcn, &xhdr);
|
|
xheader_free(&xhdr);
|
|
if (ret == -1)
|
|
return(-1);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* calculate and store the checksum write the header to the archive
|
|
* return 0 tells the caller to now write the file data, 1 says no data
|
|
* needs to be written
|
|
*/
|
|
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)
|
|
return(-1);
|
|
if (wr_skip(BLKMULT - sizeof(HD_USTAR)) < 0)
|
|
return(-1);
|
|
if (PAX_IS_REG(arcn->type))
|
|
return(0);
|
|
return(1);
|
|
|
|
out:
|
|
#ifndef SMALL
|
|
xheader_free(&xhdr);
|
|
#endif
|
|
/*
|
|
* header field is out of range
|
|
*/
|
|
paxwarn(1, "Ustar header field is too small for %s", arcn->org_name);
|
|
return(1);
|
|
}
|
|
|
|
/*
|
|
* ustar_wr()
|
|
* Write out a ustar format archive.
|
|
* Have to check for file types that cannot be stored and file names that
|
|
* are too long. Be careful of the term (last arg) to ul_oct, we only use
|
|
* '\0' for the termination character (this is different than picky tar).
|
|
* ASSUMED: space after header in header block is zero filled
|
|
* Return:
|
|
* 0 if file has data to be written after the header, 1 if file has NO
|
|
* data to write after the header, -1 if archive write failed
|
|
*/
|
|
int
|
|
ustar_wr(ARCHD *arcn)
|
|
{
|
|
return wr_ustar_or_pax(arcn, 1);
|
|
}
|
|
|
|
/*
|
|
* pax_id()
|
|
* determine if a block given to us is a valid pax header.
|
|
* Return:
|
|
* 0 if a pax header, -1 otherwise
|
|
*/
|
|
#ifndef SMALL
|
|
int
|
|
pax_id(char *blk, int size)
|
|
{
|
|
HD_USTAR *hd;
|
|
|
|
if (size < BLKMULT)
|
|
return(-1);
|
|
hd = (HD_USTAR *)blk;
|
|
|
|
/*
|
|
* check for block of zero's first, a simple and fast test then check
|
|
* ustar magic cookie. We should use TMAGLEN, but some USTAR archive
|
|
* programs are fouled up and create archives missing the \0. Last we
|
|
* check the checksum and the type flag. If ok we have to assume it is
|
|
* a valid pax header.
|
|
*/
|
|
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);
|
|
/*
|
|
* It is valid for a pax formatted archive not to start with
|
|
* a global header nor with an extended header. In that case
|
|
* we'll fall back to ustar in append mode.
|
|
*/
|
|
if (hd->typeflag == XHDRTYPE || hd->typeflag == GHDRTYPE)
|
|
return(0);
|
|
return (-1);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* pax_wr()
|
|
* Write out a pax format archive.
|
|
* Have to check for file types that cannot be stored. Be careful of the
|
|
* term (last arg) to ul_oct, we only use '\0' for the termination
|
|
* character (this is different than picky tar).
|
|
* ASSUMED: space after header in header block is zero filled
|
|
* Return:
|
|
* 0 if file has data to be written after the header, 1 if file has NO
|
|
* data to write after the header, -1 if archive write failed
|
|
*/
|
|
#ifndef SMALL
|
|
int
|
|
pax_wr(ARCHD *arcn)
|
|
{
|
|
return wr_ustar_or_pax(arcn, 0);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* pax_opt()
|
|
* handle pax format specific -o options
|
|
* Return:
|
|
* 0 if ok -1 otherwise
|
|
*/
|
|
#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
|
|
|
|
/*
|
|
* name_split()
|
|
* see if the name has to be split for storage in a ustar header. We try
|
|
* to fit the entire name in the name field without splitting if we can.
|
|
* The split point is always at a /
|
|
* Return
|
|
* character pointer to split point (always the / that is to be removed
|
|
* if the split is not needed, the points is set to the start of the file
|
|
* name (it would violate the spec to split there). A NULL is returned if
|
|
* the file name is too long
|
|
*/
|
|
|
|
static char *
|
|
name_split(char *name, int len)
|
|
{
|
|
char *start;
|
|
|
|
/*
|
|
* check to see if the file name is small enough to fit in the name
|
|
* field. if so just return a pointer to the name.
|
|
* The strings can fill the complete name and prefix fields
|
|
* without a NUL terminator.
|
|
*/
|
|
if (len <= TNMSZ)
|
|
return(name);
|
|
if (len > (TPFSZ + TNMSZ + 1))
|
|
return(NULL);
|
|
|
|
/*
|
|
* we start looking at the biggest sized piece that fits in the name
|
|
* field. We walk forward looking for a slash to split at. The idea is
|
|
* to find the biggest piece to fit in the name field (or the smallest
|
|
* prefix we can find) (the -1 is correct the biggest piece would
|
|
* include the slash between the two parts that gets thrown away)
|
|
*/
|
|
start = name + len - TNMSZ - 1;
|
|
|
|
/*
|
|
* the prefix may not be empty, so skip the first character when
|
|
* trying to split a path of exactly TNMSZ+1 characters.
|
|
* NOTE: This means the ustar format can't store /str if
|
|
* str contains no slashes and the length of str == TNMSZ
|
|
*/
|
|
if (start == name)
|
|
++start;
|
|
|
|
while ((*start != '\0') && (*start != '/'))
|
|
++start;
|
|
|
|
/*
|
|
* if we hit the end of the string, this name cannot be split, so we
|
|
* cannot store this file.
|
|
*/
|
|
if (*start == '\0')
|
|
return(NULL);
|
|
|
|
/*
|
|
* the split point isn't valid if it results in a prefix
|
|
* longer than TPFSZ
|
|
*/
|
|
if ((start - name) > TPFSZ)
|
|
return(NULL);
|
|
|
|
/*
|
|
* ok have a split point, return it to the caller
|
|
*/
|
|
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) {
|
|
/* *gnu_name is NUL terminated */
|
|
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;
|
|
|
|
/* Assume off_t is a long long. */
|
|
*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)
|
|
{
|
|
/*
|
|
* The pax format supposedly supports arbitrarily sized extended
|
|
* record headers, this implementation doesn't.
|
|
*/
|
|
char buf[sizeof("30xx linkpath=") - 1 + PAXPATHLEN + sizeof("\n")];
|
|
long len;
|
|
char *delim, *keyword;
|
|
char *nextp, *p, *end;
|
|
int pad, ret = 0;
|
|
|
|
/* before we alter size, make note of how much we have to skip */
|
|
pad = TAR_PAD((unsigned)size);
|
|
|
|
p = end = buf;
|
|
while (size > 0 || p < end) {
|
|
if (size > 0) {
|
|
int rdlen;
|
|
|
|
/* shift stuff down */
|
|
if (p > buf) {
|
|
memmove(buf, p, end - p);
|
|
end -= p - buf;
|
|
p = buf;
|
|
}
|
|
|
|
/* fill starting at end */
|
|
rdlen = MINIMUM(size, (buf + sizeof buf) - end);
|
|
if (rd_wrbuf(end, rdlen) != rdlen) {
|
|
ret = -1;
|
|
break;
|
|
}
|
|
size -= rdlen;
|
|
end += rdlen;
|
|
}
|
|
|
|
/* [p, end) is good */
|
|
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);
|
|
/* if we can just toss this record, do so */
|
|
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);
|
|
}
|