From: jca Date: Sat, 9 Dec 2023 23:00:11 +0000 (+0000) Subject: Add basic write support for 'pax' format archives X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=013e174a3726f5dfc8d86cfb0c801d83d8f77ad6;p=openbsd Add basic write support for 'pax' format archives Keep writing archives in ustar format by default. People can test the posix 'pax' format using pax(1) -w -x pax ... or cpio -o -H pax ...; tar(1) can't exercise this code yet. Only long names file and link names are supported for now. With input and tests from caspar@, ok millert@ --- diff --git a/bin/pax/cpio.1 b/bin/pax/cpio.1 index 89a6e3675f7..341afe1e17b 100644 --- a/bin/pax/cpio.1 +++ b/bin/pax/cpio.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: cpio.1,v 1.36 2020/01/16 16:46:46 schwarze Exp $ +.\" $OpenBSD: cpio.1,v 1.37 2023/12/09 23:00:11 jca Exp $ .\" .\" Copyright (c) 1997 SigmaSoft, Th. Lockert .\" All rights reserved. @@ -23,7 +23,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: January 16 2020 $ +.Dd $Mdocdate: December 9 2023 $ .Dt CPIO 1 .Os .Sh NAME @@ -98,6 +98,8 @@ format. Old octal character .Nm format. +.It Ar pax +POSIX pax format. .It Ar sv4cpio SVR4 hex .Nm @@ -173,6 +175,8 @@ format. Old octal character .Nm format. +.It Ar pax +POSIX pax format. .It Ar sv4cpio SVR4 hex .Nm @@ -298,6 +302,8 @@ be used for larger files. .It bcpio Ta "4 Gigabytes" .It sv4cpio Ta "4 Gigabytes" .It cpio Ta "8 Gigabytes" +.\" XXX should be "unlimited" +.It pax Ta "8 Gigabytes" .It tar Ta "8 Gigabytes" .It ustar Ta "8 Gigabytes" .El diff --git a/bin/pax/extern.h b/bin/pax/extern.h index 3d7303c42cb..9730b8db82b 100644 --- a/bin/pax/extern.h +++ b/bin/pax/extern.h @@ -1,4 +1,4 @@ -/* $OpenBSD: extern.h,v 1.61 2023/11/26 16:04:17 espie Exp $ */ +/* $OpenBSD: extern.h,v 1.62 2023/12/09 23:00:11 jca Exp $ */ /* $NetBSD: extern.h,v 1.5 1996/03/26 23:54:16 mrg Exp $ */ /*- @@ -284,6 +284,7 @@ int tar_wr(ARCHD *); int ustar_id(char *, int); int ustar_rd(ARCHD *, char *); int ustar_wr(ARCHD *); +int pax_wr(ARCHD *); /* * tty_subs.c diff --git a/bin/pax/options.c b/bin/pax/options.c index 835c38f00b2..454c8409a97 100644 --- a/bin/pax/options.c +++ b/bin/pax/options.c @@ -1,4 +1,4 @@ -/* $OpenBSD: options.c,v 1.106 2023/11/26 16:04:17 espie Exp $ */ +/* $OpenBSD: options.c,v 1.107 2023/12/09 23:00:11 jca Exp $ */ /* $NetBSD: options.c,v 1.6 1996/03/26 23:54:18 mrg Exp $ */ /*- @@ -216,6 +216,8 @@ FSUB fsub[] = { { }, /* 9: gzip, to detect failure to use -z */ { }, +/* 10: POSIX PAX */ + { }, #else /* 6: compress, to detect failure to use -Z */ {NULL, 0, 4, 0, 0, 0, 0, compress_id}, @@ -225,6 +227,10 @@ FSUB fsub[] = { {NULL, 0, 4, 0, 0, 0, 0, bzip2_id}, /* 9: gzip, to detect failure to use -z */ {NULL, 0, 4, 0, 0, 0, 0, gzip_id}, +/* 10: POSIX PAX */ + {"pax", 5120, BLKMULT, 0, 1, BLKMULT, 0, ustar_id, no_op, + ustar_rd, tar_endrd, no_op, pax_wr, tar_endwr, tar_trail, + tar_opt}, #endif }; #define F_OCPIO 0 /* format when called as cpio -6 */ diff --git a/bin/pax/pax.1 b/bin/pax/pax.1 index dda8e2abfb6..ce3670216ad 100644 --- a/bin/pax/pax.1 +++ b/bin/pax/pax.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: pax.1,v 1.76 2022/03/31 17:27:14 naddy Exp $ +.\" $OpenBSD: pax.1,v 1.77 2023/12/09 23:00:11 jca Exp $ .\" $NetBSD: pax.1,v 1.3 1995/03/21 09:07:37 cgd Exp $ .\" .\" Copyright (c) 1992 Keith Muller. @@ -34,7 +34,7 @@ .\" .\" @(#)pax.1 8.4 (Berkeley) 4/18/94 .\" -.Dd $Mdocdate: March 31 2022 $ +.Dd $Mdocdate: December 9 2023 $ .Dt PAX 1 .Os .Sh NAME @@ -868,6 +868,11 @@ standard. The default blocksize for this format is 10240 bytes. Filenames stored by this format must be 100 characters or less in length; the total pathname must be 256 characters or less. +.It Cm pax +The pax interchange format specified in the +.St -p1003.1-2001 +standard. +The default blocksize for this format is 5120 bytes. .El .Pp .Nm @@ -1081,9 +1086,10 @@ utility is compliant with the specification, except that the .Cm pax -archive format and the +archive format is only partially supported, +and the .Cm listopt -keyword are unsupported. +keyword is unsupported. .Pp The flags .Op Fl 0BDEGjOPTUYZz , diff --git a/bin/pax/tar.c b/bin/pax/tar.c index d3eb5daec17..91bc114a7ed 100644 --- a/bin/pax/tar.c +++ b/bin/pax/tar.c @@ -1,4 +1,4 @@ -/* $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 $ */ /*- @@ -35,10 +35,12 @@ */ #include +#include #include #include #include #include +#include #include #include #include @@ -50,6 +52,19 @@ #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 */ @@ -60,6 +75,9 @@ 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; @@ -891,24 +909,121 @@ reset: 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 @@ -929,8 +1044,19 @@ ustar_wr(ARCHD *arcn) */ 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 } /* @@ -938,8 +1064,21 @@ ustar_wr(ARCHD *arcn) * 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 } /* @@ -1074,6 +1213,18 @@ ustar_wr(ARCHD *arcn) 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 @@ -1091,6 +1242,9 @@ ustar_wr(ARCHD *arcn) return(1); out: +#ifndef SMALL + xheader_free(&xhdr); +#endif /* * header field is out of range */ @@ -1098,6 +1252,42 @@ ustar_wr(ARCHD *arcn) 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 @@ -1184,12 +1374,6 @@ expandname(char *buf, size_t len, char **gnu_name, const char *name, 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) {