tar-util: properly deal with sparse files
The extractor already deals with sparse files properly (because archive_read_data_into_fd() does). Let's also make sure the archiver also does this, and attaches the necessary sparse file metadata to each file.
This commit is contained in:
@@ -37,6 +37,7 @@ DLSYM_PROTOTYPE(archive_entry_set_rdevminor) = NULL;
|
||||
DLSYM_PROTOTYPE(archive_entry_set_size) = NULL;
|
||||
DLSYM_PROTOTYPE(archive_entry_set_symlink) = NULL;
|
||||
DLSYM_PROTOTYPE(archive_entry_set_uid) = NULL;
|
||||
DLSYM_PROTOTYPE(archive_entry_sparse_add_entry) = NULL;
|
||||
DLSYM_PROTOTYPE(archive_entry_symlink) = NULL;
|
||||
DLSYM_PROTOTYPE(archive_entry_uid) = NULL;
|
||||
#if HAVE_LIBARCHIVE_UID_IS_SET
|
||||
@@ -61,7 +62,7 @@ DLSYM_PROTOTYPE(archive_write_new) = NULL;
|
||||
DLSYM_PROTOTYPE(archive_write_open_FILE) = NULL;
|
||||
DLSYM_PROTOTYPE(archive_write_open_fd) = NULL;
|
||||
DLSYM_PROTOTYPE(archive_write_set_format_filter_by_ext) = NULL;
|
||||
DLSYM_PROTOTYPE(archive_write_set_format_gnutar) = NULL;
|
||||
DLSYM_PROTOTYPE(archive_write_set_format_pax) = NULL;
|
||||
|
||||
int dlopen_libarchive(void) {
|
||||
ELF_NOTE_DLOPEN("archive",
|
||||
@@ -103,6 +104,7 @@ int dlopen_libarchive(void) {
|
||||
DLSYM_ARG(archive_entry_set_size),
|
||||
DLSYM_ARG(archive_entry_set_symlink),
|
||||
DLSYM_ARG(archive_entry_set_uid),
|
||||
DLSYM_ARG(archive_entry_sparse_add_entry),
|
||||
DLSYM_ARG(archive_entry_symlink),
|
||||
DLSYM_ARG(archive_entry_uid),
|
||||
#if HAVE_LIBARCHIVE_UID_IS_SET
|
||||
@@ -127,8 +129,7 @@ int dlopen_libarchive(void) {
|
||||
DLSYM_ARG(archive_write_open_FILE),
|
||||
DLSYM_ARG(archive_write_open_fd),
|
||||
DLSYM_ARG(archive_write_set_format_filter_by_ext),
|
||||
DLSYM_ARG(archive_write_set_format_gnutar)
|
||||
);
|
||||
DLSYM_ARG(archive_write_set_format_pax));
|
||||
}
|
||||
|
||||
/* libarchive uses its own file type macros. They happen to be defined the same way as the Linux ones, and
|
||||
|
||||
@@ -33,6 +33,7 @@ extern DLSYM_PROTOTYPE(archive_entry_set_rdevminor);
|
||||
extern DLSYM_PROTOTYPE(archive_entry_set_size);
|
||||
extern DLSYM_PROTOTYPE(archive_entry_set_symlink);
|
||||
extern DLSYM_PROTOTYPE(archive_entry_set_uid);
|
||||
extern DLSYM_PROTOTYPE(archive_entry_sparse_add_entry);
|
||||
extern DLSYM_PROTOTYPE(archive_entry_symlink);
|
||||
extern DLSYM_PROTOTYPE(archive_entry_uid);
|
||||
extern DLSYM_PROTOTYPE(archive_entry_xattr_add_entry);
|
||||
@@ -54,7 +55,7 @@ extern DLSYM_PROTOTYPE(archive_write_new);
|
||||
extern DLSYM_PROTOTYPE(archive_write_open_FILE);
|
||||
extern DLSYM_PROTOTYPE(archive_write_open_fd);
|
||||
extern DLSYM_PROTOTYPE(archive_write_set_format_filter_by_ext);
|
||||
extern DLSYM_PROTOTYPE(archive_write_set_format_gnutar);
|
||||
extern DLSYM_PROTOTYPE(archive_write_set_format_pax);
|
||||
|
||||
#if HAVE_LIBARCHIVE_UID_IS_SET
|
||||
extern DLSYM_PROTOTYPE(archive_entry_gid_is_set);
|
||||
|
||||
+65
-7
@@ -804,6 +804,57 @@ bypass:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int archive_generate_sparse(struct archive_entry *entry, int fd) {
|
||||
assert(entry);
|
||||
assert(fd);
|
||||
|
||||
off_t c = 0;
|
||||
for (;;) {
|
||||
/* Look for the next hole */
|
||||
off_t h = lseek(fd, c, SEEK_HOLE);
|
||||
if (h < 0) {
|
||||
if (errno != ENXIO)
|
||||
return log_error_errno(errno, "Failed to issue SEEK_HOLE: %m");
|
||||
|
||||
/* If errno == ENXIO, that means we've reached the final data of the file and
|
||||
* that data isn't followed by anything more */
|
||||
|
||||
/* Figure out where the end of the file is */
|
||||
off_t e = lseek(fd, 0, SEEK_END);
|
||||
if (e < 0)
|
||||
return log_error_errno(errno, "Failed to issue SEEK_END: %m");
|
||||
|
||||
/* Generate sparse entry for final block */
|
||||
if (e > c && c != 0) {
|
||||
log_debug("final sparse block %" PRIu64 "…%" PRIu64, (uint64_t) c, (uint64_t) e);
|
||||
sym_archive_entry_sparse_add_entry(entry, c, e - c);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (h > c) {
|
||||
log_debug("inner sparse block %" PRIu64 "…%" PRIu64 " (%" PRIu64 ")", (uint64_t) c, (uint64_t) h, (uint64_t) h - (uint64_t) c);
|
||||
sym_archive_entry_sparse_add_entry(entry, c, h - c);
|
||||
}
|
||||
|
||||
/* Now look for the next data after the hole */
|
||||
c = lseek(fd, h, SEEK_DATA);
|
||||
if (c < 0) {
|
||||
if (errno != ENXIO)
|
||||
return log_error_errno(errno, "Failed to issue SEEK_DATA: %m");
|
||||
|
||||
/* No data anymore */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lseek(fd, 0, SEEK_SET) < 0)
|
||||
return log_error_errno(errno, "Failed to reset seek offset: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int archive_item(
|
||||
RecurseDirEvent event,
|
||||
const char *path,
|
||||
@@ -912,16 +963,23 @@ static int archive_item(
|
||||
sym_archive_entry_xattr_add_entry(entry, xa, buf, size);
|
||||
}
|
||||
|
||||
_cleanup_close_ int data_fd = -EBADF;
|
||||
if (S_ISREG(sx->stx_mode)) {
|
||||
/* Convert the O_PATH fd into a proper fd */
|
||||
data_fd = fd_reopen(inode_fd, O_RDONLY|O_CLOEXEC);
|
||||
if (data_fd < 0)
|
||||
return log_error_errno(data_fd, "Failed to open '%s': %m", path);
|
||||
|
||||
r = archive_generate_sparse(entry, data_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (sym_archive_write_header(d->archive, entry) != ARCHIVE_OK)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(d->archive));
|
||||
|
||||
if (S_ISREG(sx->stx_mode)) {
|
||||
_cleanup_close_ int data_fd = -EBADF;
|
||||
|
||||
/* Convert the O_PATH fd in a proper fd */
|
||||
data_fd = fd_reopen(inode_fd, O_RDONLY|O_CLOEXEC);
|
||||
if (data_fd < 0)
|
||||
return log_error_errno(data_fd, "Failed to open '%s': %m", path);
|
||||
assert(data_fd >= 0);
|
||||
|
||||
for (;;) {
|
||||
char buffer[64*1024];
|
||||
@@ -965,7 +1023,7 @@ int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) {
|
||||
if (filename)
|
||||
r = sym_archive_write_set_format_filter_by_ext(a, filename);
|
||||
else
|
||||
r = sym_archive_write_set_format_gnutar(a);
|
||||
r = sym_archive_write_set_format_pax(a);
|
||||
if (r != ARCHIVE_OK)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output format: %s", sym_archive_error_string(a));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user