1 /* SPDX-License-Identifier: LGPL-2.1+ */
6 #include "alloc-util.h"
7 #include "btrfs-util.h"
10 #include "hostname-util.h"
11 #include "import-common.h"
12 #include "import-util.h"
13 #include "machine-image.h"
15 #include "parse-util.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 if (isempty(path
) || streq(path
, "-"))
126 local
= basename(path
);
127 if (isempty(local
) || streq(local
, "-"))
131 if (!machine_name_is_valid(local
)) {
132 log_error("Local image name '%s' is not valid.", local
);
137 r
= image_find(IMAGE_MACHINE
, local
, NULL
);
140 return log_error_errno(r
, "Failed to check whether image '%s' exists: %m", local
);
142 log_error("Image '%s' already exists.", local
);
150 open_fd
= open(path
, O_DIRECTORY
|O_RDONLY
|O_CLOEXEC
);
152 return log_error_errno(errno
, "Failed to open directory to import: %m");
156 log_info("Importing '%s', saving as '%s'.", path
, local
);
158 _cleanup_free_
char *pretty
= NULL
;
162 (void) fd_get_path(fd
, &pretty
);
163 log_info("Importing '%s', saving as '%s'.", strempty(pretty
), local
);
166 final_path
= strjoina(arg_image_root
, "/", local
);
168 r
= tempfn_random(final_path
, NULL
, &temp_path
);
172 (void) mkdir_parents_label(temp_path
, 0700);
174 RATELIMIT_INIT(progress
.limit
, 200*USEC_PER_MSEC
, 1);
176 /* Hook into SIGINT/SIGTERM, so that we can cancel things then */
177 assert(sigaction(SIGINT
, &sa
, &old_sigint_sa
) >= 0);
178 assert(sigaction(SIGTERM
, &sa
, &old_sigterm_sa
) >= 0);
180 r
= btrfs_subvol_snapshot_fd_full(
183 BTRFS_SNAPSHOT_FALLBACK_COPY
|BTRFS_SNAPSHOT_RECURSIVE
|BTRFS_SNAPSHOT_FALLBACK_DIRECTORY
|BTRFS_SNAPSHOT_QUOTA
,
187 if (r
== -EOWNERDEAD
) { /* SIGINT + SIGTERM cause this, see signal handler above */
188 log_error("Copy cancelled.");
192 log_error_errno(r
, "Failed to copy directory: %m");
196 r
= import_mangle_os_tree(temp_path
);
200 (void) import_assign_pool_quota_and_warn(temp_path
);
203 r
= import_make_read_only(temp_path
);
205 log_error_errno(r
, "Failed to make directory read-only: %m");
211 (void) rm_rf(final_path
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
213 r
= rename_noreplace(AT_FDCWD
, temp_path
, AT_FDCWD
, final_path
);
215 log_error_errno(r
, "Failed to move image into place: %m");
219 temp_path
= mfree(temp_path
);
221 log_info("Exiting.");
224 /* Put old signal handlers into place */
225 assert(sigaction(SIGINT
, &old_sigint_sa
, NULL
) >= 0);
226 assert(sigaction(SIGTERM
, &old_sigterm_sa
, NULL
) >= 0);
231 static int help(int argc
, char *argv
[], void *userdata
) {
233 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
234 "Import container images from a file system.\n\n"
235 " -h --help Show this help\n"
236 " --version Show package version\n"
237 " --force Force creation of image\n"
238 " --image-root=PATH Image root directory\n"
239 " --read-only Create a read-only image\n\n"
241 " run DIRECTORY [NAME] Import a directory\n",
242 program_invocation_short_name
);
247 static int parse_argv(int argc
, char *argv
[]) {
256 static const struct option options
[] = {
257 { "help", no_argument
, NULL
, 'h' },
258 { "version", no_argument
, NULL
, ARG_VERSION
},
259 { "force", no_argument
, NULL
, ARG_FORCE
},
260 { "image-root", required_argument
, NULL
, ARG_IMAGE_ROOT
},
261 { "read-only", no_argument
, NULL
, ARG_READ_ONLY
},
270 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
275 return help(0, NULL
, NULL
);
285 arg_image_root
= optarg
;
289 arg_read_only
= true;
296 assert_not_reached("Unhandled option");
302 static int import_fs_main(int argc
, char *argv
[]) {
304 static const Verb verbs
[] = {
305 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
306 { "run", 2, 3, 0, import_fs
},
310 return dispatch_verb(argc
, argv
, verbs
, NULL
);
313 int main(int argc
, char *argv
[]) {
316 setlocale(LC_ALL
, "");
317 log_parse_environment();
320 r
= parse_argv(argc
, argv
);
324 r
= import_fs_main(argc
, argv
);
327 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;