/* tar.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 "apk_defines.h" #include "apk_tar.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 TAR_BLOB(s) APK_BLOB_PTR_LEN(s, strnlen(s, sizeof(s))) #define GET_OCTAL(s,r) get_octal(s, sizeof(s), r) #define PUT_OCTAL(s,v,hz) put_octal(s, sizeof(s), v, hz) static unsigned int get_octal(char *s, size_t l, int *r) { apk_blob_t b = APK_BLOB_PTR_LEN(s, l); unsigned int val = apk_blob_pull_uint(&b, 8); while (b.len >= 1 && (b.ptr[0] == 0 || b.ptr[0] == 0x20)) b.ptr++, b.len--; if (b.len != 0) *r = -APKE_V2PKG_FORMAT; return val; } static void put_octal(char *s, size_t l, size_t value, int has_zero) { char *ptr = &s[l - 1]; if (has_zero) *(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 alg = APK_DIGEST_NONE; if (apk_blob_compare(name, APK_BLOB_STR("SHA1")) == 0) alg = APK_DIGEST_SHA1; else if (apk_blob_compare(name, APK_BLOB_STR("MD5")) == 0) alg = APK_DIGEST_MD5; if (alg > fi->digest.alg) { apk_digest_set(&fi->digest, alg); apk_blob_pull_hexdump(&value, APK_DIGEST_BLOB(fi->digest)); if (APK_BLOB_IS_NULL(value)) apk_digest_reset(&fi->digest); } } } } 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(is)) return PTR_ERR(is); memset(&entry, 0, sizeof(entry)); entry.name = buf.name; while ((r = apk_istream_read_max(is, &buf, 512)) == 512) { if (buf.name[0] == '\0') { if (end) break; end++; continue; } if (memcmp(buf.magic, "ustar", 5) != 0) { r = -APKE_V2PKG_FORMAT; goto err; } r = 0; entry = (struct apk_file_info){ .size = GET_OCTAL(buf.size, &r), .uid = apk_id_cache_resolve_uid(idc, TAR_BLOB(buf.uname), GET_OCTAL(buf.uid, &r)), .gid = apk_id_cache_resolve_gid(idc, TAR_BLOB(buf.gname), GET_OCTAL(buf.gid, &r)), .mode = GET_OCTAL(buf.mode, &r) & 07777, .mtime = GET_OCTAL(buf.mtime, &r), .name = entry.name, .uname = buf.uname, .gname = buf.gname, .device = makedev(GET_OCTAL(buf.devmajor, &r), GET_OCTAL(buf.devminor, &r)), .xattrs = entry.xattrs, }; if (r != 0) goto err; 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, APK_DIGEST_SHA1); } 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)) < 0) goto err; longname.ptr[entry.size] = 0; entry.name = longname.ptr; 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)) < 0) 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)) < 0) 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_max(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 = -APKE_EOF; ok: free(pax.ptr); free(longname.ptr); apk_fileinfo_free(&entry); return apk_istream_close_error(is, 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, 0); PUT_OCTAL(buf.uid, ae->uid, 1); PUT_OCTAL(buf.gid, ae->gid, 1); PUT_OCTAL(buf.mode, ae->mode & 07777, 1); PUT_OCTAL(buf.mtime, ae->mtime ?: apk_get_build_time(), 0); /* 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, 1); } if (apk_ostream_write(os, &buf, sizeof(buf)) < 0) return -1; if (ae == NULL) { /* End-of-archive is two empty headers */ if (apk_ostream_write(os, &buf, sizeof(buf)) < 0) return -1; } else if (data != NULL) { if (apk_ostream_write(os, data, ae->size) < 0) 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) < 0) return -1; return 0; }