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:
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);
|
||||
|
||||
Reference in new issue
Block a user