/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include "alloc-util.h" #include "ansi-color.h" #include "build.h" #include "btrfs-util.h" #include "discover-image.h" #include "fd-util.h" #include "format-util.h" #include "fs-util.h" #include "hostname-util.h" #include "import-common.h" #include "import-util.h" #include "install-file.h" #include "main-func.h" #include "mkdir-label.h" #include "parse-argument.h" #include "ratelimit.h" #include "rm-rf.h" #include "signal-util.h" #include "string-util.h" #include "terminal-util.h" #include "tmpfile-util.h" #include "verbs.h" static bool arg_force = false; static bool arg_read_only = false; static bool arg_btrfs_subvol = true; static bool arg_btrfs_quota = true; static bool arg_sync = true; static bool arg_direct = false; static const char *arg_image_root = NULL; static ImageClass arg_class = IMAGE_MACHINE; typedef struct ProgressInfo { RateLimit limit; char *path; uint64_t size; bool started; bool logged_incomplete; } ProgressInfo; static void progress_info_free(ProgressInfo *p) { free(p->path); } static void progress_show(ProgressInfo *p) { assert(p); /* Show progress only every now and then. */ if (!ratelimit_below(&p->limit)) return; /* Suppress the first message, start with the second one */ if (!p->started) { p->started = true; return; } /* Mention the list is incomplete before showing first output. */ if (!p->logged_incomplete) { log_notice("(Note: file list shown below is incomplete, and is intended as sporadic progress report only.)"); p->logged_incomplete = true; } if (p->size == 0) log_info("Copying tree, currently at '%s'...", p->path); else log_info("Copying tree, currently at '%s' (@%s)...", p->path, FORMAT_BYTES(p->size)); } static int progress_path(const char *path, const struct stat *st, void *userdata) { ProgressInfo *p = ASSERT_PTR(userdata); int r; r = free_and_strdup(&p->path, path); if (r < 0) return r; p->size = 0; progress_show(p); return 0; } static int progress_bytes(uint64_t nbytes, void *userdata) { ProgressInfo *p = ASSERT_PTR(userdata); assert(p->size != UINT64_MAX); p->size += nbytes; progress_show(p); return 0; } static int import_fs(int argc, char *argv[], void *userdata) { _cleanup_(rm_rf_subvolume_and_freep) char *temp_path = NULL; _cleanup_(progress_info_free) ProgressInfo progress = {}; _cleanup_free_ char *l = NULL, *final_path = NULL; const char *path = NULL, *local = NULL, *dest = NULL; _cleanup_close_ int open_fd = -EBADF; int r, fd; if (argc >= 2) path = empty_or_dash_to_null(argv[1]); if (argc >= 3) local = empty_or_dash_to_null(argv[2]); else if (path) { r = path_extract_filename(path, &l); if (r < 0) return log_error_errno(r, "Failed to extract filename from path '%s': %m", path); local = l; } if (arg_direct) { if (!local) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No local path specified."); if (path_is_absolute(local)) final_path = strdup(local); else final_path = path_join(arg_image_root, local); if (!final_path) return log_oom(); if (!path_is_valid(final_path)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Local path name '%s' is not valid.", final_path); } else { if (local) { if (!image_name_is_valid(local)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Local image name '%s' is not valid.", local); } else local = "imported"; final_path = path_join(arg_image_root, local); if (!final_path) return log_oom(); if (!arg_force) { r = image_find(arg_class, local, NULL, NULL); if (r < 0) { if (r != -ENOENT) return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); } else return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Image '%s' already exists.", local); } } if (path) { open_fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC); if (open_fd < 0) return log_error_errno(errno, "Failed to open directory 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) fd_get_path(fd, &pretty); log_info("Importing '%s', saving as '%s'.", strempty(pretty), local); } log_info("Operating on image directory '%s'.", arg_image_root); if (!arg_sync) log_info("File system synchronization on completion is off."); if (arg_direct) { if (arg_force) (void) rm_rf(final_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); dest = final_path; } else { r = tempfn_random(final_path, NULL, &temp_path); if (r < 0) return log_oom(); dest = temp_path; } (void) mkdir_parents_label(dest, 0700); progress.limit = (const RateLimit) { 200*USEC_PER_MSEC, 1 }; { BLOCK_SIGNALS(SIGINT, SIGTERM); if (arg_btrfs_subvol) r = btrfs_subvol_snapshot_at_full( fd, NULL, AT_FDCWD, dest, BTRFS_SNAPSHOT_FALLBACK_COPY| BTRFS_SNAPSHOT_FALLBACK_DIRECTORY| BTRFS_SNAPSHOT_RECURSIVE| BTRFS_SNAPSHOT_SIGINT| BTRFS_SNAPSHOT_SIGTERM, progress_path, progress_bytes, &progress); else r = copy_directory_at_full( fd, NULL, AT_FDCWD, dest, COPY_REFLINK| COPY_SAME_MOUNT| COPY_HARDLINKS| COPY_SIGINT| COPY_SIGTERM| (arg_direct ? COPY_MERGE_EMPTY : 0), progress_path, progress_bytes, &progress); if (r == -EINTR) /* SIGINT/SIGTERM hit */ return log_error_errno(r, "Copy cancelled."); if (r < 0) return log_error_errno(r, "Failed to copy directory: %m"); } r = import_mangle_os_tree(dest); if (r < 0) return r; if (arg_btrfs_quota) { if (!arg_direct) (void) import_assign_pool_quota_and_warn(arg_image_root); (void) import_assign_pool_quota_and_warn(dest); } r = install_file(AT_FDCWD, dest, AT_FDCWD, arg_direct ? NULL : final_path, /* pass NULL as target in case of direct * mode since file is already in place */ (arg_force ? INSTALL_REPLACE : 0) | (arg_read_only ? INSTALL_READ_ONLY : 0) | (arg_sync ? INSTALL_SYNCFS : 0)); if (r < 0) return log_error_errno(r, "Failed install directory as '%s': %m", final_path); temp_path = mfree(temp_path); log_info("Directory '%s successfully installed. Exiting.", final_path); return 0; } static int help(int argc, char *argv[], void *userdata) { printf("%1$s [OPTIONS...] {COMMAND} ...\n" "\n%4$sImport container images from a file system directories.%5$s\n" "\n%2$sCommands:%3$s\n" " run DIRECTORY [NAME] Import a directory\n" "\n%2$sOptions:%3$s\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" " --direct Import directly to specified directory\n" " --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n" " instead of a directory\n" " --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n" " subvolume\n" " --sync=BOOL Controls whether to sync() before completing\n" " --class=CLASS Select image class (machine, sysext, confext,\n" " portable)\n", program_invocation_short_name, ansi_underline(), ansi_normal(), ansi_highlight(), ansi_normal()); return 0; } static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_FORCE, ARG_IMAGE_ROOT, ARG_READ_ONLY, ARG_DIRECT, ARG_BTRFS_SUBVOL, ARG_BTRFS_QUOTA, ARG_SYNC, ARG_CLASS, }; 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 }, { "direct", no_argument, NULL, ARG_DIRECT }, { "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL }, { "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA }, { "sync", required_argument, NULL, ARG_SYNC }, { "class", required_argument, NULL, ARG_CLASS }, {} }; int c, r; 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: return version(); 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 ARG_DIRECT: arg_direct = true; break; case ARG_BTRFS_SUBVOL: r = parse_boolean_argument("--btrfs-subvol=", optarg, &arg_btrfs_subvol); if (r < 0) return r; break; case ARG_BTRFS_QUOTA: r = parse_boolean_argument("--btrfs-quota=", optarg, &arg_btrfs_quota); if (r < 0) return r; break; case ARG_SYNC: r = parse_boolean_argument("--sync=", optarg, &arg_sync); if (r < 0) return r; break; case ARG_CLASS: arg_class = image_class_from_string(optarg); if (arg_class < 0) return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); break; case '?': return -EINVAL; default: assert_not_reached(); } if (!arg_image_root) arg_image_root = image_root_to_string(arg_class); return 1; } static int import_fs_main(int argc, char *argv[]) { static const Verb verbs[] = { { "help", VERB_ANY, VERB_ANY, 0, help }, { "run", 2, 3, 0, import_fs }, {} }; return dispatch_verb(argc, argv, verbs, NULL); } static int run(int argc, char *argv[]) { int r; setlocale(LC_ALL, ""); log_setup(); r = parse_argv(argc, argv); if (r <= 0) return r; return import_fs_main(argc, argv); } DEFINE_MAIN_FUNCTION(run);