Merge pull request #3321 from carrerasdarren-cell/fix/cpio-newc-inode-synthesis

cpio: synthesize newc inode values
This commit is contained in:
Tim Kientzle authored and GitHub committed 2026-08-10 06:58:16 -07:00
commit 6ea9e10bc5
3 files changed
+163 -44

No files matched your search

+2 -38
View File
@@ -113,42 +113,6 @@ DEFINE_TEST(test_format_newc)
/* Setup result message. */ /* Setup result message. */
memset(result, 0, sizeof(result)); memset(result, 0, sizeof(result));
if (is_LargeInode("file1")) {
strncat(result,
"bsdcpio: file1: large inode number truncated: ",
sizeof(result) - strlen(result) -1);
strncat(result, strerror(ERANGE),
sizeof(result) - strlen(result) -1);
strncat(result, "\n",
sizeof(result) - strlen(result) -1);
}
if (canSymlink() && is_LargeInode("symlink")) {
strncat(result,
"bsdcpio: symlink: large inode number truncated: ",
sizeof(result) - strlen(result) -1);
strncat(result, strerror(ERANGE),
sizeof(result) - strlen(result) -1);
strncat(result, "\n",
sizeof(result) - strlen(result) -1);
}
if (is_LargeInode("dir")) {
strncat(result,
"bsdcpio: dir: large inode number truncated: ",
sizeof(result) - strlen(result) -1);
strncat(result, strerror(ERANGE),
sizeof(result) - strlen(result) -1);
strncat(result, "\n",
sizeof(result) - strlen(result) -1);
}
if (is_LargeInode("hardlink")) {
strncat(result,
"bsdcpio: hardlink: large inode number truncated: ",
sizeof(result) - strlen(result) -1);
strncat(result, strerror(ERANGE),
sizeof(result) - strlen(result) -1);
strncat(result, "\n",
sizeof(result) - strlen(result) -1);
}
/* Record some facts about what we just created: */ /* Record some facts about what we just created: */
now = time(NULL); /* They were all created w/in last two seconds. */ now = time(NULL); /* They were all created w/in last two seconds. */
@@ -225,7 +189,7 @@ DEFINE_TEST(test_format_newc)
/* "symlink" pointing to "file1" */ /* "symlink" pointing to "file1" */
assert(is_hex(e, 110)); assert(is_hex(e, 110));
assertEqualMem(e + 0, "070701", 6); /* Magic */ assertEqualMem(e + 0, "070701", 6); /* Magic */
assert(is_hex(e + 6, 8)); /* ino */ assert(ino != from_hex(e + 6, 8)); /* ino */
#if defined(_WIN32) && !defined(__CYGWIN__) #if defined(_WIN32) && !defined(__CYGWIN__)
/* Mode: Group members bits and others bits do not work. */ /* Mode: Group members bits and others bits do not work. */
assertEqualInt(0xa180, from_hex(e + 14, 8) & 0xffc0); assertEqualInt(0xa180, from_hex(e + 14, 8) & 0xffc0);
@@ -258,7 +222,7 @@ DEFINE_TEST(test_format_newc)
/* "dir" */ /* "dir" */
assert(is_hex(e, 110)); assert(is_hex(e, 110));
assertEqualMem(e + 0, "070701", 6); /* Magic */ assertEqualMem(e + 0, "070701", 6); /* Magic */
assert(is_hex(e + 6, 8)); /* ino */ assert(ino != from_hex(e + 6, 8)); /* ino */
#if defined(_WIN32) && !defined(__CYGWIN__) #if defined(_WIN32) && !defined(__CYGWIN__)
/* Group members bits and others bits do not work. */ /* Group members bits and others bits do not work. */
assertEqualInt(0x41c0, from_hex(e + 14, 8) & 0xffc0); /* Mode */ assertEqualInt(0x41c0, from_hex(e + 14, 8) & 0xffc0); /* Mode */
@@ -41,6 +41,7 @@
#include "archive.h" #include "archive.h"
#include "archive_entry.h" #include "archive_entry.h"
#include "archive_entry_locale.h" #include "archive_entry_locale.h"
#include "archive_integer.h"
#include "archive_private.h" #include "archive_private.h"
#include "archive_write_private.h" #include "archive_write_private.h"
#include "archive_write_set_format_private.h" #include "archive_write_set_format_private.h"
@@ -62,6 +63,17 @@ struct cpio {
uint64_t entry_bytes_remaining; uint64_t entry_bytes_remaining;
int padding; 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 *opt_sconv;
struct archive_string_conv *sconv_default; struct archive_string_conv *sconv_default;
int init_default_conversion; int init_default_conversion;
@@ -142,6 +154,86 @@ archive_write_newc_options(struct archive_write *a, const char *key,
&cpio->opt_sconv)); &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 * static struct archive_string_conv *
get_sconv(struct archive_write *a) get_sconv(struct archive_write *a)
{ {
@@ -247,15 +339,14 @@ write_header(struct archive_write *a, struct archive_entry *entry)
format_hex(archive_entry_devminor(entry), h + c_devminor_offset, format_hex(archive_entry_devminor(entry), h + c_devminor_offset,
c_devminor_size); c_devminor_size);
ino = archive_entry_ino64(entry); ino = synthesize_ino_value(a, entry);
if (ino > 0xffffffff) { if (ino < 0) {
archive_set_error(&a->archive, ERANGE, ret_final = (int)ino;
"large inode number truncated"); goto exit_write_header;
ret_final = ARCHIVE_WARN;
} }
/* TODO: Set ret_final to ARCHIVE_WARN if any of these overflow. */ /* 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_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_uid(entry), h + c_uid_offset, c_uid_size);
format_hex(archive_entry_gid(entry), h + c_gid_offset, c_gid_size); format_hex(archive_entry_gid(entry), h + c_gid_offset, c_gid_size);
@@ -422,6 +513,7 @@ archive_write_newc_free(struct archive_write *a)
{ {
struct cpio *cpio = a->format_data; struct cpio *cpio = a->format_data;
free(cpio->ino_list);
free(cpio); free(cpio);
a->format_data = NULL; a->format_data = NULL;
return (ARCHIVE_OK); return (ARCHIVE_OK);
@@ -42,6 +42,24 @@ is_hex(const char *p, size_t l)
return (1); return (1);
} }
static void
write_newc_entry(struct archive *a, const char *path, int64_t dev, int64_t ino,
int nlink)
{
struct archive_entry *entry;
assert((entry = archive_entry_new()) != NULL);
archive_entry_set_pathname(entry, path);
archive_entry_set_mode(entry, AE_IFREG | 0644);
archive_entry_set_size(entry, 0);
archive_entry_set_dev(entry, dev);
archive_entry_set_ino64(entry, ino);
archive_entry_set_nlink(entry, nlink);
assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, entry));
assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
archive_entry_free(entry);
}
/* /*
* Detailed verification that cpio 'newc' archives are written with * Detailed verification that cpio 'newc' archives are written with
* the correct format. * the correct format.
@@ -205,3 +223,48 @@ DEFINE_TEST(test_write_format_cpio_newc)
free(buff); free(buff);
} }
DEFINE_TEST(test_write_format_cpio_newc_large_inode)
{
struct archive *a;
char *buff;
const char *first, *again, *other, *small_inode, *different_device;
size_t buffsize = 10000;
size_t used;
buff = malloc(buffsize);
assert(buff != NULL);
assert((a = archive_write_new()) != NULL);
assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_cpio_newc(a));
assertEqualIntA(a, ARCHIVE_OK, archive_write_add_filter_none(a));
assertEqualIntA(a, ARCHIVE_OK,
archive_write_open_memory(a, buff, buffsize, &used));
write_newc_entry(a, "first", 12, INT64_C(0x100000001), 2);
write_newc_entry(a, "again", 12, INT64_C(0x100000001), 2);
write_newc_entry(a, "other", 12, INT64_C(0x200000001), 2);
write_newc_entry(a, "small", 12, 1, 1);
write_newc_entry(a, "dev13", 13, INT64_C(0x100000001), 2);
assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
assertEqualInt(ARCHIVE_OK, archive_write_free(a));
/* Each entry is a 110-byte header plus a padded six-byte name. */
first = buff + 6;
again = buff + 116 + 6;
other = buff + 232 + 6;
small_inode = buff + 348 + 6;
different_device = buff + 464 + 6;
assertEqualMem(first, again, 8);
assert(memcmp(first, other, 8) != 0);
assert(memcmp(first, small_inode, 8) != 0);
assert(memcmp(first, different_device, 8) != 0);
assert(memcmp(other, small_inode, 8) != 0);
assert(memcmp(other, different_device, 8) != 0);
assert(memcmp(small_inode, different_device, 8) != 0);
assert(memcmp(first, "00000000", 8) != 0);
assert(memcmp(other, "00000000", 8) != 0);
assert(memcmp(small_inode, "00000000", 8) != 0);
assert(memcmp(different_device, "00000000", 8) != 0);
free(buff);
}