Branch data 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 : 12 : static int help(int argc, char *argv[], void *userdata) {
775 : 12 : _cleanup_free_ char *link = NULL;
776 : : int r;
777 : :
778 : 12 : (void) pager_open(arg_pager_flags);
779 : :
780 : 12 : r = terminal_urlify_man("portablectl", "1", &link);
781 [ - + ]: 12 : if (r < 0)
782 : 0 : return log_oom();
783 : :
784 : 12 : 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 : 12 : return 0;
817 : : }
818 : :
819 : 16 : 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 [ - + ]: 16 : assert(argc >= 0);
850 [ - + ]: 16 : assert(argv);
851 : :
852 : 0 : for (;;) {
853 : : int c;
854 : :
855 : 16 : c = getopt_long(argc, argv, "hH:M:qp:", options, NULL);
856 [ - + ]: 16 : if (c < 0)
857 : 0 : break;
858 : :
859 [ + - - - : 16 : switch (c) {
- - - - -
- - - - +
- ]
860 : :
861 : 12 : case 'h':
862 : 12 : 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 : 4 : case '?':
933 : 4 : return -EINVAL;
934 : :
935 : 0 : default:
936 : 0 : assert_not_reached("Unhandled option");
937 : : }
938 : : }
939 : :
940 : 0 : return 1;
941 : : }
942 : :
943 : 16 : 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 : 16 : log_show_color(true);
960 : 16 : log_parse_environment();
961 : 16 : log_open();
962 : :
963 : 16 : r = parse_argv(argc, argv);
964 [ + - ]: 16 : if (r <= 0)
965 : 16 : return r;
966 : :
967 : 0 : return dispatch_verb(argc, argv, verbs, NULL);
968 : : }
969 : :
970 : 16 : DEFINE_MAIN_FUNCTION(run);
|