-/* $OpenBSD: tar.c,v 1.73 2023/09/04 17:05:34 jca Exp $ */
+/* $OpenBSD: tar.c,v 1.74 2023/12/09 23:00:11 jca Exp $ */
/* $NetBSD: tar.c,v 1.5 1995/03/21 09:07:49 cgd Exp $ */
/*-
*/
#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 "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
+
+/* longest record we'll accept */
+#define MAXXHDRSZ BLKMULT
+
/*
* Routines for reading, writing and header identify of various versions of tar
*/
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;
return(0);
}
-/*
- * ustar_wr()
- * write a ustar 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, 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
+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;
-int
-ustar_wr(ARCHD *arcn)
+ 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 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
/*
* check for those file system types ustar cannot store
*/
if (PAX_IS_LINK(arcn->type) &&
((size_t)arcn->ln_nlen > sizeof(hd->linkname))) {
- paxwarn(1, "Link name too long for ustar %s", arcn->ln_name);
- return(1);
+ 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->name) == -1) {
+ paxwarn(1, "Link name too long for pax %s",
+ arcn->ln_name);
+ xheader_free(&xhdr);
+ return(1);
+ }
+#endif
}
/*
* pt != arcn->name, the name has to be split
*/
if ((pt = name_split(arcn->name, arcn->nlen)) == NULL) {
- paxwarn(1, "File name too long for ustar %s", arcn->name);
- return(1);
+ 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->ln_name);
+ xheader_free(&xhdr);
+ return(1);
+ }
+ /* PAX format, we don't need to split the path */
+ pt = arcn->name;
+#endif
}
/*
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
return(1);
out:
+#ifndef SMALL
+ xheader_free(&xhdr);
+#endif
/*
* header field is out of range
*/
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_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
+
/*
* name_split()
* see if the name has to be split for storage in a ustar header. We try
return(nlen);
}
-/* shortest possible extended record: "5 a=\n" */
-#define MINXHDRSZ 5
-
-/* longest record we'll accept */
-#define MAXXHDRSZ BLKMULT
-
static int
rd_time(struct timespec *ts, const char *keyword, char *p)
{