cpio: synthesize newc inode values

The newc format stores only 32-bit inode values. Assign unique
synthetic values from each full device and inode identity. This avoids
collisions after truncation while preserving hardlink groups.

Fixes #3314.
This commit is contained in:
Darren Carreras authored and Artemis committed 2026-08-10 09:40:05 -04:00
1 parent 48fc018080
commit 1803922e49
1 file changed
+98 -6
@@ -41,6 +41,7 @@
#include "archive.h"
#include "archive_entry.h"
#include "archive_entry_locale.h"
#include "archive_integer.h"
#include "archive_private.h"
#include "archive_write_private.h"
#include "archive_write_set_format_private.h"
@@ -62,6 +63,17 @@ struct cpio {
uint64_t entry_bytes_remaining;
int padding;
int64_t ino_next;
struct {
int64_t old_devmajor;
int64_t old_devminor;
int64_t old_ino;
int64_t new_ino;
} *ino_list;
size_t ino_list_size;
size_t ino_list_next;
struct archive_string_conv *opt_sconv;
struct archive_string_conv *sconv_default;
int init_default_conversion;
@@ -162,6 +174,86 @@ archive_write_newc_options(struct archive_write *a, const char *key,
return (ARCHIVE_WARN);
}
/*
* 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)
{
@@ -267,15 +359,14 @@ write_header(struct archive_write *a, struct archive_entry *entry)
format_hex(archive_entry_devminor(entry), h + c_devminor_offset,
c_devminor_size);
ino = archive_entry_ino64(entry);
if (ino > 0xffffffff) {
archive_set_error(&a->archive, ERANGE,
"large inode number truncated");
ret_final = ARCHIVE_WARN;
ino = synthesize_ino_value(a, entry);
if (ino < 0) {
ret_final = (int)ino;
goto exit_write_header;
}
/* TODO: Set ret_final to ARCHIVE_WARN if any of these overflow. */
format_hex(ino & 0xffffffff, h + c_ino_offset, c_ino_size);
format_hex(ino, h + c_ino_offset, c_ino_size);
format_hex(archive_entry_mode(entry), h + c_mode_offset, c_mode_size);
format_hex(archive_entry_uid(entry), h + c_uid_offset, c_uid_size);
format_hex(archive_entry_gid(entry), h + c_gid_offset, c_gid_size);
@@ -442,6 +533,7 @@ archive_write_newc_free(struct archive_write *a)
{
struct cpio *cpio = a->format_data;
free(cpio->ino_list);
free(cpio);
a->format_data = NULL;
return (ARCHIVE_OK);