Browse Source

importd: add API for exporting container/VM images

Also, expose it in machinectl.
keep-around/964a6d9fb555cc86528eb1cc1f6d044f85584842
Lennart Poettering 8 years ago
parent
commit
587fec427c
  1. 1
      .gitignore
  2. 30
      Makefile.am
  3. 345
      src/import/export-raw.c
  4. 37
      src/import/export-raw.h
  5. 328
      src/import/export-tar.c
  6. 37
      src/import/export-tar.h
  7. 319
      src/import/export.c
  8. 76
      src/import/import-common.c
  9. 3
      src/import/import-common.h
  10. 284
      src/import/import-compress.c
  11. 7
      src/import/import-compress.h
  12. 9
      src/import/import-raw.c
  13. 15
      src/import/import-tar.c
  14. 6
      src/import/import.c
  15. 116
      src/import/importd.c
  16. 10
      src/import/org.freedesktop.import1.policy.in
  17. 2
      src/import/pull-dkr.c
  18. 2
      src/import/pull-tar.c
  19. 2
      src/import/pull.c
  20. 151
      src/machine/machinectl.c
  21. 53
      src/shared/btrfs-util.c
  22. 2
      src/shared/btrfs-util.h

1
.gitignore

@ -75,6 +75,7 @@
/systemd-efi-boot-generator
/systemd-escape
/systemd-evcat
/systemd-export
/systemd-firstboot
/systemd-fsck
/systemd-fsckd

30
Makefile.am

@ -5371,7 +5371,8 @@ if HAVE_GCRYPT
rootlibexec_PROGRAMS += \
systemd-importd \
systemd-pull \
systemd-import
systemd-import \
systemd-export
systemd_importd_SOURCES = \
src/import/importd.c
@ -5379,7 +5380,8 @@ systemd_importd_SOURCES = \
systemd_importd_CFLAGS = \
$(AM_CFLAGS) \
-D SYSTEMD_PULL_PATH=\"$(rootlibexecdir)/systemd-pull\" \
-D SYSTEMD_IMPORT_PATH=\"$(rootlibexecdir)/systemd-import\"
-D SYSTEMD_IMPORT_PATH=\"$(rootlibexecdir)/systemd-import\" \
-D SYSTEMD_EXPORT_PATH=\"$(rootlibexecdir)/systemd-export\"
systemd_importd_LDADD = \
libsystemd-internal.la \
@ -5454,6 +5456,30 @@ systemd_import_LDADD = \
$(ZLIB_LIBS) \
-lbz2
systemd_export_SOURCES = \
src/import/export.c \
src/import/export-tar.c \
src/import/export-tar.h \
src/import/export-raw.c \
src/import/export-raw.h \
src/import/import-common.c \
src/import/import-common.h \
src/import/import-compress.c \
src/import/import-compress.h
systemd_export_CFLAGS = \
$(AM_CFLAGS) \
$(XZ_CFLAGS) \
$(ZLIB_CFLAGS)
systemd_export_LDADD = \
libsystemd-internal.la \
libsystemd-label.la \
libsystemd-shared.la \
$(XZ_LIBS) \
$(ZLIB_LIBS) \
-lbz2
dist_rootlibexec_DATA = \
src/import/import-pubring.gpg

345
src/import/export-raw.c

@ -0,0 +1,345 @@
/*-*- 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/sendfile.h>
#include <libgen.h>
#undef basename
#include "sd-daemon.h"
#include "util.h"
#include "ratelimit.h"
#include "btrfs-util.h"
#include "copy.h"
#include "import-common.h"
#include "export-raw.h"
#define COPY_BUFFER_SIZE (16*1024)
struct RawExport {
sd_event *event;
RawExportFinished on_finished;
void *userdata;
char *path;
int input_fd;
int output_fd;
ImportCompress compress;
sd_event_source *output_event_source;
void *buffer;
size_t buffer_size;
size_t buffer_allocated;
uint64_t written_compressed;
uint64_t written_uncompressed;
unsigned last_percent;
RateLimit progress_rate_limit;
struct stat st;
bool eof;
bool tried_reflink;
bool tried_sendfile;
};
RawExport *raw_export_unref(RawExport *e) {
if (!e)
return NULL;
sd_event_source_unref(e->output_event_source);
import_compress_free(&e->compress);
sd_event_unref(e->event);
safe_close(e->input_fd);
free(e->buffer);
free(e->path);
free(e);
return NULL;
}
int raw_export_new(
RawExport **ret,
sd_event *event,
RawExportFinished on_finished,
void *userdata) {
_cleanup_(raw_export_unrefp) RawExport *e = NULL;
int r;
assert(ret);
e = new0(RawExport, 1);
if (!e)
return -ENOMEM;
e->output_fd = e->input_fd = -1;
e->on_finished = on_finished;
e->userdata = userdata;
RATELIMIT_INIT(e->progress_rate_limit, 100 * USEC_PER_MSEC, 1);
e->last_percent = (unsigned) -1;
if (event)
e->event = sd_event_ref(event);
else {
r = sd_event_default(&e->event);
if (r < 0)
return r;
}
*ret = e;
e = NULL;
return 0;
}
static void raw_export_report_progress(RawExport *e) {
unsigned percent;
assert(e);
if (e->written_uncompressed >= (uint64_t) e->st.st_size)
percent = 100;
else
percent = (unsigned) ((e->written_uncompressed * UINT64_C(100)) / (uint64_t) e->st.st_size);
if (percent == e->last_percent)
return;
if (!ratelimit_test(&e->progress_rate_limit))
return;
sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
log_info("Exported %u%%.", percent);
e->last_percent = percent;
}
static int raw_export_process(RawExport *e) {
ssize_t l;
int r;
assert(e);
if (!e->tried_reflink && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) {
/* If we shall take an uncompressed snapshot we can
* reflink source to destination directly. Let's see
* if this works. */
r = btrfs_reflink(e->input_fd, e->output_fd);
if (r >= 0) {
r = 0;
goto finish;
}
e->tried_reflink = true;
}
if (!e->tried_sendfile && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) {
l = sendfile(e->output_fd, e->input_fd, NULL, COPY_BUFFER_SIZE);
if (l < 0) {
if (errno == EAGAIN)
return 0;
e->tried_sendfile = true;
} else if (l == 0) {
r = 0;
goto finish;
} else {
e->written_uncompressed += l;
e->written_compressed += l;
raw_export_report_progress(e);
return 0;
}
}
while (e->buffer_size <= 0) {
uint8_t input[COPY_BUFFER_SIZE];
if (e->eof) {
r = 0;
goto finish;
}
l = read(e->input_fd, input, sizeof(input));
if (l < 0) {
r = log_error_errno(errno, "Failed to read raw file: %m");
goto finish;
}
if (l == 0) {
e->eof = true;
r = import_compress_finish(&e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated);
} else {
e->written_uncompressed += l;
r = import_compress(&e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated);
}
if (r < 0) {
r = log_error_errno(r, "Failed to encode: %m");
goto finish;
}
}
l = write(e->output_fd, e->buffer, e->buffer_size);
if (l < 0) {
if (errno == EAGAIN)
return 0;
r = log_error_errno(errno, "Failed to write output file: %m");
goto finish;
}
assert((size_t) l <= e->buffer_size);
memmove(e->buffer, (uint8_t*) e->buffer + l, e->buffer_size - l);
e->buffer_size -= l;
e->written_compressed += l;
raw_export_report_progress(e);
return 0;
finish:
if (r >= 0) {
(void) copy_times(e->input_fd, e->output_fd);
(void) copy_xattr(e->input_fd, e->output_fd);
}
if (e->on_finished)
e->on_finished(e, r, e->userdata);
else
sd_event_exit(e->event, r);
return 0;
}
static int raw_export_on_output(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
RawExport *i = userdata;
return raw_export_process(i);
}
static int raw_export_on_defer(sd_event_source *s, void *userdata) {
RawExport *i = userdata;
return raw_export_process(i);
}
static int reflink_snapshot(int fd, const char *path) {
char *p, *d;
int new_fd, r;
p = strdupa(path);
d = dirname(p);
new_fd = open(d, O_TMPFILE|O_CLOEXEC|O_NOCTTY|O_RDWR, 0600);
if (new_fd < 0) {
_cleanup_free_ char *t = NULL;
r = tempfn_random(path, &t);
if (r < 0)
return r;
new_fd = open(t, O_CLOEXEC|O_CREAT|O_NOCTTY|O_RDWR, 0600);
if (new_fd < 0)
return -errno;
(void) unlink(t);
}
r = btrfs_reflink(fd, new_fd);
if (r < 0) {
safe_close(new_fd);
return r;
}
return new_fd;
}
int raw_export_start(RawExport *e, const char *path, int fd, ImportCompressType compress) {
_cleanup_close_ int sfd = -1, tfd = -1;
int r;
assert(e);
assert(path);
assert(fd >= 0);
assert(compress < _IMPORT_COMPRESS_TYPE_MAX);
assert(compress != IMPORT_COMPRESS_UNKNOWN);
if (e->output_fd >= 0)
return -EBUSY;
r = fd_nonblock(fd, true);
if (r < 0)
return r;
r = free_and_strdup(&e->path, path);
if (r < 0)
return r;
sfd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (sfd < 0)
return -errno;
if (fstat(sfd, &e->st) < 0)
return -errno;
if (!S_ISREG(e->st.st_mode))
return -ENOTTY;
/* Try to take a reflink snapshot of the file, if we can t make the export atomic */
tfd = reflink_snapshot(sfd, path);
if (tfd >= 0) {
e->input_fd = tfd;
tfd = -1;
} else {
e->input_fd = sfd;
sfd = -1;
}
r = import_compress_init(&e->compress, compress);
if (r < 0)
return r;
r = sd_event_add_io(e->event, &e->output_event_source, fd, EPOLLOUT, raw_export_on_output, e);
if (r == -EPERM) {
r = sd_event_add_defer(e->event, &e->output_event_source, raw_export_on_defer, e);
if (r < 0)
return r;
r = sd_event_source_set_enabled(e->output_event_source, SD_EVENT_ON);
}
if (r < 0)
return r;
e->output_fd = fd;
return r;
}

37
src/import/export-raw.h

@ -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-compress.h"
typedef struct RawExport RawExport;
typedef void (*RawExportFinished)(RawExport *export, int error, void *userdata);
int raw_export_new(RawExport **export, sd_event *event, RawExportFinished on_finished, void *userdata);
RawExport* raw_export_unref(RawExport *export);
DEFINE_TRIVIAL_CLEANUP_FUNC(RawExport*, raw_export_unref);
int raw_export_start(RawExport *export, const char *path, int fd, ImportCompressType compress);

328
src/import/export-tar.c

@ -0,0 +1,328 @@
/*-*- 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/sendfile.h>
#include "sd-daemon.h"
#include "util.h"
#include "ratelimit.h"
#include "btrfs-util.h"
#include "import-common.h"
#include "export-tar.h"
#define COPY_BUFFER_SIZE (16*1024)
struct TarExport {
sd_event *event;
TarExportFinished on_finished;
void *userdata;
char *path;
char *temp_path;
int output_fd;
int tar_fd;
ImportCompress compress;
sd_event_source *output_event_source;
void *buffer;
size_t buffer_size;
size_t buffer_allocated;
uint64_t written_compressed;
uint64_t written_uncompressed;
pid_t tar_pid;
struct stat st;
uint64_t quota_referenced;
unsigned last_percent;
RateLimit progress_rate_limit;
bool eof;
bool tried_splice;
};
TarExport *tar_export_unref(TarExport *e) {
if (!e)
return NULL;
sd_event_source_unref(e->output_event_source);
if (e->tar_pid > 1) {
(void) kill_and_sigcont(e->tar_pid, SIGKILL);
(void) wait_for_terminate(e->tar_pid, NULL);
}
if (e->temp_path) {
(void) btrfs_subvol_remove(e->temp_path);
free(e->temp_path);
}
import_compress_free(&e->compress);
sd_event_unref(e->event);
safe_close(e->tar_fd);
free(e->buffer);
free(e->path);
free(e);
return NULL;
}
int tar_export_new(
TarExport **ret,
sd_event *event,
TarExportFinished on_finished,
void *userdata) {
_cleanup_(tar_export_unrefp) TarExport *e = NULL;
int r;
assert(ret);
e = new0(TarExport, 1);
if (!e)
return -ENOMEM;
e->output_fd = e->tar_fd = -1;
e->on_finished = on_finished;
e->userdata = userdata;
e->quota_referenced = (uint64_t) -1;
RATELIMIT_INIT(e->progress_rate_limit, 100 * USEC_PER_MSEC, 1);
e->last_percent = (unsigned) -1;
if (event)
e->event = sd_event_ref(event);
else {
r = sd_event_default(&e->event);
if (r < 0)
return r;
}
*ret = e;
e = NULL;
return 0;
}
static void tar_export_report_progress(TarExport *e) {
unsigned percent;
assert(e);
/* Do we have any quota info? I fnot, we don't know anything about the progress */
if (e->quota_referenced == (uint64_t) -1)
return;
if (e->written_uncompressed >= e->quota_referenced)
percent = 100;
else
percent = (unsigned) ((e->written_uncompressed * UINT64_C(100)) / e->quota_referenced);
if (percent == e->last_percent)
return;
if (!ratelimit_test(&e->progress_rate_limit))
return;
sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
log_info("Exported %u%%.", percent);
e->last_percent = percent;
}
static int tar_export_process(TarExport *e) {
ssize_t l;
int r;
assert(e);
if (!e->tried_splice && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) {
l = splice(e->tar_fd, NULL, e->output_fd, NULL, COPY_BUFFER_SIZE, 0);
if (l < 0) {
if (errno == EAGAIN)
return 0;
e->tried_splice = true;
} else if (l == 0) {
r = 0;
goto finish;
} else {
e->written_uncompressed += l;
e->written_compressed += l;
tar_export_report_progress(e);
return 0;
}
}
while (e->buffer_size <= 0) {
uint8_t input[COPY_BUFFER_SIZE];
if (e->eof) {
r = 0;
goto finish;
}
l = read(e->tar_fd, input, sizeof(input));
if (l < 0) {
r = log_error_errno(errno, "Failed to read tar file: %m");
goto finish;
}
if (l == 0) {
e->eof = true;
r = import_compress_finish(&e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated);
} else {
e->written_uncompressed += l;
r = import_compress(&e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated);
}
if (r < 0) {
r = log_error_errno(r, "Failed to encode: %m");
goto finish;
}
}
l = write(e->output_fd, e->buffer, e->buffer_size);
if (l < 0) {
if (errno == EAGAIN)
return 0;
r = log_error_errno(errno, "Failed to write output file: %m");
goto finish;
}
assert((size_t) l <= e->buffer_size);
memmove(e->buffer, (uint8_t*) e->buffer + l, e->buffer_size - l);
e->buffer_size -= l;
e->written_compressed += l;
tar_export_report_progress(e);
return 0;
finish:
if (e->on_finished)
e->on_finished(e, r, e->userdata);
else
sd_event_exit(e->event, r);
return 0;
}
static int tar_export_on_output(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
TarExport *i = userdata;
return tar_export_process(i);
}
static int tar_export_on_defer(sd_event_source *s, void *userdata) {
TarExport *i = userdata;
return tar_export_process(i);
}
int tar_export_start(TarExport *e, const char *path, int fd, ImportCompressType compress) {
_cleanup_close_ int sfd = -1;
int r;
assert(e);
assert(path);
assert(fd >= 0);
assert(compress < _IMPORT_COMPRESS_TYPE_MAX);
assert(compress != IMPORT_COMPRESS_UNKNOWN);
if (e->output_fd >= 0)
return -EBUSY;
sfd = open(path, O_DIRECTORY|O_RDONLY|O_NOCTTY|O_CLOEXEC);
if (sfd < 0)
return -errno;
if (fstat(sfd, &e->st) < 0)
return -errno;
r = fd_nonblock(fd, true);
if (r < 0)
return r;
r = free_and_strdup(&e->path, path);
if (r < 0)
return r;
e->quota_referenced = (uint64_t) -1;
if (e->st.st_ino == 256) { /* might be a btrfs subvolume? */
BtrfsQuotaInfo q;
r = btrfs_subvol_get_quota_fd(sfd, &q);
if (r >= 0)
e->quota_referenced = q.referred;
free(e->temp_path);
e->temp_path = NULL;
r = tempfn_random(path, &e->temp_path);
if (r < 0)
return r;
/* Let's try to make a snapshot, if we can, so that the export is atomic */
r = btrfs_subvol_snapshot_fd(sfd, e->temp_path, true, false);
if (r < 0) {
log_debug_errno(r, "Couldn't create snapshot %s of %s, not exporting atomically: %m", e->temp_path, path);
free(e->temp_path);
e->temp_path = NULL;
}
}
r = import_compress_init(&e->compress, compress);
if (r < 0)
return r;
r = sd_event_add_io(e->event, &e->output_event_source, fd, EPOLLOUT, tar_export_on_output, e);
if (r == -EPERM) {
r = sd_event_add_defer(e->event, &e->output_event_source, tar_export_on_defer, e);
if (r < 0)
return r;
r = sd_event_source_set_enabled(e->output_event_source, SD_EVENT_ON);
}
if (r < 0)
return r;
e->tar_fd = import_fork_tar_c(e->temp_path ?: e->path, &e->tar_pid);
if (e->tar_fd < 0) {
e->output_event_source = sd_event_source_unref(e->output_event_source);
return e->tar_fd;
}
e->output_fd = fd;
return r;
}

37
src/import/export-tar.h

@ -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-compress.h"
typedef struct TarExport TarExport;
typedef void (*TarExportFinished)(TarExport *export, int error, void *userdata);
int tar_export_new(TarExport **export, sd_event *event, TarExportFinished on_finished, void *userdata);
TarExport* tar_export_unref(TarExport *export);
DEFINE_TRIVIAL_CLEANUP_FUNC(TarExport*, tar_export_unref);
int tar_export_start(TarExport *export, const char *path, int fd, ImportCompressType compress);

319
src/import/export.c

@ -0,0 +1,319 @@
/*-*- 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 "export-tar.h"
#include "export-raw.h"
static ImportCompressType arg_compress = IMPORT_COMPRESS_UNKNOWN;
static void determine_compression_from_filename(const char *p) {
if (arg_compress != IMPORT_COMPRESS_UNKNOWN)
return;
if (!p) {
arg_compress = IMPORT_COMPRESS_UNCOMPRESSED;
return;
}
if (endswith(p, ".xz"))
arg_compress = IMPORT_COMPRESS_XZ;
else if (endswith(p, ".gz"))
arg_compress = IMPORT_COMPRESS_GZIP;
else if (endswith(p, ".bz2"))
arg_compress = IMPORT_COMPRESS_BZIP2;
else
arg_compress = IMPORT_COMPRESS_UNCOMPRESSED;
}
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(TarExport *export, int error, void *userdata) {
sd_event *event = userdata;
assert(export);
if (error == 0)
log_info("Operation completed successfully.");
sd_event_exit(event, abs(error));
}
static int export_tar(int argc, char *argv[], void *userdata) {
_cleanup_(tar_export_unrefp) TarExport *export = NULL;
_cleanup_event_unref_ sd_event *event = NULL;
_cleanup_(image_unrefp) Image *image = NULL;
const char *path = NULL, *local = NULL;
_cleanup_close_ int open_fd = -1;
int r, fd;
if (machine_name_is_valid(argv[1])) {
r = image_find(argv[1], &image);
if (r < 0)
return log_error_errno(r, "Failed to look for machine %s: %m", argv[1]);
if (r == 0) {
log_error("Machine image %s not found.", argv[1]);
return -ENOENT;
}
local = image->path;
} else
local = argv[1];
if (argc >= 3)
path = argv[2];
if (isempty(path) || streq(path, "-"))
path = NULL;
determine_compression_from_filename(path);
if (path) {
open_fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666);
if (open_fd < 0)
return log_error_errno(errno, "Failed to open tar image for export: %m");
fd = open_fd;
log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, import_compress_type_to_string(arg_compress));
} else {
_cleanup_free_ char *pretty = NULL;
fd = STDOUT_FILENO;
(void) readlink_malloc("/proc/self/fd/1", &pretty);
log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress));
}
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_export_new(&export, event, on_tar_finished, event);
if (r < 0)
return log_error_errno(r, "Failed to allocate exporter: %m");
r = tar_export_start(export, local, fd, arg_compress);
if (r < 0)
return log_error_errno(r, "Failed to export 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(RawExport *export, int error, void *userdata) {
sd_event *event = userdata;
assert(export);
if (error == 0)
log_info("Operation completed successfully.");
sd_event_exit(event, abs(error));
}
static int export_raw(int argc, char *argv[], void *userdata) {
_cleanup_(raw_export_unrefp) RawExport *export = NULL;
_cleanup_event_unref_ sd_event *event = NULL;
_cleanup_(image_unrefp) Image *image = NULL;
const char *path = NULL, *local = NULL;
_cleanup_close_ int open_fd = -1;
int r, fd;
if (machine_name_is_valid(argv[1])) {
r = image_find(argv[1], &image);
if (r < 0)
return log_error_errno(r, "Failed to look for machine %s: %m", argv[1]);
if (r == 0) {
log_error("Machine image %s not found.", argv[1]);
return -ENOENT;
}
local = image->path;
} else
local = argv[1];
if (argc >= 3)
path = argv[2];
if (isempty(path) || streq(path, "-"))
path = NULL;
determine_compression_from_filename(path);
if (path) {
open_fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666);
if (open_fd < 0)
return log_error_errno(errno, "Failed to open raw image for export: %m");
fd = open_fd;
log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, import_compress_type_to_string(arg_compress));
} else {
_cleanup_free_ char *pretty = NULL;
fd = STDOUT_FILENO;
(void) readlink_malloc("/proc/self/fd/1", &pretty);
log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress));
}
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_export_new(&export, event, on_raw_finished, event);
if (r < 0)
return log_error_errno(r, "Failed to allocate exporter: %m");
r = raw_export_start(export, local, fd, arg_compress);
if (r < 0)
return log_error_errno(r, "Failed to export 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"
"Export container or virtual machine images.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --format=FORMAT Select format\n\n"
"Commands:\n"
" tar NAME [FILE] Export a TAR image\n"
" raw NAME [FILE] Export a RAW image\n",
program_invocation_short_name);
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_FORMAT,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "format", required_argument, NULL, ARG_FORMAT },
{}
};
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_FORMAT:
if (streq(optarg, "uncompressed"))
arg_compress = IMPORT_COMPRESS_UNCOMPRESSED;
else if (streq(optarg, "xz"))
arg_compress = IMPORT_COMPRESS_XZ;
else if (streq(optarg, "gzip"))
arg_compress = IMPORT_COMPRESS_GZIP;
else if (streq(optarg, "bzip2"))
arg_compress = IMPORT_COMPRESS_BZIP2;
else {
log_error("Unknown format: %s", optarg);
return -EINVAL;
}
break;
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
return 1;
}
static int export_main(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "tar", 2, 3, 0, export_tar },
{ "raw", 2, 3, 0, export_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 = export_main(argc, argv);
finish:
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

76
src/import/import-common.c

@ -69,7 +69,7 @@ int import_make_read_only(const char *path) {
return import_make_read_only_fd(fd);
}
int import_fork_tar(const char *path, pid_t *ret) {
int import_fork_tar_x(const char *path, pid_t *ret) {
_cleanup_close_pair_ int pipefd[2] = { -1, -1 };
pid_t pid;
int r;
@ -148,3 +148,77 @@ int import_fork_tar(const char *path, pid_t *ret) {
return r;
}
int import_fork_tar_c(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_DAC_OVERRIDE);
/* Child */
reset_all_signal_handlers();
reset_signal_mask();
assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
pipefd[0] = safe_close(pipefd[0]);
if (dup2(pipefd[1], STDOUT_FILENO) != STDOUT_FILENO) {
log_error_errno(errno, "Failed to dup2() fd: %m");
_exit(EXIT_FAILURE);
}
if (pipefd[1] != STDOUT_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 (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) {
log_error_errno(errno, "Failed to dup2() fd: %m");
_exit(EXIT_FAILURE);
}
if (null_fd != STDIN_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", "--sparse", "-C", path, "-c", ".", NULL);
log_error_errno(errno, "Failed to execute tar: %m");
_exit(EXIT_FAILURE);
}
pipefd[1] = safe_close(pipefd[1]);
r = pipefd[0];
pipefd[0] = -1;
*ret = pid;
return r;
}

3
src/import/import-common.h

@ -24,4 +24,5 @@
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);
int import_fork_tar_c(const char *path, pid_t *ret);
int import_fork_tar_x(const char *path, pid_t *ret);

284
src/import/import-compress.c

@ -27,10 +27,17 @@ void import_compress_free(ImportCompress *c) {
if (c->type == IMPORT_COMPRESS_XZ)
lzma_end(&c->xz);
else if (c->type == IMPORT_COMPRESS_GZIP)
inflateEnd(&c->gzip);
else if (c->type == IMPORT_COMPRESS_BZIP2)
BZ2_bzDecompressEnd(&c->bzip2);
else if (c->type == IMPORT_COMPRESS_GZIP) {
if (c->encoding)
deflateEnd(&c->gzip);
else
inflateEnd(&c->gzip);
} else if (c->type == IMPORT_COMPRESS_BZIP2) {
if (c->encoding)
BZ2_bzCompressEnd(&c->bzip2);
else
BZ2_bzDecompressEnd(&c->bzip2);
}
c->type = IMPORT_COMPRESS_UNKNOWN;
}
@ -85,6 +92,8 @@ int import_uncompress_detect(ImportCompress *c, const void *data, size_t size) {
} else
c->type = IMPORT_COMPRESS_UNCOMPRESSED;
c->encoding = false;
return 1;
}
@ -98,6 +107,9 @@ int import_uncompress(ImportCompress *c, const void *data, size_t size, ImportCo
if (r <= 0)
return r;
if (c->encoding)
return -EINVAL;
if (size <= 0)
return 1;
@ -183,6 +195,270 @@ int import_uncompress(ImportCompress *c, const void *data, size_t size, ImportCo
return 1;
}
int import_compress_init(ImportCompress *c, ImportCompressType t) {
int r;
assert(c);
switch (t) {
case IMPORT_COMPRESS_XZ: {
lzma_ret xzr;
xzr = lzma_easy_encoder(&c->xz, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64);
if (xzr != LZMA_OK)
return -EIO;
c->type = IMPORT_COMPRESS_XZ;
break;
}
case IMPORT_COMPRESS_GZIP:
r = deflateInit2(&c->gzip, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
if (r != Z_OK)
return -EIO;
c->type = IMPORT_COMPRESS_GZIP;
break;
case IMPORT_COMPRESS_BZIP2:
r = BZ2_bzCompressInit(&c->bzip2, 9, 0, 0);
if (r != BZ_OK)
return -EIO;
c->type = IMPORT_COMPRESS_BZIP2;
break;
case IMPORT_COMPRESS_UNCOMPRESSED:
c->type = IMPORT_COMPRESS_UNCOMPRESSED;
break;
default:
return -ENOTSUP;
}
c->encoding = true;
return 0;
}
static int enlarge_buffer(void **buffer, size_t *buffer_size, size_t *buffer_allocated) {
size_t l;
void *p;
if (*buffer_allocated > *buffer_size)
return 0;
l = MAX(16*1024U, (*buffer_size * 2));
p = realloc(*buffer, l);
if (!p)
return -ENOMEM;
*buffer = p;
*buffer_allocated = l;
return 1;
}
int import_compress(ImportCompress *c, const void *data, size_t size, void **buffer, size_t *buffer_size, size_t *buffer_allocated) {
int r;
assert(c);
assert(buffer);
assert(buffer_size);
assert(buffer_allocated);
if (!c->encoding)
return -EINVAL;
if (size <= 0)
return 0;
assert(data);
*buffer_size = 0;
switch (c->type) {
case IMPORT_COMPRESS_XZ:
c->xz.next_in = data;
c->xz.avail_in = size;
while (c->xz.avail_in > 0) {
lzma_ret lzr;
r = enlarge_buffer(buffer, buffer_size, buffer_allocated);
if (r < 0)
return r;
c->xz.next_out = (uint8_t*) *buffer + *buffer_size;
c->xz.avail_out = *buffer_allocated - *buffer_size;
lzr = lzma_code(&c->xz, LZMA_RUN);
if (lzr != LZMA_OK)
return -EIO;
*buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out;
}
break;
case IMPORT_COMPRESS_GZIP:
c->gzip.next_in = (void*) data;
c->gzip.avail_in = size;
while (c->gzip.avail_in > 0) {
r = enlarge_buffer(buffer, buffer_size, buffer_allocated);
if (r < 0)
return r;
c->gzip.next_out = (uint8_t*) *buffer + *buffer_size;
c->gzip.avail_out = *buffer_allocated - *buffer_size;
r = deflate(&c->gzip, Z_NO_FLUSH);
if (r != Z_OK)
return -EIO;
*buffer_size += (*buffer_allocated - *buffer_size) - c->gzip.avail_out;
}
break;
case IMPORT_COMPRESS_BZIP2:
c->bzip2.next_in = (void*) data;
c->bzip2.avail_in = size;
while (c->bzip2.avail_in > 0) {
r = enlarge_buffer(buffer, buffer_size, buffer_allocated);
if (r < 0)
return r;
c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size);
c->bzip2.avail_out = *buffer_allocated - *buffer_size;
r = BZ2_bzCompress(&c->bzip2, BZ_RUN);
if (r != BZ_RUN_OK)
return -EIO;
*buffer_size += (*buffer_allocated - *buffer_size) - c->bzip2.avail_out;
}
break;
case IMPORT_COMPRESS_UNCOMPRESSED:
if (*buffer_allocated < size) {
void *p;
p = realloc(*buffer, size);
if (!p)
return -ENOMEM;
*buffer = p;
*buffer_allocated = size;
}
memcpy(*buffer, data, size);
*buffer_size = size;
break;
default:
return -ENOTSUP;
}
return 0;
}
int import_compress_finish(ImportCompress *c, void **buffer, size_t *buffer_size, size_t *buffer_allocated) {
int r;
assert(c);
assert(buffer);
assert(buffer_size);
assert(buffer_allocated);
if (!c->encoding)
return -EINVAL;
*buffer_size = 0;
switch (c->type) {
case IMPORT_COMPRESS_XZ: {
lzma_ret lzr;
c->xz.avail_in = 0;
do {
r = enlarge_buffer(buffer, buffer_size, buffer_allocated);
if (r < 0)
return r;
c->xz.next_out = (uint8_t*) *buffer + *buffer_size;
c->xz.avail_out = *buffer_allocated - *buffer_size;
lzr = lzma_code(&c->xz, LZMA_FINISH);
if (lzr != LZMA_OK && lzr != LZMA_STREAM_END)
return -EIO;
*buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out;
} while (lzr != LZMA_STREAM_END);
break;
}
case IMPORT_COMPRESS_GZIP:
c->gzip.avail_in = 0;
do {
r = enlarge_buffer(buffer, buffer_size, buffer_allocated);
if (r < 0)
return r;
c->gzip.next_out = (uint8_t*) *buffer + *buffer_size;
c->gzip.avail_out = *buffer_allocated - *buffer_size;
r = deflate(&c->gzip, Z_FINISH);
if (r != Z_OK && r != Z_STREAM_END)
return -EIO;
*buffer_size += (*buffer_allocated - *buffer_size) - c->gzip.avail_out;
} while (r != Z_STREAM_END);
break;
case IMPORT_COMPRESS_BZIP2:
c->bzip2.avail_in = 0;
do {
r = enlarge_buffer(buffer, buffer_size, buffer_allocated);
if (r < 0)
return r;
c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size);
c->bzip2.avail_out = *buffer_allocated - *buffer_size;
r = BZ2_bzCompress(&c->bzip2, BZ_FINISH);
if (r != BZ_FINISH_OK && r != BZ_STREAM_END)
return -EIO;
*buffer_size += (*buffer_allocated - *buffer_size) - c->bzip2.avail_out;
} while (r != BZ_STREAM_END);
break;
case IMPORT_COMPRESS_UNCOMPRESSED:
break;
default:
return -ENOTSUP;
}
return 0;
}
static const char* const import_compress_type_table[_IMPORT_COMPRESS_TYPE_MAX] = {
[IMPORT_COMPRESS_UNKNOWN] = "unknown",
[IMPORT_COMPRESS_UNCOMPRESSED] = "uncompressed",

7
src/import/import-compress.h

@ -41,7 +41,7 @@ typedef enum ImportCompressType {
typedef struct ImportCompress {
ImportCompressType type;
bool encoding;
union {
lzma_stream xz;
z_stream gzip;
@ -54,8 +54,11 @@ typedef int (*ImportCompressCallback)(const void *data, size_t size, void *userd
void import_compress_free(ImportCompress *c);
int import_uncompress_detect(ImportCompress *c, const void *data, size_t size);
int import_uncompress(ImportCompress *c, const void *data, size_t size, ImportCompressCallback callback, void *userdata);