-1 was used everywhere, but -EBADF or -EBADFD started being used in various places. Let's make things consistent in the new style. Note that there are two candidates: EBADF 9 Bad file descriptor EBADFD 77 File descriptor in bad state Since we're initializating the fd, we're just assigning a value that means "no fd yet", so it's just a bad file descriptor, and the first errno fits better. If instead we had a valid file descriptor that became invalid because of some operation or state change, the other errno would fit better. In some places, initialization is dropped if unnecessary.
530 lines
16 KiB
C
530 lines
16 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include <linux/fs.h>
|
|
|
|
#include "sd-daemon.h"
|
|
#include "sd-event.h"
|
|
|
|
#include "alloc-util.h"
|
|
#include "btrfs-util.h"
|
|
#include "copy.h"
|
|
#include "fd-util.h"
|
|
#include "format-util.h"
|
|
#include "fs-util.h"
|
|
#include "hostname-util.h"
|
|
#include "import-common.h"
|
|
#include "import-compress.h"
|
|
#include "import-raw.h"
|
|
#include "install-file.h"
|
|
#include "io-util.h"
|
|
#include "machine-pool.h"
|
|
#include "mkdir-label.h"
|
|
#include "path-util.h"
|
|
#include "qcow2-util.h"
|
|
#include "ratelimit.h"
|
|
#include "rm-rf.h"
|
|
#include "string-util.h"
|
|
#include "tmpfile-util.h"
|
|
|
|
struct RawImport {
|
|
sd_event *event;
|
|
|
|
char *image_root;
|
|
|
|
RawImportFinished on_finished;
|
|
void *userdata;
|
|
|
|
char *local;
|
|
ImportFlags flags;
|
|
|
|
char *temp_path;
|
|
char *final_path;
|
|
|
|
int input_fd;
|
|
int output_fd;
|
|
|
|
ImportCompress compress;
|
|
|
|
sd_event_source *input_event_source;
|
|
|
|
uint8_t buffer[16*1024];
|
|
size_t buffer_size;
|
|
|
|
uint64_t written_compressed;
|
|
uint64_t written_uncompressed;
|
|
|
|
struct stat input_stat;
|
|
struct stat output_stat;
|
|
|
|
unsigned last_percent;
|
|
RateLimit progress_ratelimit;
|
|
|
|
uint64_t offset;
|
|
uint64_t size_max;
|
|
};
|
|
|
|
RawImport* raw_import_unref(RawImport *i) {
|
|
if (!i)
|
|
return NULL;
|
|
|
|
sd_event_source_unref(i->input_event_source);
|
|
|
|
unlink_and_free(i->temp_path);
|
|
|
|
import_compress_free(&i->compress);
|
|
|
|
sd_event_unref(i->event);
|
|
|
|
safe_close(i->output_fd);
|
|
|
|
free(i->final_path);
|
|
free(i->image_root);
|
|
free(i->local);
|
|
return mfree(i);
|
|
}
|
|
|
|
int raw_import_new(
|
|
RawImport **ret,
|
|
sd_event *event,
|
|
const char *image_root,
|
|
RawImportFinished on_finished,
|
|
void *userdata) {
|
|
|
|
_cleanup_(raw_import_unrefp) RawImport *i = NULL;
|
|
_cleanup_free_ char *root = NULL;
|
|
int r;
|
|
|
|
assert(ret);
|
|
|
|
root = strdup(image_root ?: "/var/lib/machines");
|
|
if (!root)
|
|
return -ENOMEM;
|
|
|
|
i = new(RawImport, 1);
|
|
if (!i)
|
|
return -ENOMEM;
|
|
|
|
*i = (RawImport) {
|
|
.input_fd = -EBADF,
|
|
.output_fd = -EBADF,
|
|
.on_finished = on_finished,
|
|
.userdata = userdata,
|
|
.last_percent = UINT_MAX,
|
|
.image_root = TAKE_PTR(root),
|
|
.progress_ratelimit = { 100 * USEC_PER_MSEC, 1 },
|
|
.offset = UINT64_MAX,
|
|
.size_max = UINT64_MAX,
|
|
};
|
|
|
|
if (event)
|
|
i->event = sd_event_ref(event);
|
|
else {
|
|
r = sd_event_default(&i->event);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
*ret = TAKE_PTR(i);
|
|
return 0;
|
|
}
|
|
|
|
static void raw_import_report_progress(RawImport *i) {
|
|
unsigned percent;
|
|
assert(i);
|
|
|
|
/* We have no size information, unless the source is a regular file */
|
|
if (!S_ISREG(i->input_stat.st_mode))
|
|
return;
|
|
|
|
if (i->written_compressed >= (uint64_t) i->input_stat.st_size)
|
|
percent = 100;
|
|
else
|
|
percent = (unsigned) ((i->written_compressed * UINT64_C(100)) / (uint64_t) i->input_stat.st_size);
|
|
|
|
if (percent == i->last_percent)
|
|
return;
|
|
|
|
if (!ratelimit_below(&i->progress_ratelimit))
|
|
return;
|
|
|
|
sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
|
|
log_info("Imported %u%%.", percent);
|
|
|
|
i->last_percent = percent;
|
|
}
|
|
|
|
static int raw_import_maybe_convert_qcow2(RawImport *i) {
|
|
_cleanup_close_ int converted_fd = -EBADF;
|
|
_cleanup_(unlink_and_freep) char *t = NULL;
|
|
_cleanup_free_ char *f = NULL;
|
|
int r;
|
|
|
|
assert(i);
|
|
|
|
/* Do QCOW2 conversion if enabled and not in direct mode */
|
|
if ((i->flags & (IMPORT_CONVERT_QCOW2|IMPORT_DIRECT)) != IMPORT_CONVERT_QCOW2)
|
|
return 0;
|
|
|
|
assert(i->final_path);
|
|
|
|
r = qcow2_detect(i->output_fd);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m");
|
|
if (r == 0)
|
|
return 0;
|
|
|
|
/* This is a QCOW2 image, let's convert it */
|
|
r = tempfn_random(i->final_path, NULL, &f);
|
|
if (r < 0)
|
|
return log_oom();
|
|
|
|
converted_fd = open(f, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
|
|
if (converted_fd < 0)
|
|
return log_error_errno(errno, "Failed to create %s: %m", f);
|
|
|
|
t = TAKE_PTR(f);
|
|
|
|
(void) import_set_nocow_and_log(converted_fd, t);
|
|
|
|
log_info("Unpacking QCOW2 file.");
|
|
|
|
r = qcow2_convert(i->output_fd, converted_fd);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to convert qcow2 image: %m");
|
|
|
|
unlink_and_free(i->temp_path);
|
|
i->temp_path = TAKE_PTR(t);
|
|
close_and_replace(i->output_fd, converted_fd);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int raw_import_finish(RawImport *i) {
|
|
int r;
|
|
|
|
assert(i);
|
|
assert(i->output_fd >= 0);
|
|
|
|
/* Nothing of what is below applies to block devices */
|
|
if (S_ISBLK(i->output_stat.st_mode)) {
|
|
|
|
if (i->flags & IMPORT_SYNC) {
|
|
if (fsync(i->output_fd) < 0)
|
|
return log_error_errno(errno, "Failed to synchronize block device: %m");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
assert(S_ISREG(i->output_stat.st_mode));
|
|
|
|
/* If an offset is specified we only are supposed to affect part of an existing output file or block
|
|
* device, thus don't manipulate file properties in that case */
|
|
|
|
if (i->offset == UINT64_MAX) {
|
|
/* In case this was a sparse file, make sure the file size is right */
|
|
if (i->written_uncompressed > 0) {
|
|
if (ftruncate(i->output_fd, i->written_uncompressed) < 0)
|
|
return log_error_errno(errno, "Failed to truncate file: %m");
|
|
}
|
|
|
|
r = raw_import_maybe_convert_qcow2(i);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (S_ISREG(i->input_stat.st_mode)) {
|
|
(void) copy_times(i->input_fd, i->output_fd, COPY_CRTIME);
|
|
(void) copy_xattr(i->input_fd, i->output_fd, 0);
|
|
}
|
|
}
|
|
|
|
r = install_file(AT_FDCWD, i->temp_path ?: i->local,
|
|
AT_FDCWD, i->final_path,
|
|
(i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) |
|
|
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) |
|
|
(i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0));
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to move image into place: %m");
|
|
|
|
i->temp_path = mfree(i->temp_path);
|
|
|
|
log_info("Wrote %s.", FORMAT_BYTES(i->written_uncompressed));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int raw_import_open_disk(RawImport *i) {
|
|
int r;
|
|
|
|
assert(i);
|
|
assert(i->local);
|
|
assert(!i->final_path);
|
|
assert(!i->temp_path);
|
|
assert(i->output_fd < 0);
|
|
|
|
if (i->flags & IMPORT_DIRECT) {
|
|
(void) mkdir_parents_label(i->local, 0700);
|
|
|
|
/* In direct mode we just open/create the local path and truncate it (like shell >
|
|
* redirection would do it) — except if an offset was passed, in which case we are supposed
|
|
* to operate on a section of the file only, in which case we apparently work on an some
|
|
* existing thing (i.e. are not the sole thing stored in the file), in which case we will
|
|
* neither truncate nor create. */
|
|
|
|
i->output_fd = open(i->local, O_RDWR|O_NOCTTY|O_CLOEXEC|(i->offset == UINT64_MAX ? O_TRUNC|O_CREAT : 0), 0664);
|
|
if (i->output_fd < 0)
|
|
return log_error_errno(errno, "Failed to open destination '%s': %m", i->local);
|
|
|
|
if (i->offset == UINT64_MAX)
|
|
(void) import_set_nocow_and_log(i->output_fd, i->local);
|
|
} else {
|
|
i->final_path = strjoin(i->image_root, "/", i->local, ".raw");
|
|
if (!i->final_path)
|
|
return log_oom();
|
|
|
|
r = tempfn_random(i->final_path, NULL, &i->temp_path);
|
|
if (r < 0)
|
|
return log_oom();
|
|
|
|
(void) mkdir_parents_label(i->temp_path, 0700);
|
|
|
|
i->output_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
|
|
if (i->output_fd < 0)
|
|
return log_error_errno(errno, "Failed to open destination '%s': %m", i->temp_path);
|
|
|
|
(void) import_set_nocow_and_log(i->output_fd, i->temp_path);
|
|
}
|
|
|
|
if (fstat(i->output_fd, &i->output_stat) < 0)
|
|
return log_error_errno(errno, "Failed to stat() output file: %m");
|
|
|
|
if (!S_ISREG(i->output_stat.st_mode) && !S_ISBLK(i->output_stat.st_mode))
|
|
return log_error_errno(SYNTHETIC_ERRNO(EBADFD),
|
|
"Target file is not a regular file or block device");
|
|
|
|
if (i->offset != UINT64_MAX) {
|
|
if (lseek(i->output_fd, i->offset, SEEK_SET) == (off_t) -1)
|
|
return log_error_errno(errno, "Failed to seek to offset: %m");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int raw_import_try_reflink(RawImport *i) {
|
|
off_t p;
|
|
int r;
|
|
|
|
assert(i);
|
|
assert(i->input_fd >= 0);
|
|
assert(i->output_fd >= 0);
|
|
|
|
if (i->compress.type != IMPORT_COMPRESS_UNCOMPRESSED)
|
|
return 0;
|
|
|
|
if (i->offset != UINT64_MAX || i->size_max != UINT64_MAX)
|
|
return 0;
|
|
|
|
if (!S_ISREG(i->input_stat.st_mode) || !S_ISREG(i->output_stat.st_mode))
|
|
return 0;
|
|
|
|
p = lseek(i->input_fd, 0, SEEK_CUR);
|
|
if (p == (off_t) -1)
|
|
return log_error_errno(errno, "Failed to read file offset of input file: %m");
|
|
|
|
/* Let's only try a btrfs reflink, if we are reading from the beginning of the file */
|
|
if ((uint64_t) p != (uint64_t) i->buffer_size)
|
|
return 0;
|
|
|
|
r = btrfs_reflink(i->input_fd, i->output_fd);
|
|
if (r >= 0)
|
|
return 1;
|
|
|
|
log_debug_errno(r, "Couldn't establish reflink, using copy: %m");
|
|
return 0;
|
|
}
|
|
|
|
static int raw_import_write(const void *p, size_t sz, void *userdata) {
|
|
RawImport *i = ASSERT_PTR(userdata);
|
|
bool too_much = false;
|
|
int r;
|
|
|
|
assert(p);
|
|
assert(sz > 0);
|
|
|
|
if (i->written_uncompressed >= UINT64_MAX - sz)
|
|
return log_error_errno(SYNTHETIC_ERRNO(EOVERFLOW), "File too large, overflow");
|
|
|
|
if (i->size_max != UINT64_MAX) {
|
|
if (i->written_uncompressed >= i->size_max) {
|
|
too_much = true;
|
|
goto finish;
|
|
}
|
|
|
|
if (i->written_uncompressed + sz > i->size_max) {
|
|
too_much = true;
|
|
sz = i->size_max - i->written_uncompressed; /* since we have the data in memory
|
|
* already, we might as well write it to
|
|
* disk to the max */
|
|
}
|
|
}
|
|
|
|
/* Generate sparse file if we created/truncated the file */
|
|
if (S_ISREG(i->output_stat.st_mode) && i->offset == UINT64_MAX) {
|
|
ssize_t n;
|
|
|
|
n = sparse_write(i->output_fd, p, sz, 64);
|
|
if (n < 0)
|
|
return log_error_errno((int) n, "Failed to write file: %m");
|
|
if ((size_t) n < sz)
|
|
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write");
|
|
} else {
|
|
r = loop_write(i->output_fd, p, sz, false);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to write file: %m");
|
|
}
|
|
|
|
i->written_uncompressed += sz;
|
|
|
|
finish:
|
|
if (too_much)
|
|
return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "File too large");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int raw_import_process(RawImport *i) {
|
|
ssize_t l;
|
|
int r;
|
|
|
|
assert(i);
|
|
assert(i->buffer_size < sizeof(i->buffer));
|
|
|
|
l = read(i->input_fd, i->buffer + i->buffer_size, sizeof(i->buffer) - i->buffer_size);
|
|
if (l < 0) {
|
|
if (errno == EAGAIN)
|
|
return 0;
|
|
|
|
r = log_error_errno(errno, "Failed to read input file: %m");
|
|
goto finish;
|
|
}
|
|
|
|
i->buffer_size += l;
|
|
|
|
if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) {
|
|
|
|
if (l == 0) { /* EOF */
|
|
log_debug("File too short to be compressed, as no compression signature fits in, thus assuming uncompressed.");
|
|
import_uncompress_force_off(&i->compress);
|
|
} else {
|
|
r = import_uncompress_detect(&i->compress, i->buffer, i->buffer_size);
|
|
if (r < 0) {
|
|
log_error_errno(r, "Failed to detect file compression: %m");
|
|
goto finish;
|
|
}
|
|
if (r == 0) /* Need more data */
|
|
return 0;
|
|
}
|
|
|
|
r = raw_import_open_disk(i);
|
|
if (r < 0)
|
|
goto finish;
|
|
|
|
r = raw_import_try_reflink(i);
|
|
if (r < 0)
|
|
goto finish;
|
|
if (r > 0)
|
|
goto complete;
|
|
}
|
|
|
|
r = import_uncompress(&i->compress, i->buffer, i->buffer_size, raw_import_write, i);
|
|
if (r < 0) {
|
|
log_error_errno(r, "Failed to decode and write: %m");
|
|
goto finish;
|
|
}
|
|
|
|
i->written_compressed += i->buffer_size;
|
|
i->buffer_size = 0;
|
|
|
|
if (l == 0) /* EOF */
|
|
goto complete;
|
|
|
|
raw_import_report_progress(i);
|
|
|
|
return 0;
|
|
|
|
complete:
|
|
r = raw_import_finish(i);
|
|
|
|
finish:
|
|
if (i->on_finished)
|
|
i->on_finished(i, r, i->userdata);
|
|
else
|
|
sd_event_exit(i->event, r);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int raw_import_on_input(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
|
|
RawImport *i = userdata;
|
|
|
|
return raw_import_process(i);
|
|
}
|
|
|
|
static int raw_import_on_defer(sd_event_source *s, void *userdata) {
|
|
RawImport *i = userdata;
|
|
|
|
return raw_import_process(i);
|
|
}
|
|
|
|
int raw_import_start(
|
|
RawImport *i,
|
|
int fd,
|
|
const char *local,
|
|
uint64_t offset,
|
|
uint64_t size_max,
|
|
ImportFlags flags) {
|
|
int r;
|
|
|
|
assert(i);
|
|
assert(fd >= 0);
|
|
assert(local);
|
|
assert(!(flags & ~IMPORT_FLAGS_MASK_RAW));
|
|
assert(offset == UINT64_MAX || FLAGS_SET(flags, IMPORT_DIRECT));
|
|
|
|
if (!import_validate_local(local, flags))
|
|
return -EINVAL;
|
|
|
|
if (i->input_fd >= 0)
|
|
return -EBUSY;
|
|
|
|
r = fd_nonblock(fd, true);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = free_and_strdup(&i->local, local);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
i->flags = flags;
|
|
i->offset = offset;
|
|
i->size_max = size_max;
|
|
|
|
if (fstat(fd, &i->input_stat) < 0)
|
|
return -errno;
|
|
|
|
r = sd_event_add_io(i->event, &i->input_event_source, fd, EPOLLIN, raw_import_on_input, i);
|
|
if (r == -EPERM) {
|
|
/* This fd does not support epoll, for example because it is a regular file. Busy read in that case */
|
|
r = sd_event_add_defer(i->event, &i->input_event_source, raw_import_on_defer, i);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_event_source_set_enabled(i->input_event_source, SD_EVENT_ON);
|
|
}
|
|
if (r < 0)
|
|
return r;
|
|
|
|
i->input_fd = fd;
|
|
return 0;
|
|
}
|