Add basic write support for 'pax' format archives
authorjca <jca@openbsd.org>
Sat, 9 Dec 2023 23:00:11 +0000 (23:00 +0000)
committerjca <jca@openbsd.org>
Sat, 9 Dec 2023 23:00:11 +0000 (23:00 +0000)
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@

bin/pax/cpio.1
bin/pax/extern.h
bin/pax/options.c
bin/pax/pax.1
bin/pax/tar.c

index 89a6e36..341afe1 100644 (file)
@@ -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
index 3d7303c..9730b8d 100644 (file)
@@ -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
index 835c38f..454c840 100644 (file)
@@ -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 */
index dda8e2a..ce36702 100644 (file)
@@ -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 ,
index d3eb5da..91bc114 100644 (file)
@@ -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 $      */
 
 /*-
  */
 
 #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
  */
@@ -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)
 {