1 /* SPDX-License-Identifier: LGPL-2.1+ */
5 #include "alloc-util.h"
6 #include "btrfs-util.h"
9 #include "hostname-util.h"
10 #include "import-common.h"
11 #include "import-util.h"
12 #include "machine-image.h"
14 #include "parse-util.h"
15 #include "ratelimit.h"
17 #include "string-util.h"
18 #include "tmpfile-util.h"
21 static bool arg_force
= false;
22 static bool arg_read_only
= false;
23 static const char *arg_image_root
= "/var/lib/machines";
25 typedef struct ProgressInfo
{
30 bool logged_incomplete
;
33 static volatile sig_atomic_t cancelled
= false;
35 static void sigterm_sigint(int sig
) {
39 static void progress_info_free(ProgressInfo
*p
) {
43 static void progress_show(ProgressInfo
*p
) {
46 /* Show progress only every now and then. */
47 if (!ratelimit_below(&p
->limit
))
50 /* Suppress the first message, start with the second one */
56 /* Mention the list is incomplete before showing first output. */
57 if (!p
->logged_incomplete
) {
58 log_notice("(Note, file list shown below is incomplete, and is intended as sporadic progress report only.)");
59 p
->logged_incomplete
= true;
63 log_info("Copying tree, currently at '%s'...", p
->path
);
65 char buffer
[FORMAT_BYTES_MAX
];
67 log_info("Copying tree, currently at '%s' (@%s)...", p
->path
, format_bytes(buffer
, sizeof(buffer
), p
->size
));
71 static int progress_path(const char *path
, const struct stat
*st
, void *userdata
) {
72 ProgressInfo
*p
= userdata
;
80 r
= free_and_strdup(&p
->path
, path
);
90 static int progress_bytes(uint64_t nbytes
, void *userdata
) {
91 ProgressInfo
*p
= userdata
;
94 assert(p
->size
!= UINT64_MAX
);
105 static int import_fs(int argc
, char *argv
[], void *userdata
) {
106 _cleanup_(rm_rf_subvolume_and_freep
) char *temp_path
= NULL
;
107 _cleanup_(progress_info_free
) ProgressInfo progress
= {};
108 const char *path
= NULL
, *local
= NULL
, *final_path
;
109 _cleanup_close_
int open_fd
= -1;
110 struct sigaction old_sigint_sa
, old_sigterm_sa
;
111 static const struct sigaction sa
= {
112 .sa_handler
= sigterm_sigint
,
113 .sa_flags
= SA_RESTART
,
119 if (isempty(path
) || streq(path
, "-"))
125 local
= basename(path
);
126 if (isempty(local
) || streq(local
, "-"))
130 if (!machine_name_is_valid(local
)) {
131 log_error("Local image name '%s' is not valid.", local
);
136 r
= image_find(IMAGE_MACHINE
, local
, NULL
);
139 return log_error_errno(r
, "Failed to check whether image '%s' exists: %m", local
);
141 log_error("Image '%s' already exists.", local
);
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
= strjoina(arg_image_root
, "/", local
);
167 r
= tempfn_random(final_path
, NULL
, &temp_path
);
171 (void) mkdir_parents_label(temp_path
, 0700);
173 RATELIMIT_INIT(progress
.limit
, 200*USEC_PER_MSEC
, 1);
175 /* Hook into SIGINT/SIGTERM, so that we can cancel things then */
176 assert(sigaction(SIGINT
, &sa
, &old_sigint_sa
) >= 0);
177 assert(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(sigaction(SIGINT
, &old_sigint_sa
, NULL
) >= 0);
225 assert(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
;