/* io_archive.c - Alpine Package Keeper (APK) * * Copyright (C) 2005-2008 Natanael Copa * Copyright (C) 2008-2011 Timo Teräs * All rights reserved. * * SPDX-License-Identifier: GPL-2.0-only */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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, unsigned int apk_extract_flags, struct apk_out *out) { 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_err(out, "Failed to create %s: %s", ae->name, strerror(-ret)); return ret; } if (!(apk_extract_flags & APK_EXTRACTF_NO_CHOWN)) { r = fchownat(atfd, fn, ae->uid, ae->gid, atflags); if (r < 0) { apk_err(out, "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_err(out, "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_err(out, "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_err(out, "Failed to preserve modification time on %s: %s", fn, strerror(errno)); if (!ret || ret == -ENOTSUP) ret = -errno; } } return ret; }