1 /* SPDX-License-Identifier: LGPL-2.1+ */
6 #include "alloc-util.h"
7 #include "btrfs-util.h"
9 #include "format-util.h"
11 #include "hostname-util.h"
12 #include "import-common.h"
13 #include "import-util.h"
14 #include "machine-image.h"
16 #include "ratelimit.h"
18 #include "string-util.h"
19 #include "tmpfile-util.h"
22 static bool arg_force
= false;
23 static bool arg_read_only
= false;
24 static const char *arg_image_root
= "/var/lib/machines";
26 typedef struct ProgressInfo
{
31 bool logged_incomplete
;
34 static volatile sig_atomic_t cancelled
= false;
36 static void sigterm_sigint(int sig
) {
40 static void progress_info_free(ProgressInfo
*p
) {
44 static void progress_show(ProgressInfo
*p
) {
47 /* Show progress only every now and then. */
48 if (!ratelimit_below(&p
->limit
))
51 /* Suppress the first message, start with the second one */
57 /* Mention the list is incomplete before showing first output. */
58 if (!p
->logged_incomplete
) {
59 log_notice("(Note, file list shown below is incomplete, and is intended as sporadic progress report only.)");
60 p
->logged_incomplete
= true;
64 log_info("Copying tree, currently at '%s'...", p
->path
);
66 char buffer
[FORMAT_BYTES_MAX
];
68 log_info("Copying tree, currently at '%s' (@%s)...", p
->path
, format_bytes(buffer
, sizeof(buffer
), p
->size
));
72 static int progress_path(const char *path
, const struct stat
*st
, void *userdata
) {
73 ProgressInfo
*p
= userdata
;
81 r
= free_and_strdup(&p
->path
, path
);
91 static int progress_bytes(uint64_t nbytes
, void *userdata
) {
92 ProgressInfo
*p
= userdata
;
95 assert(p
->size
!= UINT64_MAX
);
106 static int import_fs(int argc
, char *argv
[], void *userdata
) {
107 _cleanup_(rm_rf_subvolume_and_freep
) char *temp_path
= NULL
;
108 _cleanup_(progress_info_free
) ProgressInfo progress
= {};
109 const char *path
= NULL
, *local
= NULL
, *final_path
;
110 _cleanup_close_
int open_fd
= -1;
111 struct sigaction old_sigint_sa
, old_sigterm_sa
;
112 static const struct sigaction sa
= {
113 .sa_handler
= sigterm_sigint
,
114 .sa_flags
= SA_RESTART
,
120 path
= empty_or_dash_to_null(path
);
125 local
= basename(path
);
126 local
= empty_or_dash_to_null(local
);
129 if (!machine_name_is_valid(local
))
130 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
131 "Local image name '%s' is not valid.",
135 r
= image_find(IMAGE_MACHINE
, local
, NULL
);
138 return log_error_errno(r
, "Failed to check whether image '%s' exists: %m", local
);
140 return log_error_errno(SYNTHETIC_ERRNO(EEXIST
),
141 "Image '%s' already exists.",
149 open_fd
= open(path
, O_DIRECTORY
|O_RDONLY
|O_CLOEXEC
);
151 return log_error_errno(errno
, "Failed to open directory to import: %m");
155 log_info("Importing '%s', saving as '%s'.", path
, local
);
157 _cleanup_free_
char *pretty
= NULL
;
161 (void) fd_get_path(fd
, &pretty
);
162 log_info("Importing '%s', saving as '%s'.", strempty(pretty
), local
);
165 final_path
= prefix_roota(arg_image_root
, local
);
167 r
= tempfn_random(final_path
, NULL
, &temp_path
);
171 (void) mkdir_parents_label(temp_path
, 0700);
173 progress
.limit
= (RateLimit
) { 200*USEC_PER_MSEC
, 1 };
175 /* Hook into SIGINT/SIGTERM, so that we can cancel things then */
176 assert_se(sigaction(SIGINT
, &sa
, &old_sigint_sa
) >= 0);
177 assert_se(sigaction(SIGTERM
, &sa
, &old_sigterm_sa
) >= 0);
179 r
= btrfs_subvol_snapshot_fd_full(
182 BTRFS_SNAPSHOT_FALLBACK_COPY
|BTRFS_SNAPSHOT_RECURSIVE
|BTRFS_SNAPSHOT_FALLBACK_DIRECTORY
|BTRFS_SNAPSHOT_QUOTA
,
186 if (r
== -EOWNERDEAD
) { /* SIGINT + SIGTERM cause this, see signal handler above */
187 log_error("Copy cancelled.");
191 log_error_errno(r
, "Failed to copy directory: %m");
195 r
= import_mangle_os_tree(temp_path
);
199 (void) import_assign_pool_quota_and_warn(temp_path
);
202 r
= import_make_read_only(temp_path
);
204 log_error_errno(r
, "Failed to make directory read-only: %m");
210 (void) rm_rf(final_path
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
212 r
= rename_noreplace(AT_FDCWD
, temp_path
, AT_FDCWD
, final_path
);
214 log_error_errno(r
, "Failed to move image into place: %m");
218 temp_path
= mfree(temp_path
);
220 log_info("Exiting.");
223 /* Put old signal handlers into place */
224 assert_se(sigaction(SIGINT
, &old_sigint_sa
, NULL
) >= 0);
225 assert_se(sigaction(SIGTERM
, &old_sigterm_sa
, NULL
) >= 0);
230 static int help(int argc
, char *argv
[], void *userdata
) {
232 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
233 "Import container images from a file system.\n\n"
234 " -h --help Show this help\n"
235 " --version Show package version\n"
236 " --force Force creation of image\n"
237 " --image-root=PATH Image root directory\n"
238 " --read-only Create a read-only image\n\n"
240 " run DIRECTORY [NAME] Import a directory\n",
241 program_invocation_short_name
);
246 static int parse_argv(int argc
, char *argv
[]) {
255 static const struct option options
[] = {
256 { "help", no_argument
, NULL
, 'h' },
257 { "version", no_argument
, NULL
, ARG_VERSION
},
258 { "force", no_argument
, NULL
, ARG_FORCE
},
259 { "image-root", required_argument
, NULL
, ARG_IMAGE_ROOT
},
260 { "read-only", no_argument
, NULL
, ARG_READ_ONLY
},
269 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
274 return help(0, NULL
, NULL
);
284 arg_image_root
= optarg
;
288 arg_read_only
= true;
295 assert_not_reached("Unhandled option");
301 static int import_fs_main(int argc
, char *argv
[]) {
303 static const Verb verbs
[] = {
304 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
305 { "run", 2, 3, 0, import_fs
},
309 return dispatch_verb(argc
, argv
, verbs
, NULL
);
312 int main(int argc
, char *argv
[]) {
315 setlocale(LC_ALL
, "");
316 log_parse_environment();
319 r
= parse_argv(argc
, argv
);
323 r
= import_fs_main(argc
, argv
);
326 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;