Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */
2 :
3 : #include <errno.h>
4 : #include <getopt.h>
5 :
6 : #include "sd-bus.h"
7 :
8 : #include "alloc-util.h"
9 : #include "bus-error.h"
10 : #include "bus-util.h"
11 : #include "def.h"
12 : #include "dirent-util.h"
13 : #include "env-file.h"
14 : #include "fd-util.h"
15 : #include "fileio.h"
16 : #include "format-table.h"
17 : #include "fs-util.h"
18 : #include "locale-util.h"
19 : #include "machine-image.h"
20 : #include "main-func.h"
21 : #include "pager.h"
22 : #include "parse-util.h"
23 : #include "path-util.h"
24 : #include "pretty-print.h"
25 : #include "spawn-polkit-agent.h"
26 : #include "string-util.h"
27 : #include "strv.h"
28 : #include "terminal-util.h"
29 : #include "verbs.h"
30 :
31 : static PagerFlags arg_pager_flags = 0;
32 : static bool arg_legend = true;
33 : static bool arg_ask_password = true;
34 : static bool arg_quiet = false;
35 : static const char *arg_profile = "default";
36 : static const char* arg_copy_mode = NULL;
37 : static bool arg_runtime = false;
38 : static bool arg_reload = true;
39 : static bool arg_cat = false;
40 : static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
41 : static const char *arg_host = NULL;
42 :
43 0 : static int determine_image(const char *image, bool permit_non_existing, char **ret) {
44 : int r;
45 :
46 : /* If the specified name is a valid image name, we pass it as-is to portabled, which will search for it in the
47 : * usual search directories. Otherwise we presume it's a path, and will normalize it on the client's side
48 : * (among other things, to make the path independent of the client's working directory) before passing it
49 : * over. */
50 :
51 0 : if (image_name_is_valid(image)) {
52 : char *c;
53 :
54 0 : if (!arg_quiet && laccess(image, F_OK) >= 0)
55 0 : log_warning("Ambiguous invocation: current working directory contains file matching non-path argument '%s', ignoring. "
56 : "Prefix argument with './' to force reference to file in current working directory.", image);
57 :
58 0 : c = strdup(image);
59 0 : if (!c)
60 0 : return log_oom();
61 :
62 0 : *ret = c;
63 0 : return 0;
64 : }
65 :
66 0 : if (arg_transport != BUS_TRANSPORT_LOCAL)
67 0 : return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
68 : "Operations on images by path not supported when connecting to remote systems.");
69 :
70 0 : r = chase_symlinks(image, NULL, CHASE_TRAIL_SLASH | (permit_non_existing ? CHASE_NONEXISTENT : 0), ret);
71 0 : if (r < 0)
72 0 : return log_error_errno(r, "Cannot normalize specified image path '%s': %m", image);
73 :
74 0 : return 0;
75 : }
76 :
77 0 : static int extract_prefix(const char *path, char **ret) {
78 0 : _cleanup_free_ char *name = NULL;
79 : const char *bn, *underscore;
80 : size_t m;
81 :
82 0 : bn = basename(path);
83 :
84 0 : underscore = strchr(bn, '_');
85 0 : if (underscore)
86 0 : m = underscore - bn;
87 : else {
88 : const char *e;
89 :
90 0 : e = endswith(bn, ".raw");
91 0 : if (!e)
92 0 : e = strchr(bn, 0);
93 :
94 0 : m = e - bn;
95 : }
96 :
97 0 : name = strndup(bn, m);
98 0 : if (!name)
99 0 : return -ENOMEM;
100 :
101 : /* A slightly reduced version of what's permitted in unit names. With ':' and '\' are removed, as well as '_'
102 : * which we use as delimiter for the second part of the image string, which we ignore for now. */
103 0 : if (!in_charset(name, DIGITS LETTERS "-."))
104 0 : return -EINVAL;
105 :
106 0 : if (!filename_is_valid(name))
107 0 : return -EINVAL;
108 :
109 0 : *ret = TAKE_PTR(name);
110 :
111 0 : return 0;
112 : }
113 :
114 0 : static int determine_matches(const char *image, char **l, bool allow_any, char ***ret) {
115 0 : _cleanup_strv_free_ char **k = NULL;
116 : int r;
117 :
118 : /* Determine the matches to apply. If the list is empty we derive the match from the image name. If the list
119 : * contains exactly the "-" we return a wildcard list (which is the empty list), but only if this is expressly
120 : * permitted. */
121 :
122 0 : if (strv_isempty(l)) {
123 : char *prefix;
124 :
125 0 : r = extract_prefix(image, &prefix);
126 0 : if (r < 0)
127 0 : return log_error_errno(r, "Failed to extract prefix of image name '%s': %m", image);
128 :
129 0 : if (!arg_quiet)
130 0 : log_info("(Matching unit files with prefix '%s'.)", prefix);
131 :
132 0 : r = strv_consume(&k, prefix);
133 0 : if (r < 0)
134 0 : return log_oom();
135 :
136 0 : } else if (strv_equal(l, STRV_MAKE("-"))) {
137 :
138 0 : if (!allow_any)
139 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
140 : "Refusing all unit file match.");
141 :
142 0 : if (!arg_quiet)
143 0 : log_info("(Matching all unit files.)");
144 : } else {
145 :
146 0 : k = strv_copy(l);
147 0 : if (!k)
148 0 : return log_oom();
149 :
150 0 : if (!arg_quiet) {
151 0 : _cleanup_free_ char *joined = NULL;
152 :
153 0 : joined = strv_join(k, "', '");
154 0 : if (!joined)
155 0 : return log_oom();
156 :
157 0 : log_info("(Matching unit files with prefixes '%s'.)", joined);
158 : }
159 : }
160 :
161 0 : *ret = TAKE_PTR(k);
162 :
163 0 : return 0;
164 : }
165 :
166 0 : static int acquire_bus(sd_bus **bus) {
167 : int r;
168 :
169 0 : assert(bus);
170 :
171 0 : if (*bus)
172 0 : return 0;
173 :
174 0 : r = bus_connect_transport(arg_transport, arg_host, false, bus);
175 0 : if (r < 0)
176 0 : return log_error_errno(r, "Failed to connect to bus: %m");
177 :
178 0 : (void) sd_bus_set_allow_interactive_authorization(*bus, arg_ask_password);
179 :
180 0 : return 0;
181 : }
182 :
183 0 : static int maybe_reload(sd_bus **bus) {
184 0 : _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
185 0 : _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
186 : int r;
187 :
188 0 : if (!arg_reload)
189 0 : return 0;
190 :
191 0 : r = acquire_bus(bus);
192 0 : if (r < 0)
193 0 : return r;
194 :
195 0 : r = sd_bus_message_new_method_call(
196 : *bus,
197 : &m,
198 : "org.freedesktop.systemd1",
199 : "/org/freedesktop/systemd1",
200 : "org.freedesktop.systemd1.Manager",
201 : "Reload");
202 0 : if (r < 0)
203 0 : return bus_log_create_error(r);
204 :
205 : /* Reloading the daemon may take long, hence set a longer timeout here */
206 0 : r = sd_bus_call(*bus, m, DEFAULT_TIMEOUT_USEC * 2, &error, NULL);
207 0 : if (r < 0)
208 0 : return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r));
209 :
210 0 : return 0;
211 : }
212 :
213 0 : static int inspect_image(int argc, char *argv[], void *userdata) {
214 0 : _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
215 0 : _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
216 0 : _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
217 0 : _cleanup_strv_free_ char **matches = NULL;
218 0 : _cleanup_free_ char *image = NULL;
219 0 : bool nl = false, header = false;
220 : const void *data;
221 : const char *path;
222 : size_t sz;
223 : int r;
224 :
225 0 : r = determine_image(argv[1], false, &image);
226 0 : if (r < 0)
227 0 : return r;
228 :
229 0 : r = determine_matches(argv[1], argv + 2, true, &matches);
230 0 : if (r < 0)
231 0 : return r;
232 :
233 0 : r = acquire_bus(&bus);
234 0 : if (r < 0)
235 0 : return r;
236 :
237 0 : r = sd_bus_message_new_method_call(
238 : bus,
239 : &m,
240 : "org.freedesktop.portable1",
241 : "/org/freedesktop/portable1",
242 : "org.freedesktop.portable1.Manager",
243 : "GetImageMetadata");
244 0 : if (r < 0)
245 0 : return bus_log_create_error(r);
246 :
247 0 : r = sd_bus_message_append(m, "s", image);
248 0 : if (r < 0)
249 0 : return bus_log_create_error(r);
250 :
251 0 : r = sd_bus_message_append_strv(m, matches);
252 0 : if (r < 0)
253 0 : return bus_log_create_error(r);
254 :
255 0 : r = sd_bus_call(bus, m, 0, &error, &reply);
256 0 : if (r < 0)
257 0 : return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(&error, r));
258 :
259 0 : r = sd_bus_message_read(reply, "s", &path);
260 0 : if (r < 0)
261 0 : return bus_log_parse_error(r);
262 :
263 0 : r = sd_bus_message_read_array(reply, 'y', &data, &sz);
264 0 : if (r < 0)
265 0 : return bus_log_parse_error(r);
266 :
267 0 : (void) pager_open(arg_pager_flags);
268 :
269 0 : if (arg_cat) {
270 0 : printf("%s-- OS Release: --%s\n", ansi_highlight(), ansi_normal());
271 0 : fwrite(data, sz, 1, stdout);
272 0 : fflush(stdout);
273 0 : nl = true;
274 : } else {
275 0 : _cleanup_free_ char *pretty_portable = NULL, *pretty_os = NULL;
276 0 : _cleanup_fclose_ FILE *f;
277 :
278 0 : f = fmemopen_unlocked((void*) data, sz, "re");
279 0 : if (!f)
280 0 : return log_error_errno(errno, "Failed to open /etc/os-release buffer: %m");
281 :
282 0 : r = parse_env_file(f, "/etc/os-release",
283 : "PORTABLE_PRETTY_NAME", &pretty_portable,
284 : "PRETTY_NAME", &pretty_os);
285 0 : if (r < 0)
286 0 : return log_error_errno(r, "Failed to parse /etc/os-release: %m");
287 :
288 0 : printf("Image:\n\t%s\n"
289 : "Portable Service:\n\t%s\n"
290 : "Operating System:\n\t%s\n",
291 : path,
292 : strna(pretty_portable),
293 : strna(pretty_os));
294 : }
295 :
296 0 : r = sd_bus_message_enter_container(reply, 'a', "{say}");
297 0 : if (r < 0)
298 0 : return bus_log_parse_error(r);
299 :
300 0 : for (;;) {
301 : const char *name;
302 :
303 0 : r = sd_bus_message_enter_container(reply, 'e', "say");
304 0 : if (r < 0)
305 0 : return bus_log_parse_error(r);
306 0 : if (r == 0)
307 0 : break;
308 :
309 0 : r = sd_bus_message_read(reply, "s", &name);
310 0 : if (r < 0)
311 0 : return bus_log_parse_error(r);
312 :
313 0 : r = sd_bus_message_read_array(reply, 'y', &data, &sz);
314 0 : if (r < 0)
315 0 : return bus_log_parse_error(r);
316 :
317 0 : if (arg_cat) {
318 0 : if (nl)
319 0 : fputc('\n', stdout);
320 :
321 0 : printf("%s-- Unit file: %s --%s\n", ansi_highlight(), name, ansi_normal());
322 0 : fwrite(data, sz, 1, stdout);
323 0 : fflush(stdout);
324 0 : nl = true;
325 : } else {
326 0 : if (!header) {
327 0 : fputs("Unit files:\n", stdout);
328 0 : header = true;
329 : }
330 :
331 0 : fputc('\t', stdout);
332 0 : fputs(name, stdout);
333 0 : fputc('\n', stdout);
334 : }
335 :
336 0 : r = sd_bus_message_exit_container(reply);
337 0 : if (r < 0)
338 0 : return bus_log_parse_error(r);
339 : }
340 :
341 0 : r = sd_bus_message_exit_container(reply);
342 0 : if (r < 0)
343 0 : return bus_log_parse_error(r);
344 :
345 0 : return 0;
346 : }
347 :
348 0 : static int print_changes(sd_bus_message *m) {
349 : int r;
350 :
351 0 : if (arg_quiet)
352 0 : return 0;
353 :
354 0 : r = sd_bus_message_enter_container(m, 'a', "(sss)");
355 0 : if (r < 0)
356 0 : return bus_log_parse_error(r);
357 :
358 0 : for (;;) {
359 : const char *type, *path, *source;
360 :
361 0 : r = sd_bus_message_read(m, "(sss)", &type, &path, &source);
362 0 : if (r < 0)
363 0 : return bus_log_parse_error(r);
364 0 : if (r == 0)
365 0 : break;
366 :
367 0 : if (streq(type, "symlink"))
368 0 : log_info("Created symlink %s %s %s.", path, special_glyph(SPECIAL_GLYPH_ARROW), source);
369 0 : else if (streq(type, "copy")) {
370 0 : if (isempty(source))
371 0 : log_info("Copied %s.", path);
372 : else
373 0 : log_info("Copied %s %s %s.", source, special_glyph(SPECIAL_GLYPH_ARROW), path);
374 0 : } else if (streq(type, "unlink"))
375 0 : log_info("Removed %s.", path);
376 0 : else if (streq(type, "write"))
377 0 : log_info("Written %s.", path);
378 0 : else if (streq(type, "mkdir"))
379 0 : log_info("Created directory %s.", path);
380 : else
381 0 : log_error("Unexpected change: %s/%s/%s", type, path, source);
382 : }
383 :
384 0 : r = sd_bus_message_exit_container(m);
385 0 : if (r < 0)
386 0 : return r;
387 :
388 0 : return 0;
389 : }
390 :
391 0 : static int attach_image(int argc, char *argv[], void *userdata) {
392 0 : _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
393 0 : _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
394 0 : _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
395 0 : _cleanup_strv_free_ char **matches = NULL;
396 0 : _cleanup_free_ char *image = NULL;
397 : int r;
398 :
399 0 : r = determine_image(argv[1], false, &image);
400 0 : if (r < 0)
401 0 : return r;
402 :
403 0 : r = determine_matches(argv[1], argv + 2, false, &matches);
404 0 : if (r < 0)
405 0 : return r;
406 :
407 0 : r = acquire_bus(&bus);
408 0 : if (r < 0)
409 0 : return r;
410 :
411 0 : (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
412 :
413 0 : r = sd_bus_message_new_method_call(
414 : bus,
415 : &m,
416 : "org.freedesktop.portable1",
417 : "/org/freedesktop/portable1",
418 : "org.freedesktop.portable1.Manager",
419 : "AttachImage");
420 0 : if (r < 0)
421 0 : return bus_log_create_error(r);
422 :
423 0 : r = sd_bus_message_append(m, "s", image);
424 0 : if (r < 0)
425 0 : return bus_log_create_error(r);
426 :
427 0 : r = sd_bus_message_append_strv(m, matches);
428 0 : if (r < 0)
429 0 : return bus_log_create_error(r);
430 :
431 0 : r = sd_bus_message_append(m, "sbs", arg_profile, arg_runtime, arg_copy_mode);
432 0 : if (r < 0)
433 0 : return bus_log_create_error(r);
434 :
435 0 : r = sd_bus_call(bus, m, 0, &error, &reply);
436 0 : if (r < 0)
437 0 : return log_error_errno(r, "Failed to attach image: %s", bus_error_message(&error, r));
438 :
439 0 : (void) maybe_reload(&bus);
440 :
441 0 : print_changes(reply);
442 0 : return 0;
443 : }
444 :
445 0 : static int detach_image(int argc, char *argv[], void *userdata) {
446 0 : _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
447 0 : _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
448 0 : _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
449 0 : _cleanup_free_ char *image = NULL;
450 : int r;
451 :
452 0 : r = determine_image(argv[1], true, &image);
453 0 : if (r < 0)
454 0 : return r;
455 :
456 0 : r = acquire_bus(&bus);
457 0 : if (r < 0)
458 0 : return r;
459 :
460 0 : (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
461 :
462 0 : r = sd_bus_call_method(
463 : bus,
464 : "org.freedesktop.portable1",
465 : "/org/freedesktop/portable1",
466 : "org.freedesktop.portable1.Manager",
467 : "DetachImage",
468 : &error,
469 : &reply,
470 : "sb", image, arg_runtime);
471 0 : if (r < 0)
472 0 : return log_error_errno(r, "Failed to detach image: %s", bus_error_message(&error, r));
473 :
474 0 : (void) maybe_reload(&bus);
475 :
476 0 : print_changes(reply);
477 0 : return 0;
478 : }
479 :
480 0 : static int list_images(int argc, char *argv[], void *userdata) {
481 0 : _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
482 0 : _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
483 0 : _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
484 0 : _cleanup_(table_unrefp) Table *table = NULL;
485 : int r;
486 :
487 0 : r = acquire_bus(&bus);
488 0 : if (r < 0)
489 0 : return r;
490 :
491 0 : r = sd_bus_call_method(
492 : bus,
493 : "org.freedesktop.portable1",
494 : "/org/freedesktop/portable1",
495 : "org.freedesktop.portable1.Manager",
496 : "ListImages",
497 : &error,
498 : &reply,
499 : NULL);
500 0 : if (r < 0)
501 0 : return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r));
502 :
503 0 : table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state");
504 0 : if (!table)
505 0 : return log_oom();
506 :
507 0 : r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)");
508 0 : if (r < 0)
509 0 : return bus_log_parse_error(r);
510 :
511 0 : for (;;) {
512 : const char *name, *type, *state;
513 : uint64_t crtime, mtime, usage;
514 : TableCell *cell;
515 : bool ro_bool;
516 : int ro_int;
517 :
518 0 : r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, NULL);
519 0 : if (r < 0)
520 0 : return bus_log_parse_error(r);
521 0 : if (r == 0)
522 0 : break;
523 :
524 0 : r = table_add_many(table,
525 : TABLE_STRING, name,
526 : TABLE_STRING, type);
527 0 : if (r < 0)
528 0 : return log_error_errno(r, "Failed to add row to table: %m");
529 :
530 0 : ro_bool = ro_int;
531 0 : r = table_add_cell(table, &cell, TABLE_BOOLEAN, &ro_bool);
532 0 : if (r < 0)
533 0 : return log_error_errno(r, "Failed to add row to table: %m");
534 :
535 0 : if (ro_bool) {
536 0 : r = table_set_color(table, cell, ansi_highlight_red());
537 0 : if (r < 0)
538 0 : return log_error_errno(r, "Failed to set table cell color: %m");
539 : }
540 :
541 0 : r = table_add_many(table,
542 : TABLE_TIMESTAMP, crtime,
543 : TABLE_TIMESTAMP, mtime,
544 : TABLE_SIZE, usage);
545 0 : if (r < 0)
546 0 : return log_error_errno(r, "Failed to add row to table: %m");
547 :
548 0 : r = table_add_cell(table, &cell, TABLE_STRING, state);
549 0 : if (r < 0)
550 0 : return log_error_errno(r, "Failed to add row to table: %m");
551 :
552 0 : if (!streq(state, "detached")) {
553 0 : r = table_set_color(table, cell, ansi_highlight_green());
554 0 : if (r < 0)
555 0 : return log_error_errno(r, "Failed to set table cell color: %m");
556 : }
557 : }
558 :
559 0 : r = sd_bus_message_exit_container(reply);
560 0 : if (r < 0)
561 0 : return bus_log_parse_error(r);
562 :
563 0 : if (table_get_rows(table) > 1) {
564 0 : r = table_set_sort(table, (size_t) 0, (size_t) -1);
565 0 : if (r < 0)
566 0 : return log_error_errno(r, "Failed to sort table: %m");
567 :
568 0 : table_set_header(table, arg_legend);
569 :
570 0 : r = table_print(table, NULL);
571 0 : if (r < 0)
572 0 : return log_error_errno(r, "Failed to show table: %m");
573 : }
574 :
575 0 : if (arg_legend) {
576 0 : if (table_get_rows(table) > 1)
577 0 : printf("\n%zu images listed.\n", table_get_rows(table) - 1);
578 : else
579 0 : printf("No images.\n");
580 : }
581 :
582 0 : return 0;
583 : }
584 :
585 0 : static int remove_image(int argc, char *argv[], void *userdata) {
586 0 : _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
587 : int r, i;
588 :
589 0 : r = acquire_bus(&bus);
590 0 : if (r < 0)
591 0 : return r;
592 :
593 0 : (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
594 :
595 0 : for (i = 1; i < argc; i++) {
596 0 : _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
597 0 : _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
598 :
599 0 : r = sd_bus_message_new_method_call(
600 : bus,
601 : &m,
602 : "org.freedesktop.portable1",
603 : "/org/freedesktop/portable1",
604 : "org.freedesktop.portable1.Manager",
605 : "RemoveImage");
606 0 : if (r < 0)
607 0 : return bus_log_create_error(r);
608 :
609 0 : r = sd_bus_message_append(m, "s", argv[i]);
610 0 : if (r < 0)
611 0 : return bus_log_create_error(r);
612 :
613 : /* This is a slow operation, hence turn off any method call timeouts */
614 0 : r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
615 0 : if (r < 0)
616 0 : return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r));
617 : }
618 :
619 0 : return 0;
620 : }
621 :
622 0 : static int read_only_image(int argc, char *argv[], void *userdata) {
623 0 : _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
624 0 : _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
625 0 : int b = true, r;
626 :
627 0 : if (argc > 2) {
628 0 : b = parse_boolean(argv[2]);
629 0 : if (b < 0)
630 0 : return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]);
631 : }
632 :
633 0 : r = acquire_bus(&bus);
634 0 : if (r < 0)
635 0 : return r;
636 :
637 0 : (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
638 :
639 0 : r = sd_bus_call_method(
640 : bus,
641 : "org.freedesktop.portable1",
642 : "/org/freedesktop/portable1",
643 : "org.freedesktop.portable1.Manager",
644 : "MarkImageReadOnly",
645 : &error,
646 : NULL,
647 0 : "sb", argv[1], b);
648 0 : if (r < 0)
649 0 : return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r));
650 :
651 0 : return 0;
652 : }
653 :
654 0 : static int set_limit(int argc, char *argv[], void *userdata) {
655 0 : _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
656 0 : _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
657 : uint64_t limit;
658 : int r;
659 :
660 0 : r = acquire_bus(&bus);
661 0 : if (r < 0)
662 0 : return r;
663 :
664 0 : (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
665 :
666 0 : if (STR_IN_SET(argv[argc-1], "-", "none", "infinity"))
667 0 : limit = (uint64_t) -1;
668 : else {
669 0 : r = parse_size(argv[argc-1], 1024, &limit);
670 0 : if (r < 0)
671 0 : return log_error_errno(r, "Failed to parse size: %s", argv[argc-1]);
672 : }
673 :
674 0 : if (argc > 2)
675 : /* With two arguments changes the quota limit of the specified image */
676 0 : r = sd_bus_call_method(
677 : bus,
678 : "org.freedesktop.portable1",
679 : "/org/freedesktop/portable1",
680 : "org.freedesktop.portable1.Manager",
681 : "SetImageLimit",
682 : &error,
683 : NULL,
684 0 : "st", argv[1], limit);
685 : else
686 : /* With one argument changes the pool quota limit */
687 0 : r = sd_bus_call_method(
688 : bus,
689 : "org.freedesktop.portable1",
690 : "/org/freedesktop/portable1",
691 : "org.freedesktop.portable1.Manager",
692 : "SetPoolLimit",
693 : &error,
694 : NULL,
695 : "t", limit);
696 :
697 0 : if (r < 0)
698 0 : return log_error_errno(r, "Could not set limit: %s", bus_error_message(&error, r));
699 :
700 0 : return 0;
701 : }
702 :
703 0 : static int is_image_attached(int argc, char *argv[], void *userdata) {
704 0 : _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
705 0 : _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
706 0 : _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
707 0 : _cleanup_free_ char *image = NULL;
708 : const char *state;
709 : int r;
710 :
711 0 : r = determine_image(argv[1], true, &image);
712 0 : if (r < 0)
713 0 : return r;
714 :
715 0 : r = acquire_bus(&bus);
716 0 : if (r < 0)
717 0 : return r;
718 :
719 0 : r = sd_bus_call_method(
720 : bus,
721 : "org.freedesktop.portable1",
722 : "/org/freedesktop/portable1",
723 : "org.freedesktop.portable1.Manager",
724 : "GetImageState",
725 : &error,
726 : &reply,
727 : "s", image);
728 0 : if (r < 0)
729 0 : return log_error_errno(r, "Failed to get image state: %s", bus_error_message(&error, r));
730 :
731 0 : r = sd_bus_message_read(reply, "s", &state);
732 0 : if (r < 0)
733 0 : return r;
734 :
735 0 : if (!arg_quiet)
736 0 : puts(state);
737 :
738 0 : return streq(state, "detached");
739 : }
740 :
741 0 : static int dump_profiles(void) {
742 0 : _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
743 0 : _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
744 0 : _cleanup_strv_free_ char **l = NULL;
745 : char **i;
746 : int r;
747 :
748 0 : r = acquire_bus(&bus);
749 0 : if (r < 0)
750 0 : return r;
751 :
752 0 : r = sd_bus_get_property_strv(
753 : bus,
754 : "org.freedesktop.portable1",
755 : "/org/freedesktop/portable1",
756 : "org.freedesktop.portable1.Manager",
757 : "Profiles",
758 : &error,
759 : &l);
760 0 : if (r < 0)
761 0 : return log_error_errno(r, "Failed to acquire list of profiles: %s", bus_error_message(&error, r));
762 :
763 0 : if (arg_legend)
764 0 : log_info("Available unit profiles:");
765 :
766 0 : STRV_FOREACH(i, l) {
767 0 : fputs(*i, stdout);
768 0 : fputc('\n', stdout);
769 : }
770 :
771 0 : return 0;
772 : }
773 :
774 3 : static int help(int argc, char *argv[], void *userdata) {
775 3 : _cleanup_free_ char *link = NULL;
776 : int r;
777 :
778 3 : (void) pager_open(arg_pager_flags);
779 :
780 3 : r = terminal_urlify_man("portablectl", "1", &link);
781 3 : if (r < 0)
782 0 : return log_oom();
783 :
784 3 : printf("%s [OPTIONS...] {COMMAND} ...\n\n"
785 : "Attach or detach portable services from the local system.\n\n"
786 : " -h --help Show this help\n"
787 : " --version Show package version\n"
788 : " --no-pager Do not pipe output into a pager\n"
789 : " --no-legend Do not show the headers and footers\n"
790 : " --no-ask-password Do not ask for system passwords\n"
791 : " -H --host=[USER@]HOST Operate on remote host\n"
792 : " -M --machine=CONTAINER Operate on local container\n"
793 : " -q --quiet Suppress informational messages\n"
794 : " -p --profile=PROFILE Pick security profile for portable service\n"
795 : " --copy=copy|auto|symlink Prefer copying or symlinks if possible\n"
796 : " --runtime Attach portable service until next reboot only\n"
797 : " --no-reload Don't reload the system and service manager\n"
798 : " --cat When inspecting include unit and os-release file\n"
799 : " contents\n\n"
800 : "Commands:\n"
801 : " list List available portable service images\n"
802 : " attach NAME|PATH [PREFIX...]\n"
803 : " Attach the specified portable service image\n"
804 : " detach NAME|PATH Detach the specified portable service image\n"
805 : " inspect NAME|PATH [PREFIX...]\n"
806 : " Show details of specified portable service image\n"
807 : " is-attached NAME|PATH Query if portable service image is attached\n"
808 : " read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n"
809 : " remove NAME|PATH... Remove a portable service image\n"
810 : " set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n"
811 : "\nSee the %s for details.\n"
812 : , program_invocation_short_name
813 : , link
814 : );
815 :
816 3 : return 0;
817 : }
818 :
819 4 : static int parse_argv(int argc, char *argv[]) {
820 :
821 : enum {
822 : ARG_VERSION = 0x100,
823 : ARG_NO_PAGER,
824 : ARG_NO_LEGEND,
825 : ARG_NO_ASK_PASSWORD,
826 : ARG_COPY,
827 : ARG_RUNTIME,
828 : ARG_NO_RELOAD,
829 : ARG_CAT,
830 : };
831 :
832 : static const struct option options[] = {
833 : { "help", no_argument, NULL, 'h' },
834 : { "version", no_argument, NULL, ARG_VERSION },
835 : { "no-pager", no_argument, NULL, ARG_NO_PAGER },
836 : { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
837 : { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
838 : { "host", required_argument, NULL, 'H' },
839 : { "machine", required_argument, NULL, 'M' },
840 : { "quiet", no_argument, NULL, 'q' },
841 : { "profile", required_argument, NULL, 'p' },
842 : { "copy", required_argument, NULL, ARG_COPY },
843 : { "runtime", no_argument, NULL, ARG_RUNTIME },
844 : { "no-reload", no_argument, NULL, ARG_NO_RELOAD },
845 : { "cat", no_argument, NULL, ARG_CAT },
846 : {}
847 : };
848 :
849 4 : assert(argc >= 0);
850 4 : assert(argv);
851 :
852 0 : for (;;) {
853 : int c;
854 :
855 4 : c = getopt_long(argc, argv, "hH:M:qp:", options, NULL);
856 4 : if (c < 0)
857 0 : break;
858 :
859 4 : switch (c) {
860 :
861 3 : case 'h':
862 3 : return help(0, NULL, NULL);
863 :
864 0 : case ARG_VERSION:
865 0 : return version();
866 :
867 0 : case ARG_NO_PAGER:
868 0 : arg_pager_flags |= PAGER_DISABLE;
869 0 : break;
870 :
871 0 : case ARG_NO_LEGEND:
872 0 : arg_legend = false;
873 0 : break;
874 :
875 0 : case ARG_NO_ASK_PASSWORD:
876 0 : arg_ask_password = false;
877 0 : break;
878 :
879 0 : case 'H':
880 0 : arg_transport = BUS_TRANSPORT_REMOTE;
881 0 : arg_host = optarg;
882 0 : break;
883 :
884 0 : case 'M':
885 0 : arg_transport = BUS_TRANSPORT_MACHINE;
886 0 : arg_host = optarg;
887 0 : break;
888 :
889 0 : case 'q':
890 0 : arg_quiet = true;
891 0 : break;
892 :
893 0 : case 'p':
894 0 : if (streq(optarg, "help"))
895 0 : return dump_profiles();
896 :
897 0 : if (!filename_is_valid(optarg))
898 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
899 : "Unit profile name not valid: %s", optarg);
900 :
901 0 : arg_profile = optarg;
902 0 : break;
903 :
904 0 : case ARG_COPY:
905 0 : if (streq(optarg, "auto"))
906 0 : arg_copy_mode = NULL;
907 0 : else if (STR_IN_SET(optarg, "copy", "symlink"))
908 0 : arg_copy_mode = optarg;
909 0 : else if (streq(optarg, "help")) {
910 0 : puts("auto\n"
911 : "copy\n"
912 : "symlink");
913 0 : return 0;
914 : } else
915 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
916 : "Failed to parse --copy= argument: %s", optarg);
917 :
918 0 : break;
919 :
920 0 : case ARG_RUNTIME:
921 0 : arg_runtime = true;
922 0 : break;
923 :
924 0 : case ARG_NO_RELOAD:
925 0 : arg_reload = false;
926 0 : break;
927 :
928 0 : case ARG_CAT:
929 0 : arg_cat = true;
930 0 : break;
931 :
932 1 : case '?':
933 1 : return -EINVAL;
934 :
935 0 : default:
936 0 : assert_not_reached("Unhandled option");
937 : }
938 : }
939 :
940 0 : return 1;
941 : }
942 :
943 4 : static int run(int argc, char *argv[]) {
944 : static const Verb verbs[] = {
945 : { "help", VERB_ANY, VERB_ANY, 0, help },
946 : { "list", VERB_ANY, 1, VERB_DEFAULT, list_images },
947 : { "attach", 2, VERB_ANY, 0, attach_image },
948 : { "detach", 2, 2, 0, detach_image },
949 : { "inspect", 2, VERB_ANY, 0, inspect_image },
950 : { "is-attached", 2, 2, 0, is_image_attached },
951 : { "read-only", 2, 3, 0, read_only_image },
952 : { "remove", 2, VERB_ANY, 0, remove_image },
953 : { "set-limit", 3, 3, 0, set_limit },
954 : {}
955 : };
956 :
957 : int r;
958 :
959 4 : log_show_color(true);
960 4 : log_parse_environment();
961 4 : log_open();
962 :
963 4 : r = parse_argv(argc, argv);
964 4 : if (r <= 0)
965 4 : return r;
966 :
967 0 : return dispatch_verb(argc, argv, verbs, NULL);
968 : }
969 :
970 4 : DEFINE_MAIN_FUNCTION(run);
|