From: Tim Kientzle Date: Mon, 10 Aug 2026 13:58:16 +0000 (-0700) Subject: Merge pull request #3321 from carrerasdarren-cell/fix/cpio-newc-inode-synthesis X-Git-Url: http://git.ipfire.org/index.cgi?a=commitdiff_plain;p=thirdparty%2Flibarchive.git Merge pull request #3321 from carrerasdarren-cell/fix/cpio-newc-inode-synthesis cpio: synthesize newc inode values --- 6ea9e10bc535dfb22807319d0d7f2e1d921599e8 diff --cc libarchive/archive_write_set_format_cpio_newc.c index 069dbc184,710a68659..9f6c88edd --- a/libarchive/archive_write_set_format_cpio_newc.c +++ b/libarchive/archive_write_set_format_cpio_newc.c @@@ -138,10 -150,110 +150,90 @@@ archive_write_newc_options(struct archi const char *val) { struct cpio *cpio = a->format_data; - int ret = ARCHIVE_FAILED; - - if (strcmp(key, "hdrcharset") == 0) { - if (val == NULL || val[0] == 0) - archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, - "%s: hdrcharset option needs a character-set name", - a->format_name); - else { - cpio->opt_sconv = archive_string_conversion_to_charset( - &a->archive, val, 0); - if (cpio->opt_sconv != NULL) - ret = ARCHIVE_OK; - else - ret = ARCHIVE_FATAL; - } - return (ret); - } - - /* Note: The "warn" return is just to inform the options - * supervisor that we didn't handle it. It will generate - * a suitable error if no one used this option. */ - return (ARCHIVE_WARN); + return (__archive_write_option_header_charset(a, key, val, + &cpio->opt_sconv)); } + /* + * The newc format stores 32-bit inode numbers and relies on them to identify + * hardlinked files. Generate unique in-range values so distinct 64-bit inode + * numbers cannot collide when written to the archive. + */ + static int64_t + synthesize_ino_value(struct archive_write *a, struct archive_entry *entry) + { + struct cpio *cpio = a->format_data; + int64_t devmajor = archive_entry_devmajor(entry); + int64_t devminor = archive_entry_devminor(entry); + int64_t ino = archive_entry_ino64(entry); + int64_t ino_new; + size_t i; + + if (ino == 0) + return (0); + + /* Directory link counts do not represent separate archive entries. */ + if (archive_entry_nlink(entry) < 2 || + archive_entry_filetype(entry) == AE_IFDIR) { + if (cpio->ino_next == UINT32_MAX) { + archive_set_error(&a->archive, ERANGE, + "No available inode values for cpio format"); + return (ARCHIVE_FATAL); + } + return (++cpio->ino_next); + } + + /* TODO: Revisit the flat list if large hardlink sets become slow. */ + for (i = 0; i < cpio->ino_list_next; ++i) { + if (cpio->ino_list[i].old_devmajor == devmajor && + cpio->ino_list[i].old_devminor == devminor && + cpio->ino_list[i].old_ino == ino) + return (cpio->ino_list[i].new_ino); + } + + if (cpio->ino_next == UINT32_MAX) { + archive_set_error(&a->archive, ERANGE, + "No available inode values for cpio format"); + return (ARCHIVE_FATAL); + } + ino_new = ++cpio->ino_next; + + if (cpio->ino_list_size <= cpio->ino_list_next) { + size_t newsize, size; + + if (cpio->ino_list_size < 512) + newsize = 512; + else if (archive_ckd_mul_size(&newsize, + cpio->ino_list_size, 2)) { + archive_set_error(&a->archive, ENOMEM, + "No memory for inode translation table"); + return (ARCHIVE_FATAL); + } + if (archive_ckd_mul_size(&size, + newsize, sizeof(cpio->ino_list[0]))) { + archive_set_error(&a->archive, ENOMEM, + "No memory for inode translation table"); + return (ARCHIVE_FATAL); + } + void *newlist = realloc(cpio->ino_list, size); + if (newlist == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for inode translation table"); + return (ARCHIVE_FATAL); + } + + cpio->ino_list_size = newsize; + cpio->ino_list = newlist; + } + + cpio->ino_list[cpio->ino_list_next].old_devmajor = devmajor; + cpio->ino_list[cpio->ino_list_next].old_devminor = devminor; + cpio->ino_list[cpio->ino_list_next].old_ino = ino; + cpio->ino_list[cpio->ino_list_next].new_ino = ino_new; + ++cpio->ino_list_next; + return (ino_new); + } + static struct archive_string_conv * get_sconv(struct archive_write *a) {