importd: add new bus calls for importing local tar and raw images

This also adds "machinectl import-raw" and "machinectl import-tar" to
wrap these new bus calls.

THe commands basically do for local files that "machinectl pull-raw" and
friends do for remote files.
This commit is contained in:
Lennart Poettering 2015-03-05 00:56:08 +01:00
parent 776a972612
commit b6e676ce41
19 changed files with 1769 additions and 188 deletions

View File

@ -5430,14 +5430,16 @@ if HAVE_GCRYPT
rootlibexec_PROGRAMS += \
systemd-importd \
systemd-pull
systemd-pull \
systemd-import
systemd_importd_SOURCES = \
src/import/importd.c
systemd_importd_CFLAGS = \
$(AM_CFLAGS) \
-D SYSTEMD_PULL_PATH=\"$(rootlibexecdir)/systemd-pull\"
-D SYSTEMD_PULL_PATH=\"$(rootlibexecdir)/systemd-pull\" \
-D SYSTEMD_IMPORT_PATH=\"$(rootlibexecdir)/systemd-import\"
systemd_importd_LDADD = \
libsystemd-internal.la \
@ -5456,6 +5458,8 @@ systemd_pull_SOURCES = \
src/import/pull-job.h \
src/import/pull-common.c \
src/import/pull-common.h \
src/import/import-common.c \
src/import/import-common.h \
src/import/import-compress.c \
src/import/import-compress.h \
src/import/curl-util.c \
@ -5484,6 +5488,32 @@ systemd_pull_LDADD = \
-lbz2 \
$(GCRYPT_LIBS)
systemd_import_SOURCES = \
src/import/import.c \
src/import/import-raw.c \
src/import/import-raw.h \
src/import/import-tar.c \
src/import/import-tar.h \
src/import/import-common.c \
src/import/import-common.h \
src/import/import-compress.c \
src/import/import-compress.h \
src/import/qcow2-util.c \
src/import/qcow2-util.h
systemd_import_CFLAGS = \
$(AM_CFLAGS) \
$(XZ_CFLAGS) \
$(ZLIB_CFLAGS)
systemd_import_LDADD = \
libsystemd-internal.la \
libsystemd-label.la \
libsystemd-shared.la \
$(XZ_LIBS) \
$(ZLIB_LIBS) \
-lbz2
dist_rootlibexec_DATA = \
src/import/import-pubring.gpg

150
src/import/import-common.c Normal file
View File

@ -0,0 +1,150 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2015 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <sys/prctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include "util.h"
#include "btrfs-util.h"
#include "capability.h"
#include "import-common.h"
int import_make_read_only_fd(int fd) {
int r;
assert(fd >= 0);
/* First, let's make this a read-only subvolume if it refers
* to a subvolume */
r = btrfs_subvol_set_read_only_fd(fd, true);
if (r == -ENOTTY || r == -ENOTDIR || r == -EINVAL) {
struct stat st;
/* This doesn't refer to a subvolume, or the file
* system isn't even btrfs. In that, case fall back to
* chmod()ing */
r = fstat(fd, &st);
if (r < 0)
return log_error_errno(errno, "Failed to stat temporary image: %m");
/* Drop "w" flag */
if (fchmod(fd, st.st_mode & 07555) < 0)
return log_error_errno(errno, "Failed to chmod() final image: %m");
return 0;
} else if (r < 0)
return log_error_errno(r, "Failed to make subvolume read-only: %m");
return 0;
}
int import_make_read_only(const char *path) {
_cleanup_close_ int fd = 1;
fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
return log_error_errno(errno, "Failed to open %s: %m", path);
return import_make_read_only_fd(fd);
}
int import_fork_tar(const char *path, pid_t *ret) {
_cleanup_close_pair_ int pipefd[2] = { -1, -1 };
pid_t pid;
int r;
assert(path);
assert(ret);
if (pipe2(pipefd, O_CLOEXEC) < 0)
return log_error_errno(errno, "Failed to create pipe for tar: %m");
pid = fork();
if (pid < 0)
return log_error_errno(errno, "Failed to fork off tar: %m");
if (pid == 0) {
int null_fd;
uint64_t retain =
(1ULL << CAP_CHOWN) |
(1ULL << CAP_FOWNER) |
(1ULL << CAP_FSETID) |
(1ULL << CAP_MKNOD) |
(1ULL << CAP_SETFCAP) |
(1ULL << CAP_DAC_OVERRIDE);
/* Child */
reset_all_signal_handlers();
reset_signal_mask();
assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
pipefd[1] = safe_close(pipefd[1]);
if (dup2(pipefd[0], STDIN_FILENO) != STDIN_FILENO) {
log_error_errno(errno, "Failed to dup2() fd: %m");
_exit(EXIT_FAILURE);
}
if (pipefd[0] != STDIN_FILENO)
pipefd[0] = safe_close(pipefd[0]);
null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
if (null_fd < 0) {
log_error_errno(errno, "Failed to open /dev/null: %m");
_exit(EXIT_FAILURE);
}
if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) {
log_error_errno(errno, "Failed to dup2() fd: %m");
_exit(EXIT_FAILURE);
}
if (null_fd != STDOUT_FILENO)
null_fd = safe_close(null_fd);
fd_cloexec(STDIN_FILENO, false);
fd_cloexec(STDOUT_FILENO, false);
fd_cloexec(STDERR_FILENO, false);
if (unshare(CLONE_NEWNET) < 0)
log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
r = capability_bounding_set_drop(~retain, true);
if (r < 0)
log_error_errno(r, "Failed to drop capabilities, ignoring: %m");
execlp("tar", "tar", "--numeric-owner", "-C", path, "-px", NULL);
log_error_errno(errno, "Failed to execute tar: %m");
_exit(EXIT_FAILURE);
}
pipefd[0] = safe_close(pipefd[0]);
r = pipefd[1];
pipefd[1] = -1;
*ret = pid;
return r;
}

View File

@ -0,0 +1,27 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
#pragma once
/***
This file is part of systemd.
Copyright 2015 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
int import_make_read_only_fd(int fd);
int import_make_read_only(const char *path);
int import_fork_tar(const char *path, pid_t *ret);

454
src/import/import-raw.c Normal file
View File

@ -0,0 +1,454 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2015 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <linux/fs.h>
#include "sd-daemon.h"
#include "sd-event.h"
#include "util.h"
#include "path-util.h"
#include "btrfs-util.h"
#include "copy.h"
#include "mkdir.h"
#include "ratelimit.h"
#include "machine-pool.h"
#include "qcow2-util.h"
#include "import-compress.h"
#include "import-common.h"
#include "import-raw.h"
struct RawImport {
sd_event *event;
char *image_root;
RawImportFinished on_finished;
void *userdata;
char *local;
bool force_local;
bool read_only;
bool grow_machine_directory;
char *temp_path;
char *final_path;
int input_fd;
int output_fd;
ImportCompress compress;
uint64_t written_since_last_grow;
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 st;
unsigned last_percent;
RateLimit progress_rate_limit;
};
RawImport* raw_import_unref(RawImport *i) {
if (!i)
return NULL;
sd_event_unref(i->event);
if (i->temp_path) {
(void) unlink(i->temp_path);
free(i->temp_path);
}
import_compress_free(&i->compress);
sd_event_source_unref(i->input_event_source);
safe_close(i->output_fd);
free(i->final_path);
free(i->image_root);
free(i->local);
free(i);
return NULL;
}
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;
int r;
assert(ret);
i = new0(RawImport, 1);
if (!i)
return -ENOMEM;
i->input_fd = i->output_fd = -1;
i->on_finished = on_finished;
i->userdata = userdata;
RATELIMIT_INIT(i->progress_rate_limit, 500 * USEC_PER_MSEC, 1);
i->last_percent = (unsigned) -1;
i->image_root = strdup(image_root ?: "/var/lib/machines");
if (!i->image_root)
return -ENOMEM;
i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines");
if (event)
i->event = sd_event_ref(event);
else {
r = sd_event_default(&i->event);
if (r < 0)
return r;
}
*ret = i;
i = NULL;
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->st.st_mode))
return;
if (i->written_compressed >= (uint64_t) i->st.st_size)
percent = 100;
else
percent = (unsigned) ((i->written_compressed * UINT64_C(100)) / (uint64_t) i->st.st_size);
if (percent == i->last_percent)
return;
if (!ratelimit_test(&i->progress_rate_limit))
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 = -1;
_cleanup_free_ char *t = NULL;
int r;
assert(i);
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, &t);
if (r < 0)
return log_oom();
converted_fd = open(t, 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", t);
r = chattr_fd(converted_fd, true, FS_NOCOW_FL);
if (r < 0)
log_warning_errno(errno, "Failed to set file attributes on %s: %m", t);
log_info("Unpacking QCOW2 file.");
r = qcow2_convert(i->output_fd, converted_fd);
if (r < 0) {
unlink(t);
return log_error_errno(r, "Failed to convert qcow2 image: %m");
}
(void) unlink(i->temp_path);
free(i->temp_path);
i->temp_path = t;
t = NULL;
safe_close(i->output_fd);
i->output_fd = converted_fd;
converted_fd = -1;
return 1;
}
static int raw_import_finish(RawImport *i) {
int r;
assert(i);
assert(i->output_fd >= 0);
assert(i->temp_path);
assert(i->final_path);
/* In case this was a sparse file, make sure the file system 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->st.st_mode)) {
(void) copy_times(i->input_fd, i->output_fd);
(void) copy_xattr(i->input_fd, i->output_fd);
}
if (i->read_only) {
r = import_make_read_only_fd(i->output_fd);
if (r < 0)
return r;
}
if (i->force_local) {
(void) btrfs_subvol_remove(i->final_path);
(void) rm_rf_dangerous(i->final_path, false, true, false);
}
if (renameat2(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path, RENAME_NOREPLACE) < 0)
return log_error_errno(errno, "Failed to move image into place: %m");
free(i->temp_path);
i->temp_path = NULL;
return 0;
}
static int raw_import_open_disk(RawImport *i) {
int r;
assert(i);
assert(!i->final_path);
assert(!i->temp_path);
assert(i->output_fd < 0);
i->final_path = strjoin(i->image_root, "/", i->local, ".raw", NULL);
if (!i->final_path)
return log_oom();
r = tempfn_random(i->final_path, &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);
r = chattr_fd(i->output_fd, true, FS_NOCOW_FL);
if (r < 0)
log_warning_errno(errno, "Failed to set file attributes on %s: %m", i->temp_path);
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 (!S_ISREG(i->st.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;
return 0;
}
static int raw_import_write(const void *p, size_t sz, void *userdata) {
RawImport *i = userdata;
ssize_t n;
if (i->grow_machine_directory && i->written_since_last_grow >= GROW_INTERVAL_BYTES) {
i->written_since_last_grow = 0;
grow_machine_directory();
}
n = sparse_write(i->output_fd, p, sz, 64);
if (n < 0)
return -errno;
if ((size_t) n < sz)
return -EIO;
i->written_uncompressed += sz;
i->written_since_last_grow += sz;
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) {
r = log_error_errno(errno, "Failed to read input file: %m");
goto finish;
}
if (l == 0) {
if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) {
log_error("Premature end of file: %m");
r = -EIO;
goto finish;
}
r = raw_import_finish(i);
goto finish;
}
i->buffer_size += l;
if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) {
r = import_uncompress_detect(&i->compress, i->buffer, i->buffer_size);
if (r < 0) {
log_error("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) {
r = raw_import_finish(i);
goto finish;
}
}
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;
raw_import_report_progress(i);
return 0;
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, bool force_local, bool read_only) {
int r;
assert(i);
assert(fd >= 0);
assert(local);
if (!machine_name_is_valid(local))
return -EINVAL;
if (i->input_fd >= 0)
return -EBUSY;
r = free_and_strdup(&i->local, local);
if (r < 0)
return r;
i->force_local = force_local;
i->read_only = read_only;
if (fstat(fd, &i->st) < 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 r;
}

37
src/import/import-raw.h Normal file
View File

@ -0,0 +1,37 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
#pragma once
/***
This file is part of systemd.
Copyright 2015 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "sd-event.h"
#include "macro.h"
#include "import-util.h"
typedef struct RawImport RawImport;
typedef void (*RawImportFinished)(RawImport *import, int error, void *userdata);
int raw_import_new(RawImport **import, sd_event *event, const char *image_root, RawImportFinished on_finished, void *userdata);
RawImport* raw_import_unref(RawImport *import);
DEFINE_TRIVIAL_CLEANUP_FUNC(RawImport*, raw_import_unref);
int raw_import_start(RawImport *i, int fd, const char *local, bool force_local, bool read_only);

374
src/import/import-tar.c Normal file
View File

@ -0,0 +1,374 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2015 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <linux/fs.h>
#include "sd-daemon.h"
#include "sd-event.h"
#include "util.h"
#include "path-util.h"
#include "btrfs-util.h"
#include "copy.h"
#include "mkdir.h"
#include "ratelimit.h"
#include "machine-pool.h"
#include "qcow2-util.h"
#include "import-compress.h"
#include "import-common.h"
#include "import-tar.h"
struct TarImport {
sd_event *event;
char *image_root;
TarImportFinished on_finished;
void *userdata;
char *local;
bool force_local;
bool read_only;
bool grow_machine_directory;
char *temp_path;
char *final_path;
int input_fd;
int tar_fd;
ImportCompress compress;
uint64_t written_since_last_grow;
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 st;
pid_t tar_pid;
unsigned last_percent;
RateLimit progress_rate_limit;
};
TarImport* tar_import_unref(TarImport *i) {
if (!i)
return NULL;
sd_event_unref(i->event);
if (i->tar_pid > 1) {
(void) kill_and_sigcont(i->tar_pid, SIGKILL);
(void) wait_for_terminate(i->tar_pid, NULL);
}
if (i->temp_path) {
(void) btrfs_subvol_remove(i->temp_path);
(void) rm_rf_dangerous(i->temp_path, false, true, false);
free(i->temp_path);
}
import_compress_free(&i->compress);
sd_event_source_unref(i->input_event_source);
safe_close(i->tar_fd);
free(i->final_path);
free(i->image_root);
free(i->local);
free(i);
return NULL;
}
int tar_import_new(
TarImport **ret,
sd_event *event,
const char *image_root,
TarImportFinished on_finished,
void *userdata) {
_cleanup_(tar_import_unrefp) TarImport *i = NULL;
int r;
assert(ret);
i = new0(TarImport, 1);
if (!i)
return -ENOMEM;
i->input_fd = i->tar_fd = -1;
i->on_finished = on_finished;
i->userdata = userdata;
RATELIMIT_INIT(i->progress_rate_limit, 500 * USEC_PER_MSEC, 1);
i->last_percent = (unsigned) -1;
i->image_root = strdup(image_root ?: "/var/lib/machines");
if (!i->image_root)
return -ENOMEM;
i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines");
if (event)
i->event = sd_event_ref(event);
else {
r = sd_event_default(&i->event);
if (r < 0)
return r;
}
*ret = i;
i = NULL;
return 0;
}
static void tar_import_report_progress(TarImport *i) {
unsigned percent;
assert(i);
/* We have no size information, unless the source is a regular file */
if (!S_ISREG(i->st.st_mode))
return;
if (i->written_compressed >= (uint64_t) i->st.st_size)
percent = 100;
else
percent = (unsigned) ((i->written_compressed * UINT64_C(100)) / (uint64_t) i->st.st_size);
if (percent == i->last_percent)
return;
if (!ratelimit_test(&i->progress_rate_limit))
return;
sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
log_info("Imported %u%%.", percent);
i->last_percent = percent;
}
static int tar_import_finish(TarImport *i) {
int r;
assert(i);
assert(i->tar_fd >= 0);
assert(i->temp_path);
assert(i->final_path);
i->tar_fd = safe_close(i->tar_fd);
if (i->tar_pid > 0) {
r = wait_for_terminate_and_warn("tar", i->tar_pid, true);
i->tar_pid = 0;
if (r < 0)
return r;
}
if (i->read_only) {
r = import_make_read_only(i->temp_path);
if (r < 0)
return r;
}
if (i->force_local) {
(void) btrfs_subvol_remove(i->final_path);
(void) rm_rf_dangerous(i->final_path, false, true, false);
}
if (renameat2(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path, RENAME_NOREPLACE) < 0)
return log_error_errno(errno, "Failed to move image into place: %m");
free(i->temp_path);
i->temp_path = NULL;
return 0;
}
static int tar_import_fork_tar(TarImport *i) {
int r;
assert(i);
assert(!i->final_path);
assert(!i->temp_path);
assert(i->tar_fd < 0);
i->final_path = strjoin(i->image_root, "/", i->local, NULL);
if (!i->final_path)
return log_oom();
r = tempfn_random(i->final_path, &i->temp_path);
if (r < 0)
return log_oom();
(void) mkdir_parents_label(i->temp_path, 0700);
r = btrfs_subvol_make(i->temp_path);
if (r == -ENOTTY) {
if (mkdir(i->temp_path, 0755) < 0)
return log_error_errno(errno, "Failed to create directory %s: %m", i->temp_path);
} else if (r < 0)
return log_error_errno(errno, "Failed to create subvolume %s: %m", i->temp_path);
i->tar_fd = import_fork_tar(i->temp_path, &i->tar_pid);
if (i->tar_fd < 0)
return i->tar_fd;
return 0;
}
static int tar_import_write(const void *p, size_t sz, void *userdata) {
TarImport *i = userdata;
int r;
if (i->grow_machine_directory && i->written_since_last_grow >= GROW_INTERVAL_BYTES) {
i->written_since_last_grow = 0;
grow_machine_directory();
}
r = loop_write(i->tar_fd, p, sz, false);
if (r < 0)
return r;
i->written_uncompressed += sz;
i->written_since_last_grow += sz;
return 0;
}
static int tar_import_process(TarImport *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) {
r = log_error_errno(errno, "Failed to read input file: %m");
goto finish;
}
if (l == 0) {
if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) {
log_error("Premature end of file: %m");
r = -EIO;
goto finish;
}
r = tar_import_finish(i);
goto finish;
}
i->buffer_size += l;
if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) {
r = import_uncompress_detect(&i->compress, i->buffer, i->buffer_size);
if (r < 0) {
log_error("Failed to detect file compression: %m");
goto finish;
}
if (r == 0) /* Need more data */
return 0;
r = tar_import_fork_tar(i);
if (r < 0)
goto finish;
}
r = import_uncompress(&i->compress, i->buffer, i->buffer_size, tar_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;
tar_import_report_progress(i);
return 0;
finish:
if (i->on_finished)
i->on_finished(i, r, i->userdata);
else
sd_event_exit(i->event, r);
return 0;
}
static int tar_import_on_input(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
TarImport *i = userdata;
return tar_import_process(i);
}
static int tar_import_on_defer(sd_event_source *s, void *userdata) {
TarImport *i = userdata;
return tar_import_process(i);
}
int tar_import_start(TarImport *i, int fd, const char *local, bool force_local, bool read_only) {
int r;
assert(i);
assert(fd >= 0);
assert(local);
if (!machine_name_is_valid(local))
return -EINVAL;
if (i->input_fd >= 0)
return -EBUSY;
r = free_and_strdup(&i->local, local);
if (r < 0)
return r;
i->force_local = force_local;
i->read_only = read_only;
if (fstat(fd, &i->st) < 0)
return -errno;
r = sd_event_add_io(i->event, &i->input_event_source, fd, EPOLLIN, tar_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, tar_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 r;
}

37
src/import/import-tar.h Normal file
View File

@ -0,0 +1,37 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
#pragma once
/***
This file is part of systemd.
Copyright 2015 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "sd-event.h"
#include "macro.h"
#include "import-util.h"
typedef struct TarImport TarImport;
typedef void (*TarImportFinished)(TarImport *import, int error, void *userdata);
int tar_import_new(TarImport **import, sd_event *event, const char *image_root, TarImportFinished on_finished, void *userdata);
TarImport* tar_import_unref(TarImport *import);
DEFINE_TRIVIAL_CLEANUP_FUNC(TarImport*, tar_import_unref);
int tar_import_start(TarImport *import, int fd, const char *local, bool force_local, bool read_only);

336
src/import/import.c Normal file
View File

@ -0,0 +1,336 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2015 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <getopt.h>
#include "sd-event.h"
#include "event-util.h"
#include "verbs.h"
#include "build.h"
#include "machine-image.h"
#include "import-util.h"
#include "import-tar.h"
#include "import-raw.h"
static bool arg_force = false;
static bool arg_read_only = false;
static const char *arg_image_root = "/var/lib/machines";
static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
log_notice("Transfer aborted.");
sd_event_exit(sd_event_source_get_event(s), EINTR);
return 0;
}
static void on_tar_finished(TarImport *import, int error, void *userdata) {
sd_event *event = userdata;
assert(import);
if (error == 0)
log_info("Operation completed successfully.");
sd_event_exit(event, abs(error));
}
static int import_tar(int argc, char *argv[], void *userdata) {
_cleanup_(tar_import_unrefp) TarImport *import = NULL;
_cleanup_event_unref_ sd_event *event = NULL;
const char *path = NULL, *local = NULL;
_cleanup_free_ char *ll = NULL;
_cleanup_close_ int open_fd = -1;
int r, fd;
if (argc >= 2)
path = argv[1];
if (isempty(path) || streq(path, "-"))
path = NULL;
if (argc >= 3)
local = argv[2];
else if (path)
local = basename(path);
if (isempty(local) || streq(local, "-"))
local = NULL;
if (local) {
r = tar_strip_suffixes(local, &ll);
if (r < 0)
return log_oom();
local = ll;
if (!machine_name_is_valid(local)) {
log_error("Local image name '%s' is not valid.", local);
return -EINVAL;
}
if (!arg_force) {
r = image_find(local, NULL);
if (r < 0)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
else if (r > 0) {
log_error_errno(EEXIST, "Image '%s' already exists.", local);
return -EEXIST;
}
}
} else
local = "imported";
if (path) {
open_fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (open_fd < 0)
return log_error_errno(errno, "Failed to open tar image to import: %m");
fd = open_fd;
log_info("Importing '%s', saving as '%s'.", path, local);
} else {
_cleanup_free_ char *pretty = NULL;
fd = STDIN_FILENO;
(void) readlink_malloc("/proc/self/fd/0", &pretty);
log_info("Importing '%s', saving as '%s'.", strna(pretty), local);
}
r = sd_event_default(&event);
if (r < 0)
return log_error_errno(r, "Failed to allocate event loop: %m");
assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0);
sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL);
sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL);
r = tar_import_new(&import, event, arg_image_root, on_tar_finished, event);
if (r < 0)
return log_error_errno(r, "Failed to allocate importer: %m");
r = tar_import_start(import, fd, local, arg_force, arg_read_only);
if (r < 0)
return log_error_errno(r, "Failed to import image: %m");
r = sd_event_loop(event);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
log_info("Exiting.");
return -r;
}
static void on_raw_finished(RawImport *import, int error, void *userdata) {
sd_event *event = userdata;
assert(import);
if (error == 0)
log_info("Operation completed successfully.");
sd_event_exit(event, abs(error));
}
static int import_raw(int argc, char *argv[], void *userdata) {
_cleanup_(raw_import_unrefp) RawImport *import = NULL;
_cleanup_event_unref_ sd_event *event = NULL;
const char *path = NULL, *local = NULL;
_cleanup_free_ char *ll = NULL;
_cleanup_close_ int open_fd = -1;
int r, fd;
if (argc >= 2)
path = argv[1];
if (isempty(path) || streq(path, "-"))
path = NULL;
if (argc >= 3)
local = argv[2];
else if (path)
local = basename(path);
if (isempty(local) || streq(local, "-"))
local = NULL;
if (local) {
r = raw_strip_suffixes(local, &ll);
if (r < 0)
return log_oom();
local = ll;
if (!machine_name_is_valid(local)) {
log_error("Local image name '%s' is not valid.", local);
return -EINVAL;
}
if (!arg_force) {
r = image_find(local, NULL);
if (r < 0)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
else if (r > 0) {
log_error_errno(EEXIST, "Image '%s' already exists.", local);
return -EEXIST;
}
}
} else
local = "imported";
if (path) {
open_fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (open_fd < 0)
return log_error_errno(errno, "Failed to open raw image to import: %m");
fd = open_fd;
log_info("Importing '%s', saving as '%s'.", path, local);
} else {
_cleanup_free_ char *pretty = NULL;
fd = STDIN_FILENO;
(void) readlink_malloc("/proc/self/fd/0", &pretty);
log_info("Importing '%s', saving as '%s'.", pretty, local);
}
r = sd_event_default(&event);
if (r < 0)
return log_error_errno(r, "Failed to allocate event loop: %m");
assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0);
sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL);
sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL);
r = raw_import_new(&import, event, arg_image_root, on_raw_finished, event);
if (r < 0)
return log_error_errno(r, "Failed to allocate importer: %m");
r = raw_import_start(import, fd, local, arg_force, arg_read_only);
if (r < 0)
return log_error_errno(r, "Failed to import image: %m");
r = sd_event_loop(event);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
log_info("Exiting.");
return -r;
}
static int help(int argc, char *argv[], void *userdata) {
printf("%s [OPTIONS...] {COMMAND} ...\n\n"
"Import container or virtual machine image.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --force Force creation of image\n"
" --image-root=PATH Image root directory\n"
" --read-only Create a read-only image\n\n"
"Commands:\n"
" tar FILE [NAME] Download a TAR image\n"
" raw FILE [NAME] Download a RAW image\n",
program_invocation_short_name);
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_FORCE,
ARG_IMAGE_ROOT,
ARG_READ_ONLY,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "force", no_argument, NULL, ARG_FORCE },
{ "image-root", required_argument, NULL, ARG_IMAGE_ROOT },
{ "read-only", no_argument, NULL, ARG_READ_ONLY },
{}
};
int c;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
switch (c) {
case 'h':
return help(0, NULL, NULL);
case ARG_VERSION:
puts(PACKAGE_STRING);
puts(SYSTEMD_FEATURES);
return 0;
case ARG_FORCE:
arg_force = true;
break;
case ARG_IMAGE_ROOT:
arg_image_root = optarg;
break;
case ARG_READ_ONLY:
arg_read_only = true;
break;
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
return 1;
}
static int import_main(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "tar", 2, 3, 0, import_tar },
{ "raw", 2, 3, 0, import_raw },
{}
};
return dispatch_verb(argc, argv, verbs, NULL);
}
int main(int argc, char *argv[]) {
int r;
setlocale(LC_ALL, "");
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
if (r <= 0)
goto finish;
ignore_signals(SIGPIPE, -1);
r = import_main(argc, argv);
finish:
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

View File

@ -39,9 +39,11 @@ typedef struct Transfer Transfer;
typedef struct Manager Manager;
typedef enum TransferType {
TRANSFER_TAR,
TRANSFER_RAW,
TRANSFER_DKR,
TRANSFER_IMPORT_TAR,
TRANSFER_IMPORT_RAW,
TRANSFER_PULL_TAR,
TRANSFER_PULL_RAW,
TRANSFER_PULL_DKR,
_TRANSFER_TYPE_MAX,
_TRANSFER_TYPE_INVALID = -1,
} TransferType;
@ -58,6 +60,7 @@ struct Transfer {
char *remote;
char *local;
bool force_local;
bool read_only;
char *dkr_index_url;
@ -73,6 +76,8 @@ struct Transfer {
unsigned n_canceled;
unsigned progress_percent;
int stdin_fd;
};
struct Manager {
@ -92,9 +97,11 @@ struct Manager {
#define TRANSFERS_MAX 64
static const char* const transfer_type_table[_TRANSFER_TYPE_MAX] = {
[TRANSFER_TAR] = "tar",
[TRANSFER_RAW] = "raw",
[TRANSFER_DKR] = "dkr",
[TRANSFER_IMPORT_TAR] = "import-tar",
[TRANSFER_IMPORT_RAW] = "import-raw",
[TRANSFER_PULL_TAR] = "pull-tar",
[TRANSFER_PULL_RAW] = "pull-raw",
[TRANSFER_PULL_DKR] = "pull-dkr",
};
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(transfer_type, TransferType);
@ -120,6 +127,7 @@ static Transfer *transfer_unref(Transfer *t) {
}
safe_close(t->log_fd);
safe_close(t->stdin_fd);
free(t);
return NULL;
@ -148,6 +156,8 @@ static int transfer_new(Manager *m, Transfer **ret) {
t->type = _TRANSFER_TYPE_INVALID;
t->log_fd = -1;
t->stdin_fd = -1;
t->verify = _IMPORT_VERIFY_INVALID;
id = m->current_transfer_id + 1;
@ -353,19 +363,19 @@ static int transfer_start(Transfer *t) {
return -errno;
if (t->pid == 0) {
const char *cmd[] = {
"systemd-pull",
transfer_type_to_string(t->type),
"--verify",
NULL, /* systemd-import or systemd-pull */
NULL, /* tar, raw, dkr */
NULL, /* --verify= */
NULL, /* verify argument */
NULL, /* maybe --force */
NULL, /* maybe --read-only */
NULL, /* maybe --dkr-index-url */
NULL, /* the actual URL */
NULL, /* remote */
NULL, /* local */
NULL
};
int null_fd;
unsigned k = 3;
unsigned k = 0;
/* Child */
@ -388,19 +398,31 @@ static int transfer_start(Transfer *t) {
if (pipefd[1] != STDOUT_FILENO && pipefd[1] != STDERR_FILENO)
pipefd[1] = safe_close(pipefd[1]);
null_fd = open("/dev/null", O_RDONLY|O_NOCTTY);
if (null_fd < 0) {
log_error_errno(errno, "Failed to open /dev/null: %m");
_exit(EXIT_FAILURE);
}
if (t->stdin_fd >= 0) {
if (dup2(t->stdin_fd, STDIN_FILENO) != STDIN_FILENO) {
log_error_errno(errno, "Failed to dup2() fd: %m");
_exit(EXIT_FAILURE);
}
if (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) {
log_error_errno(errno, "Failed to dup2() fd: %m");
_exit(EXIT_FAILURE);
}
if (t->stdin_fd != STDIN_FILENO)
safe_close(t->stdin_fd);
} else {
int null_fd;
if (null_fd != STDIN_FILENO)
safe_close(null_fd);
null_fd = open("/dev/null", O_RDONLY|O_NOCTTY);
if (null_fd < 0) {
log_error_errno(errno, "Failed to open /dev/null: %m");
_exit(EXIT_FAILURE);
}
if (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) {
log_error_errno(errno, "Failed to dup2() fd: %m");
_exit(EXIT_FAILURE);
}
if (null_fd != STDIN_FILENO)
safe_close(null_fd);
}
fd_cloexec(STDIN_FILENO, false);
fd_cloexec(STDOUT_FILENO, false);
@ -409,22 +431,44 @@ static int transfer_start(Transfer *t) {
setenv("SYSTEMD_LOG_TARGET", "console-prefixed", 1);
setenv("NOTIFY_SOCKET", "/run/systemd/import/notify", 1);
cmd[k++] = import_verify_to_string(t->verify);
if (IN_SET(t->type, TRANSFER_IMPORT_TAR, TRANSFER_IMPORT_RAW))
cmd[k++] = SYSTEMD_IMPORT_PATH;
else
cmd[k++] = SYSTEMD_PULL_PATH;
if (IN_SET(t->type, TRANSFER_IMPORT_TAR, TRANSFER_PULL_TAR))
cmd[k++] = "tar";
else if (IN_SET(t->type, TRANSFER_IMPORT_RAW, TRANSFER_PULL_RAW))
cmd[k++] = "raw";
else
cmd[k++] = "dkr";
if (t->verify != _IMPORT_VERIFY_INVALID) {
cmd[k++] = "--verify";
cmd[k++] = import_verify_to_string(t->verify);
}
if (t->force_local)
cmd[k++] = "--force";
if (t->read_only)
cmd[k++] = "--read-only";
if (t->dkr_index_url) {
cmd[k++] = "--dkr-index-url";
cmd[k++] = t->dkr_index_url;
}
cmd[k++] = t->remote;
if (t->remote)
cmd[k++] = t->remote;
else
cmd[k++] = "-";
if (t->local)
cmd[k++] = t->local;
cmd[k] = NULL;
execv(SYSTEMD_PULL_PATH, (char * const *) cmd);
log_error_errno(errno, "Failed to execute import tool: %m");
execv(cmd[0], (char * const *) cmd);
log_error_errno(errno, "Failed to execute %s tool: %m", cmd[0]);
_exit(EXIT_FAILURE);
}
@ -432,6 +476,8 @@ static int transfer_start(Transfer *t) {
t->log_fd = pipefd[0];
pipefd[0] = -1;
t->stdin_fd = safe_close(t->stdin_fd);
r = sd_event_add_child(t->manager->event, &t->pid_event_source, t->pid, WEXITED, transfer_on_pid, t);
if (r < 0)
return r;
@ -644,6 +690,67 @@ static Transfer *manager_find(Manager *m, TransferType type, const char *dkr_ind
return NULL;
}
static int method_import_tar_or_raw(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
_cleanup_(transfer_unrefp) Transfer *t = NULL;
int fd, force, read_only, r;
const char *local, *object;
Manager *m = userdata;
TransferType type;
uint32_t id;
r = bus_verify_polkit_async(
msg,
CAP_SYS_ADMIN,
"org.freedesktop.import1.import",
false,
UID_INVALID,
&m->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
r = sd_bus_message_read(msg, "hsbb", &fd, &local, &force, &read_only);
if (r < 0)
return r;
if (!machine_name_is_valid(local))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local);
r = setup_machine_directory((uint64_t) -1, error);
if (r < 0)
return r;
type = streq_ptr(sd_bus_message_get_member(msg), "ImportTar") ? TRANSFER_IMPORT_TAR : TRANSFER_IMPORT_RAW;
r = transfer_new(m, &t);
if (r < 0)
return r;
t->type = type;
t->force_local = force;
t->read_only = read_only;
t->local = strdup(local);
if (!t->local)
return -ENOMEM;
t->stdin_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
if (t->stdin_fd < 0)
return -errno;
r = transfer_start(t);
if (r < 0)
return r;
object = t->object_path;
id = t->id;
t = NULL;
return sd_bus_reply_method_return(msg, "uo", id, object);
}
static int method_pull_tar_or_raw(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
_cleanup_(transfer_unrefp) Transfer *t = NULL;
const char *remote, *local, *verify, *object;
@ -693,7 +800,7 @@ static int method_pull_tar_or_raw(sd_bus *bus, sd_bus_message *msg, void *userda
if (r < 0)
return r;
type = streq_ptr(sd_bus_message_get_member(msg), "PullTar") ? TRANSFER_TAR : TRANSFER_RAW;
type = streq_ptr(sd_bus_message_get_member(msg), "PullTar") ? TRANSFER_PULL_TAR : TRANSFER_PULL_RAW;
if (manager_find(m, type, NULL, remote))
return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote);
@ -710,9 +817,11 @@ static int method_pull_tar_or_raw(sd_bus *bus, sd_bus_message *msg, void *userda
if (!t->remote)
return -ENOMEM;
t->local = strdup(local);
if (!t->local)
return -ENOMEM;
if (local) {
t->local = strdup(local);
if (!t->local)
return -ENOMEM;
}
r = transfer_start(t);
if (r < 0)
@ -788,14 +897,14 @@ static int method_pull_dkr(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_
if (r < 0)
return r;
if (manager_find(m, TRANSFER_DKR, index_url, remote))
if (manager_find(m, TRANSFER_PULL_DKR, index_url, remote))
return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote);
r = transfer_new(m, &t);
if (r < 0)
return r;
t->type = TRANSFER_DKR;
t->type = TRANSFER_PULL_DKR;
t->verify = v;
t->force_local = force;
@ -807,9 +916,11 @@ static int method_pull_dkr(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_
if (!t->remote)
return -ENOMEM;
t->local = strdup(local);
if (!t->local)
return -ENOMEM;
if (local) {
t->local = strdup(local);
if (!t->local)
return -ENOMEM;
}
r = transfer_start(t);
if (r < 0)
@ -967,6 +1078,8 @@ static const sd_bus_vtable transfer_vtable[] = {
static const sd_bus_vtable manager_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("ImportTar", "hsbb", "uo", method_import_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ImportRaw", "hsbb", "uo", method_import_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("PullTar", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("PullRaw", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("PullDkr", "sssssb", "uo", method_pull_dkr, SD_BUS_VTABLE_UNPRIVILEGED),

View File

@ -16,6 +16,16 @@
<vendor>The systemd Project</vendor>
<vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url>
<action id="org.freedesktop.import1.import">
<_description>Import a VM or container image</_description>
<_message>Authentication is required to import a VM or container image</_message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="org.freedesktop.import1.pull">
<_description>Download a VM or container image</_description>
<_message>Authentication is required to download a VM or container image</_message>

View File

@ -141,47 +141,6 @@ int pull_make_local_copy(const char *final, const char *image_root, const char *
return 0;
}
int pull_make_read_only_fd(int fd) {
int r;
assert(fd >= 0);
/* First, let's make this a read-only subvolume if it refers
* to a subvolume */
r = btrfs_subvol_set_read_only_fd(fd, true);
if (r == -ENOTTY || r == -ENOTDIR || r == -EINVAL) {
struct stat st;
/* This doesn't refer to a subvolume, or the file
* system isn't even btrfs. In that, case fall back to
* chmod()ing */
r = fstat(fd, &st);
if (r < 0)
return log_error_errno(errno, "Failed to stat temporary image: %m");
/* Drop "w" flag */
if (fchmod(fd, st.st_mode & 07555) < 0)
return log_error_errno(errno, "Failed to chmod() final image: %m");
return 0;
} else if (r < 0)
return log_error_errno(r, "Failed to make subvolume read-only: %m");
return 0;
}
int pull_make_read_only(const char *path) {
_cleanup_close_ int fd = 1;
fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
return log_error_errno(errno, "Failed to open %s: %m", path);
return pull_make_read_only_fd(fd);
}
int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) {
_cleanup_free_ char *escaped_url = NULL;
char *path;
@ -463,83 +422,3 @@ finish:
return r;
}
int pull_fork_tar(const char *path, pid_t *ret) {
_cleanup_close_pair_ int pipefd[2] = { -1, -1 };
pid_t pid;
int r;
assert(path);
assert(ret);
if (pipe2(pipefd, O_CLOEXEC) < 0)
return log_error_errno(errno, "Failed to create pipe for tar: %m");
pid = fork();
if (pid < 0)
return log_error_errno(errno, "Failed to fork off tar: %m");
if (pid == 0) {
int null_fd;
uint64_t retain =
(1ULL << CAP_CHOWN) |
(1ULL << CAP_FOWNER) |
(1ULL << CAP_FSETID) |
(1ULL << CAP_MKNOD) |
(1ULL << CAP_SETFCAP) |
(1ULL << CAP_DAC_OVERRIDE);
/* Child */
reset_all_signal_handlers();
reset_signal_mask();
assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
pipefd[1] = safe_close(pipefd[1]);
if (dup2(pipefd[0], STDIN_FILENO) != STDIN_FILENO) {
log_error_errno(errno, "Failed to dup2() fd: %m");
_exit(EXIT_FAILURE);
}
if (pipefd[0] != STDIN_FILENO)
pipefd[0] = safe_close(pipefd[0]);
null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
if (null_fd < 0) {
log_error_errno(errno, "Failed to open /dev/null: %m");
_exit(EXIT_FAILURE);
}
if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) {
log_error_errno(errno, "Failed to dup2() fd: %m");
_exit(EXIT_FAILURE);
}
if (null_fd != STDOUT_FILENO)
null_fd = safe_close(null_fd);
fd_cloexec(STDIN_FILENO, false);
fd_cloexec(STDOUT_FILENO, false);
fd_cloexec(STDERR_FILENO, false);
if (unshare(CLONE_NEWNET) < 0)
log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
r = capability_bounding_set_drop(~retain, true);
if (r < 0)
log_error_errno(r, "Failed to drop capabilities, ignoring: %m");
execlp("tar", "tar", "--numeric-owner", "-C", path, "-px", NULL);
log_error_errno(errno, "Failed to execute tar: %m");
_exit(EXIT_FAILURE);
}
pipefd[0] = safe_close(pipefd[0]);
r = pipefd[1];
pipefd[1] = -1;
*ret = pid;
return r;
}

View File

@ -30,12 +30,7 @@ int pull_make_local_copy(const char *final, const char *root, const char *local,
int pull_find_old_etags(const char *url, const char *root, int dt, const char *prefix, const char *suffix, char ***etags);
int pull_make_read_only_fd(int fd);
int pull_make_read_only(const char *path);
int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret);
int pull_make_verification_jobs(PullJob **ret_checksum_job, PullJob **ret_signature_job, ImportVerify verify, const char *url, CurlGlue *glue, PullJobFinished on_finished, void *userdata);
int pull_verify(PullJob *main_job, PullJob *checksum_job, PullJob *signature_job);
int pull_fork_tar(const char *path, pid_t *ret);

View File

@ -34,6 +34,7 @@
#include "aufs-util.h"
#include "pull-job.h"
#include "pull-common.h"
#include "import-common.h"
#include "pull-dkr.h"
typedef enum DkrProgress {
@ -491,7 +492,7 @@ static int dkr_pull_job_on_open_disk(PullJob *j) {
if (r < 0)
return log_error_errno(r, "Failed to make btrfs subvolume %s: %m", i->temp_path);
j->disk_fd = pull_fork_tar(i->temp_path, &i->tar_pid);
j->disk_fd = import_fork_tar(i->temp_path, &i->tar_pid);
if (j->disk_fd < 0)
return j->disk_fd;

View File

@ -25,9 +25,6 @@
#include "machine-pool.h"
#include "pull-job.h"
/* Grow the /var/lib/machines directory after each 10MiB written */
#define PULL_GROW_INTERVAL_BYTES (UINT64_C(10) * UINT64_C(1024) * UINT64_C(1024))
PullJob* pull_job_unref(PullJob *j) {
if (!j)
return NULL;
@ -148,8 +145,7 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
* sparse and we just seeked for the last part */
if (ftruncate(j->disk_fd, j->written_uncompressed) < 0) {
log_error_errno(errno, "Failed to truncate file: %m");
r = -errno;
r = log_error_errno(errno, "Failed to truncate file: %m");
goto finish;
}
@ -197,7 +193,7 @@ static int pull_job_write_uncompressed(const void *p, size_t sz, void *userdata)
if (j->disk_fd >= 0) {
if (j->grow_machine_directory && j->written_since_last_grow >= PULL_GROW_INTERVAL_BYTES) {
if (j->grow_machine_directory && j->written_since_last_grow >= GROW_INTERVAL_BYTES) {
j->written_since_last_grow = 0;
grow_machine_directory();
}
@ -206,10 +202,8 @@ static int pull_job_write_uncompressed(const void *p, size_t sz, void *userdata)
n = sparse_write(j->disk_fd, p, sz, 64);
else
n = write(j->disk_fd, p, sz);
if (n < 0) {
log_error_errno(errno, "Failed to write file: %m");
return -errno;
}
if (n < 0)
return log_error_errno(errno, "Failed to write file: %m");
if ((size_t) n < sz) {
log_error("Short write");
return -EIO;

View File

@ -33,6 +33,7 @@
#include "mkdir.h"
#include "path-util.h"
#include "import-util.h"
#include "import-common.h"
#include "curl-util.h"
#include "qcow2-util.h"
#include "pull-job.h"
@ -210,7 +211,7 @@ static int raw_pull_maybe_convert_qcow2(RawPull *i) {
if (r < 0)
return log_oom();
converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
converted_fd = open(t, 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", t);
@ -226,9 +227,8 @@ static int raw_pull_maybe_convert_qcow2(RawPull *i) {
return log_error_errno(r, "Failed to convert qcow2 image: %m");
}
unlink(i->temp_path);
(void) unlink(i->temp_path);
free(i->temp_path);
i->temp_path = t;
t = NULL;
@ -380,11 +380,11 @@ static void raw_pull_job_on_finished(PullJob *j) {
raw_pull_report_progress(i, RAW_FINALIZING);
r = pull_make_read_only_fd(i->raw_job->disk_fd);
r = import_make_read_only_fd(i->raw_job->disk_fd);
if (r < 0)
goto finish;
r = rename(i->temp_path, i->final_path);
r = renameat2(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path, RENAME_NOREPLACE);
if (r < 0) {
r = log_error_errno(errno, "Failed to move RAW file into place: %m");
goto finish;
@ -426,12 +426,12 @@ static int raw_pull_job_on_open_disk(PullJob *j) {
return log_oom();
r = tempfn_random(i->final_path, &i->temp_path);
if (r <0)
if (r < 0)
return log_oom();
mkdir_parents_label(i->temp_path, 0700);
(void) mkdir_parents_label(i->temp_path, 0700);
j->disk_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
j->disk_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
if (j->disk_fd < 0)
return log_error_errno(errno, "Failed to create %s: %m", i->temp_path);

View File

@ -32,6 +32,7 @@
#include "mkdir.h"
#include "path-util.h"
#include "import-util.h"
#include "import-common.h"
#include "curl-util.h"
#include "pull-job.h"
#include "pull-common.h"
@ -276,11 +277,11 @@ static void tar_pull_job_on_finished(PullJob *j) {
tar_pull_report_progress(i, TAR_FINALIZING);
r = pull_make_read_only(i->temp_path);
r = import_make_read_only(i->temp_path);
if (r < 0)
goto finish;
if (rename(i->temp_path, i->final_path) < 0) {
if (renameat2(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path, RENAME_NOREPLACE) < 0) {
r = log_error_errno(errno, "Failed to rename to final image name: %m");
goto finish;
}
@ -334,7 +335,7 @@ static int tar_pull_job_on_open_disk(PullJob *j) {
} else if (r < 0)
return log_error_errno(errno, "Failed to create subvolume %s: %m", i->temp_path);
j->disk_fd = pull_fork_tar(i->temp_path, &i->tar_pid);
j->disk_fd = import_fork_tar(i->temp_path, &i->tar_pid);
if (j->disk_fd < 0)
return j->disk_fd;

View File

@ -433,6 +433,8 @@ int main(int argc, char *argv[]) {
if (r <= 0)
goto finish;
ignore_signals(SIGPIPE, -1);
r = pull_main(argc, argv);
finish:

View File

@ -1598,7 +1598,7 @@ static int pull_image_common(sd_bus *bus, sd_bus_message *m) {
r = sd_bus_call(bus, m, 0, &error, &reply);
if (r < 0) {
log_error("Failed pull image: %s", bus_error_message(&error, -r));
log_error("Failed acquire image: %s", bus_error_message(&error, -r));
return r;
}
@ -1621,6 +1621,140 @@ static int pull_image_common(sd_bus *bus, sd_bus_message *m) {
return -r;
}
static int import_tar(int argc, char *argv[], void *userdata) {
_cleanup_bus_message_unref_ sd_bus_message *m = NULL;
_cleanup_free_ char *ll = NULL;
_cleanup_close_ int fd = -1;
const char *local = NULL, *path = NULL;
sd_bus *bus = userdata;
int r;
assert(bus);
if (argc >= 2)
path = argv[1];
if (isempty(path) || streq(path, "-"))
path = NULL;
if (argc >= 3)
local = argv[2];
else if (path)
local = basename(path);
if (isempty(local) || streq(local, "-"))
local = NULL;
if (!local) {
log_error("Need either path or local name.");
return -EINVAL;
}
r = tar_strip_suffixes(local, &ll);
if (r < 0)
return log_oom();
local = ll;
if (!machine_name_is_valid(local)) {
log_error("Local name %s is not a suitable machine name.", local);
return -EINVAL;
}
if (path) {
fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd < 0)
return log_error_errno(errno, "Failed to open %s: %m", path);
}
r = sd_bus_message_new_method_call(
bus,
&m,
"org.freedesktop.import1",
"/org/freedesktop/import1",
"org.freedesktop.import1.Manager",
"ImportTar");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(
m,
"hsbb",
fd >= 0 ? fd : STDIN_FILENO,
local,
arg_force,
arg_read_only);
if (r < 0)
return bus_log_create_error(r);
return pull_image_common(bus, m);
}
static int import_raw(int argc, char *argv[], void *userdata) {
_cleanup_bus_message_unref_ sd_bus_message *m = NULL;
_cleanup_free_ char *ll = NULL;
_cleanup_close_ int fd = -1;
const char *local = NULL, *path = NULL;
sd_bus *bus = userdata;
int r;
assert(bus);
if (argc >= 2)
path = argv[1];
if (isempty(path) || streq(path, "-"))
path = NULL;
if (argc >= 3)
local = argv[2];
else if (path)
local = basename(path);
if (isempty(local) || streq(local, "-"))
local = NULL;
if (!local) {
log_error("Need either path or local name.");
return -EINVAL;
}
r = raw_strip_suffixes(local, &ll);
if (r < 0)
return log_oom();
local = ll;
if (!machine_name_is_valid(local)) {
log_error("Local name %s is not a suitable machine name.", local);
return -EINVAL;
}
if (path) {
fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd < 0)
return log_error_errno(errno, "Failed to open %s: %m", path);
}
r = sd_bus_message_new_method_call(
bus,
&m,
"org.freedesktop.import1",
"/org/freedesktop/import1",
"org.freedesktop.import1.Manager",
"ImportRaw");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(
m,
"hsbb",
fd >= 0 ? fd : STDIN_FILENO,
local,
arg_force,
arg_read_only);
if (r < 0)
return bus_log_create_error(r);
return pull_image_common(bus, m);
}
static int pull_tar(int argc, char *argv[], void *userdata) {
_cleanup_bus_message_unref_ sd_bus_message *m = NULL;
_cleanup_free_ char *l = NULL, *ll = NULL;
@ -1652,7 +1786,7 @@ static int pull_tar(int argc, char *argv[], void *userdata) {
if (local) {
r = tar_strip_suffixes(local, &ll);
if (r < 0)
return log_error_errno(r, "Failed to strip tar suffixes: %m");
return log_oom();
local = ll;
@ -1716,7 +1850,7 @@ static int pull_raw(int argc, char *argv[], void *userdata) {
if (local) {
r = raw_strip_suffixes(local, &ll);
if (r < 0)
return log_error_errno(r, "Failed to strip tar suffixes: %m");
return log_oom();
local = ll;
@ -2065,6 +2199,8 @@ static int help(int argc, char *argv[], void *userdata) {
" remove NAME... Remove an image\n"
" set-limit [NAME] BYTES Set image or pool size limit (quota)\n\n"
"Image Transfer Commands:\n"
" import-tar FILE [NAME] Import a local TAR container image\n"
" import-raw FILE [NAME] Import a local RAW container image\n"
" pull-tar URL [NAME] Download a TAR container image\n"
" pull-raw URL [NAME] Download a RAW container or VM image\n"
" pull-dkr REMOTE [NAME] Download a DKR container image\n"
@ -2267,6 +2403,8 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
{ "start", 2, VERB_ANY, 0, start_machine },
{ "enable", 2, VERB_ANY, 0, enable_machine },
{ "disable", 2, VERB_ANY, 0, enable_machine },
{ "import-tar", 2, 3, 0, import_tar },
{ "import-raw", 2, 3, 0, import_raw },
{ "pull-tar", 2, 3, 0, pull_tar },
{ "pull-raw", 2, 3, 0, pull_raw },
{ "pull-dkr", 2, 3, 0, pull_dkr },

View File

@ -23,5 +23,8 @@
#include "sd-bus.h"
/* Grow the /var/lib/machines directory after each 10MiB written */
#define GROW_INTERVAL_BYTES (UINT64_C(10) * UINT64_C(1024) * UINT64_C(1024))
int setup_machine_directory(uint64_t size, sd_bus_error *error);
int grow_machine_directory(void);