aboutsummaryrefslogtreecommitdiffstats
path: root/src/io_archive.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/io_archive.c')
-rw-r--r--src/io_archive.c444
1 files changed, 444 insertions, 0 deletions
diff --git a/src/io_archive.c b/src/io_archive.c
new file mode 100644
index 0000000..22145ab
--- /dev/null
+++ b/src/io_archive.c
@@ -0,0 +1,444 @@
+/* io_archive.c - Alpine Package Keeper (APK)
+ *
+ * Copyright (C) 2005-2008 Natanael Copa <n@tanael.org>
+ * Copyright (C) 2008-2011 Timo Teräs <timo.teras@iki.fi>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation. See http://www.gnu.org/ for details.
+ */
+
+#include <stdio.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <utime.h>
+#include <string.h>
+#include <unistd.h>
+#include <sysexits.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/xattr.h>
+#include <sys/sysmacros.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "apk_defines.h"
+#include "apk_print.h"
+#include "apk_archive.h"
+#include "apk_openssl.h"
+
+struct tar_header {
+ /* ustar header, Posix 1003.1 */
+ char name[100]; /* 0-99 */
+ char mode[8]; /* 100-107 */
+ char uid[8]; /* 108-115 */
+ char gid[8]; /* 116-123 */
+ char size[12]; /* 124-135 */
+ char mtime[12]; /* 136-147 */
+ char chksum[8]; /* 148-155 */
+ char typeflag; /* 156-156 */
+ char linkname[100]; /* 157-256 */
+ char magic[8]; /* 257-264 */
+ char uname[32]; /* 265-296 */
+ char gname[32]; /* 297-328 */
+ char devmajor[8]; /* 329-336 */
+ char devminor[8]; /* 337-344 */
+ char prefix[155]; /* 345-499 */
+ char padding[12]; /* 500-511 */
+};
+
+#define GET_OCTAL(s) get_octal(s, sizeof(s))
+#define PUT_OCTAL(s,v) put_octal(s, sizeof(s), v)
+
+static unsigned int get_octal(char *s, size_t l)
+{
+ apk_blob_t b = APK_BLOB_PTR_LEN(s, l);
+ return apk_blob_pull_uint(&b, 8);
+}
+
+static void put_octal(char *s, size_t l, size_t value)
+{
+ char *ptr = &s[l - 1];
+
+ *(ptr--) = '\0';
+ while (value != 0 && ptr >= s) {
+ *(ptr--) = '0' + (value % 8);
+ value /= 8;
+ }
+ while (ptr >= s)
+ *(ptr--) = '0';
+}
+
+static int blob_realloc(apk_blob_t *b, size_t newsize)
+{
+ char *tmp;
+ if (b->len >= newsize) return 0;
+ tmp = realloc(b->ptr, newsize);
+ if (!tmp) return -ENOMEM;
+ b->ptr = tmp;
+ b->len = newsize;
+ return 0;
+}
+
+static void handle_extended_header(struct apk_file_info *fi, apk_blob_t hdr)
+{
+ apk_blob_t name, value;
+
+ while (1) {
+ char *start = hdr.ptr;
+ unsigned int len = apk_blob_pull_uint(&hdr, 10);
+ apk_blob_pull_char(&hdr, ' ');
+ if (!apk_blob_split(hdr, APK_BLOB_STR("="), &name, &hdr)) break;
+ if (len < hdr.ptr - start + 1) break;
+ len -= hdr.ptr - start + 1;
+ if (hdr.len < len) break;
+ value = APK_BLOB_PTR_LEN(hdr.ptr, len);
+ hdr = APK_BLOB_PTR_LEN(hdr.ptr+len, hdr.len-len);
+ apk_blob_pull_char(&hdr, '\n');
+ if (APK_BLOB_IS_NULL(hdr)) break;
+ value.ptr[value.len] = 0;
+
+ if (apk_blob_compare(name, APK_BLOB_STR("path")) == 0) {
+ fi->name = value.ptr;
+ } else if (apk_blob_compare(name, APK_BLOB_STR("linkpath")) == 0) {
+ fi->link_target = value.ptr;
+ } else if (apk_blob_pull_blob_match(&name, APK_BLOB_STR("SCHILY.xattr."))) {
+ name.ptr[name.len] = 0;
+ *apk_xattr_array_add(&fi->xattrs) = (struct apk_xattr) {
+ .name = name.ptr,
+ .value = value,
+ };
+ } else if (apk_blob_pull_blob_match(&name, APK_BLOB_STR("APK-TOOLS.checksum."))) {
+ int type = APK_CHECKSUM_NONE;
+ if (apk_blob_compare(name, APK_BLOB_STR("SHA1")) == 0)
+ type = APK_CHECKSUM_SHA1;
+ else if (apk_blob_compare(name, APK_BLOB_STR("MD5")) == 0)
+ type = APK_CHECKSUM_MD5;
+ if (type > fi->csum.type) {
+ fi->csum.type = type;
+ apk_blob_pull_hexdump(&value, APK_BLOB_CSUM(fi->csum));
+ if (APK_BLOB_IS_NULL(value)) fi->csum.type = APK_CHECKSUM_NONE;
+ }
+ }
+ }
+}
+
+int apk_tar_parse(struct apk_istream *is, apk_archive_entry_parser parser,
+ void *ctx, struct apk_id_cache *idc)
+{
+ struct apk_file_info entry;
+ struct apk_segment_istream segment;
+ struct tar_header buf;
+ int end = 0, r;
+ size_t toskip, paxlen = 0;
+ apk_blob_t pax = APK_BLOB_NULL, longname = APK_BLOB_NULL;
+ char filename[sizeof buf.name + sizeof buf.prefix + 2];
+
+ if (IS_ERR_OR_NULL(is)) return PTR_ERR(is) ?: -EINVAL;
+
+ memset(&entry, 0, sizeof(entry));
+ entry.name = buf.name;
+ while ((r = apk_istream_read(is, &buf, 512)) == 512) {
+ if (buf.name[0] == '\0') {
+ if (end) break;
+ end++;
+ continue;
+ }
+
+ entry = (struct apk_file_info){
+ .size = GET_OCTAL(buf.size),
+ .uid = apk_resolve_uid(idc, buf.uname, GET_OCTAL(buf.uid)),
+ .gid = apk_resolve_gid(idc, buf.gname, GET_OCTAL(buf.gid)),
+ .mode = GET_OCTAL(buf.mode) & 07777,
+ .mtime = GET_OCTAL(buf.mtime),
+ .name = entry.name,
+ .uname = buf.uname,
+ .gname = buf.gname,
+ .device = makedev(GET_OCTAL(buf.devmajor),
+ GET_OCTAL(buf.devminor)),
+ .xattrs = entry.xattrs,
+ };
+ if (buf.prefix[0] && buf.typeflag != 'x' && buf.typeflag != 'g') {
+ snprintf(filename, sizeof filename, "%.*s/%.*s",
+ (int) sizeof buf.prefix, buf.prefix,
+ (int) sizeof buf.name, buf.name);
+ entry.name = filename;
+ }
+ buf.mode[0] = 0; /* to nul terminate 100-byte buf.name */
+ buf.magic[0] = 0; /* to nul terminate 100-byte buf.linkname */
+ apk_xattr_array_resize(&entry.xattrs, 0);
+
+ if (entry.size >= SSIZE_MAX-512) goto err;
+
+ if (paxlen) {
+ handle_extended_header(&entry, APK_BLOB_PTR_LEN(pax.ptr, paxlen));
+ apk_fileinfo_hash_xattr(&entry);
+ }
+
+ toskip = (entry.size + 511) & -512;
+ switch (buf.typeflag) {
+ case 'L': /* GNU long name extension */
+ if ((r = blob_realloc(&longname, entry.size+1)) != 0 ||
+ (r = apk_istream_read(is, longname.ptr, entry.size)) != entry.size)
+ goto err;
+ entry.name = longname.ptr;
+ entry.name[entry.size] = 0;
+ toskip -= entry.size;
+ break;
+ case 'K': /* GNU long link target extension - ignored */
+ break;
+ case '0':
+ case '7': /* regular file */
+ entry.mode |= S_IFREG;
+ break;
+ case '1': /* hard link */
+ entry.mode |= S_IFREG;
+ if (!entry.link_target) entry.link_target = buf.linkname;
+ break;
+ case '2': /* symbolic link */
+ entry.mode |= S_IFLNK;
+ if (!entry.link_target) entry.link_target = buf.linkname;
+ break;
+ case '3': /* char device */
+ entry.mode |= S_IFCHR;
+ break;
+ case '4': /* block device */
+ entry.mode |= S_IFBLK;
+ break;
+ case '5': /* directory */
+ entry.mode |= S_IFDIR;
+ break;
+ case '6': /* fifo */
+ entry.mode |= S_IFIFO;
+ break;
+ case 'g': /* global pax header */
+ break;
+ case 'x': /* file specific pax header */
+ paxlen = entry.size;
+ if ((r = blob_realloc(&pax, (paxlen + 511) & -512)) != 0 ||
+ (r = apk_istream_read(is, pax.ptr, paxlen)) != paxlen)
+ goto err;
+ toskip -= entry.size;
+ break;
+ default:
+ break;
+ }
+
+ if (strnlen(entry.name, PATH_MAX) >= PATH_MAX-10 ||
+ (entry.link_target && strnlen(entry.link_target, PATH_MAX) >= PATH_MAX-10)) {
+ r = -ENAMETOOLONG;
+ goto err;
+ }
+
+ if (entry.mode & S_IFMT) {
+ apk_istream_segment(&segment, is, entry.size, entry.mtime);
+ r = parser(ctx, &entry, &segment.is);
+ if (r != 0) goto err;
+ apk_istream_close(&segment.is);
+
+ entry.name = buf.name;
+ toskip -= entry.size;
+ paxlen = 0;
+ }
+
+ if (toskip && (r = apk_istream_read(is, NULL, toskip)) != toskip)
+ goto err;
+ }
+
+ /* Read remaining end-of-archive records, to ensure we read all of
+ * the file. The underlying istream is likely doing checksumming. */
+ if (r == 512) {
+ while ((r = apk_istream_read(is, &buf, 512)) == 512) {
+ if (buf.name[0] != 0) break;
+ }
+ }
+ if (r == 0) goto ok;
+err:
+ /* Check that there was no partial (or non-zero) record */
+ if (r >= 0) r = -EBADMSG;
+ok:
+ free(pax.ptr);
+ free(longname.ptr);
+ apk_fileinfo_free(&entry);
+ apk_istream_close(is);
+ return r;
+}
+
+int apk_tar_write_entry(struct apk_ostream *os, const struct apk_file_info *ae,
+ const char *data)
+{
+ struct tar_header buf;
+
+ memset(&buf, 0, sizeof(buf));
+ if (ae != NULL) {
+ const unsigned char *src;
+ int chksum, i;
+
+ if (S_ISREG(ae->mode))
+ buf.typeflag = '0';
+ else
+ return -1;
+
+ if (ae->name != NULL)
+ strlcpy(buf.name, ae->name, sizeof buf.name);
+
+ strlcpy(buf.uname, ae->uname ?: "root", sizeof buf.uname);
+ strlcpy(buf.gname, ae->gname ?: "root", sizeof buf.gname);
+
+ PUT_OCTAL(buf.size, ae->size);
+ PUT_OCTAL(buf.uid, ae->uid);
+ PUT_OCTAL(buf.gid, ae->gid);
+ PUT_OCTAL(buf.mode, ae->mode & 07777);
+ PUT_OCTAL(buf.mtime, ae->mtime ?: time(NULL));
+
+ /* Checksum */
+ strcpy(buf.magic, "ustar ");
+ memset(buf.chksum, ' ', sizeof(buf.chksum));
+ src = (const unsigned char *) &buf;
+ for (i = chksum = 0; i < sizeof(buf); i++)
+ chksum += src[i];
+ put_octal(buf.chksum, sizeof(buf.chksum)-1, chksum);
+ }
+
+ if (apk_ostream_write(os, &buf, sizeof(buf)) != sizeof(buf))
+ return -1;
+
+ if (ae == NULL) {
+ /* End-of-archive is two empty headers */
+ if (apk_ostream_write(os, &buf, sizeof(buf)) != sizeof(buf))
+ return -1;
+ } else if (data != NULL) {
+ if (apk_ostream_write(os, data, ae->size) != ae->size)
+ return -1;
+ if (apk_tar_write_padding(os, ae) != 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+int apk_tar_write_padding(struct apk_ostream *os, const struct apk_file_info *ae)
+{
+ static char padding[512];
+ int pad;
+
+ pad = 512 - (ae->size & 511);
+ if (pad != 512 &&
+ apk_ostream_write(os, padding, pad) != pad)
+ return -1;
+
+ return 0;
+}
+
+int apk_archive_entry_extract(int atfd, const struct apk_file_info *ae,
+ const char *extract_name, const char *link_target,
+ struct apk_istream *is,
+ apk_progress_cb cb, void *cb_ctx)
+{
+ struct apk_xattr *xattr;
+ const char *fn = extract_name ?: ae->name;
+ int fd, r = -1, atflags = 0, ret = 0;
+
+ if (unlinkat(atfd, fn, 0) != 0 && errno != ENOENT) return -errno;
+
+ switch (ae->mode & S_IFMT) {
+ case S_IFDIR:
+ r = mkdirat(atfd, fn, ae->mode & 07777);
+ if (r < 0 && errno != EEXIST)
+ ret = -errno;
+ break;
+ case S_IFREG:
+ if (ae->link_target == NULL) {
+ int flags = O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC | O_EXCL;
+
+ fd = openat(atfd, fn, flags, ae->mode & 07777);
+ if (fd < 0) {
+ ret = -errno;
+ break;
+ }
+ r = apk_istream_splice(is, fd, ae->size, cb, cb_ctx);
+ if (r != ae->size) ret = r < 0 ? r : -ENOSPC;
+ close(fd);
+ } else {
+ r = linkat(atfd, link_target ?: ae->link_target, atfd, fn, 0);
+ if (r < 0) ret = -errno;
+ }
+ break;
+ case S_IFLNK:
+ r = symlinkat(link_target ?: ae->link_target, atfd, fn);
+ if (r < 0) ret = -errno;
+ atflags |= AT_SYMLINK_NOFOLLOW;
+ break;
+ case S_IFBLK:
+ case S_IFCHR:
+ case S_IFIFO:
+ r = mknodat(atfd, fn, ae->mode, ae->device);
+ if (r < 0) ret = -errno;
+ break;
+ }
+ if (ret) {
+ apk_error("Failed to create %s: %s", ae->name, strerror(-ret));
+ return ret;
+ }
+
+ r = fchownat(atfd, fn, ae->uid, ae->gid, atflags);
+ if (r < 0) {
+ apk_error("Failed to set ownership on %s: %s",
+ fn, strerror(errno));
+ if (!ret) ret = -errno;
+ }
+
+ /* chown resets suid bit so we need set it again */
+ if (ae->mode & 07000) {
+ r = fchmodat(atfd, fn, ae->mode & 07777, atflags);
+ if (r < 0) {
+ apk_error("Failed to set file permissions "
+ "on %s: %s",
+ fn, strerror(errno));
+ if (!ret) ret = -errno;
+ }
+ }
+
+ /* extract xattrs */
+ if (!S_ISLNK(ae->mode) && ae->xattrs && ae->xattrs->num) {
+ r = 0;
+ fd = openat(atfd, fn, O_RDWR);
+ if (fd >= 0) {
+ foreach_array_item(xattr, ae->xattrs) {
+ if (fsetxattr(fd, xattr->name, xattr->value.ptr, xattr->value.len, 0) < 0) {
+ r = -errno;
+ if (r != -ENOTSUP) break;
+ }
+ }
+ close(fd);
+ } else {
+ r = -errno;
+ }
+ if (r) {
+ if (r != -ENOTSUP)
+ apk_error("Failed to set xattrs on %s: %s",
+ fn, strerror(-r));
+ if (!ret) ret = r;
+ }
+ }
+
+ if (!S_ISLNK(ae->mode)) {
+ /* preserve modification time */
+ struct timespec times[2];
+
+ times[0].tv_sec = times[1].tv_sec = ae->mtime;
+ times[0].tv_nsec = times[1].tv_nsec = 0;
+ r = utimensat(atfd, fn, times, atflags);
+ if (r < 0) {
+ apk_error("Failed to preserve modification time on %s: %s",
+ fn, strerror(errno));
+ if (!ret || ret == -ENOTSUP) ret = -errno;
+ }
+ }
+
+ return ret;
+}