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 log_error("Local image name '%s' is not valid.", local
);
135 r
= image_find(IMAGE_MACHINE
, local
, NULL
);
138 return log_error_errno(r
, "Failed to check whether image '%s' exists: %m", local
);
140 log_error("Image '%s' already exists.", local
);
148 open_fd
= open(path
, O_DIRECTORY
|O_RDONLY
|O_CLOEXEC
);
150 return log_error_errno(errno
, "Failed to open directory to import: %m");
154 log_info("Importing '%s', saving as '%s'.", path
, local
);
156 _cleanup_free_
char *pretty
= NULL
;
160 (void) fd_get_path(fd
, &pretty
);
161 log_info("Importing '%s', saving as '%s'.", strempty(pretty
), local
);
164 final_path
= prefix_roota(arg_image_root
, local
);
166 r
= tempfn_random(final_path
, NULL
, &temp_path
);
170 (void) mkdir_parents_label(temp_path
, 0700);
172 progress
.limit
= (RateLimit
) { 200*USEC_PER_MSEC
, 1 };
174 /* Hook into SIGINT/SIGTERM, so that we can cancel things then */
175 assert(sigaction(SIGINT
, &sa
, &old_sigint_sa
) >= 0);
176 assert(sigaction(SIGTERM
, &sa
, &old_sigterm_sa
) >= 0);
178 r
= btrfs_subvol_snapshot_fd_full(
181 BTRFS_SNAPSHOT_FALLBACK_COPY
|BTRFS_SNAPSHOT_RECURSIVE
|BTRFS_SNAPSHOT_FALLBACK_DIRECTORY
|BTRFS_SNAPSHOT_QUOTA
,
185 if (r
== -EOWNERDEAD
) { /* SIGINT + SIGTERM cause this, see signal handler above */
186 log_error("Copy cancelled.");
190 log_error_errno(r
, "Failed to copy directory: %m");
194 r
= import_mangle_os_tree(temp_path
);
198 (void) import_assign_pool_quota_and_warn(temp_path
);
201 r
= import_make_read_only(temp_path
);
203 log_error_errno(r
, "Failed to make directory read-only: %m");
209 (void) rm_rf(final_path
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
211 r
= rename_noreplace(AT_FDCWD
, temp_path
, AT_FDCWD
, final_path
);
213 log_error_errno(r
, "Failed to move image into place: %m");
217 temp_path
= mfree(temp_path
);
219 log_info("Exiting.");
222 /* Put old signal handlers into place */
223 assert(sigaction(SIGINT
, &old_sigint_sa
, NULL
) >= 0);
224 assert(sigaction(SIGTERM
, &old_sigterm_sa
, NULL
) >= 0);
229 static int help(int argc
, char *argv
[], void *userdata
) {
231 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
232 "Import container images from a file system.\n\n"
233 " -h --help Show this help\n"
234 " --version Show package version\n"
235 " --force Force creation of image\n"
236 " --image-root=PATH Image root directory\n"
237 " --read-only Create a read-only image\n\n"
239 " run DIRECTORY [NAME] Import a directory\n",
240 program_invocation_short_name
);
245 static int parse_argv(int argc
, char *argv
[]) {
254 static const struct option options
[] = {
255 { "help", no_argument
, NULL
, 'h' },
256 { "version", no_argument
, NULL
, ARG_VERSION
},
257 { "force", no_argument
, NULL
, ARG_FORCE
},
258 { "image-root", required_argument
, NULL
, ARG_IMAGE_ROOT
},
259 { "read-only", no_argument
, NULL
, ARG_READ_ONLY
},
268 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
273 return help(0, NULL
, NULL
);
283 arg_image_root
= optarg
;
287 arg_read_only
= true;
294 assert_not_reached("Unhandled option");
300 static int import_fs_main(int argc
, char *argv
[]) {
302 static const Verb verbs
[] = {
303 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
304 { "run", 2, 3, 0, import_fs
},
308 return dispatch_verb(argc
, argv
, verbs
, NULL
);
311 int main(int argc
, char *argv
[]) {
314 setlocale(LC_ALL
, "");
315 log_parse_environment();
318 r
= parse_argv(argc
, argv
);
322 r
= import_fs_main(argc
, argv
);
325 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;