]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/portable/portablectl.c
Merge pull request #21838 from lnussel/logind-refactor
[thirdparty/systemd.git] / src / portable / portablectl.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
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-locator.h"
11 #include "bus-unit-util.h"
12 #include "bus-wait-for-jobs.h"
13 #include "chase-symlinks.h"
14 #include "def.h"
15 #include "dirent-util.h"
16 #include "env-file.h"
17 #include "fd-util.h"
18 #include "fileio.h"
19 #include "format-table.h"
20 #include "fs-util.h"
21 #include "locale-util.h"
22 #include "main-func.h"
23 #include "os-util.h"
24 #include "pager.h"
25 #include "parse-argument.h"
26 #include "parse-util.h"
27 #include "path-util.h"
28 #include "portable.h"
29 #include "pretty-print.h"
30 #include "spawn-polkit-agent.h"
31 #include "string-util.h"
32 #include "strv.h"
33 #include "terminal-util.h"
34 #include "verbs.h"
35
36 static PagerFlags arg_pager_flags = 0;
37 static bool arg_legend = true;
38 static bool arg_ask_password = true;
39 static bool arg_quiet = false;
40 static const char *arg_profile = "default";
41 static const char* arg_copy_mode = NULL;
42 static bool arg_runtime = false;
43 static bool arg_reload = true;
44 static bool arg_cat = false;
45 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
46 static const char *arg_host = NULL;
47 static bool arg_enable = false;
48 static bool arg_now = false;
49 static bool arg_no_block = false;
50 static char **arg_extension_images = NULL;
51
52 STATIC_DESTRUCTOR_REGISTER(arg_extension_images, strv_freep);
53
54 static bool is_portable_managed(const char *unit) {
55 return ENDSWITH_SET(unit, ".service", ".target", ".socket", ".path", ".timer");
56 }
57
58 static int determine_image(const char *image, bool permit_non_existing, char **ret) {
59 int r;
60
61 /* If the specified name is a valid image name, we pass it as-is to portabled, which will search for it in the
62 * usual search directories. Otherwise we presume it's a path, and will normalize it on the client's side
63 * (among other things, to make the path independent of the client's working directory) before passing it
64 * over. */
65
66 if (image_name_is_valid(image)) {
67 char *c;
68
69 if (!arg_quiet && laccess(image, F_OK) >= 0)
70 log_warning("Ambiguous invocation: current working directory contains file matching non-path argument '%s', ignoring. "
71 "Prefix argument with './' to force reference to file in current working directory.", image);
72
73 c = strdup(image);
74 if (!c)
75 return log_oom();
76
77 *ret = c;
78 return 0;
79 }
80
81 if (arg_transport != BUS_TRANSPORT_LOCAL)
82 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
83 "Operations on images by path not supported when connecting to remote systems.");
84
85 r = chase_symlinks(image, NULL, CHASE_TRAIL_SLASH | (permit_non_existing ? CHASE_NONEXISTENT : 0), ret, NULL);
86 if (r < 0)
87 return log_error_errno(r, "Cannot normalize specified image path '%s': %m", image);
88
89 return 0;
90 }
91
92 static int attach_extensions_to_message(sd_bus_message *m, char **extensions) {
93 char **p;
94 int r;
95
96 assert(m);
97
98 if (strv_isempty(extensions))
99 return 0;
100
101 r = sd_bus_message_open_container(m, 'a', "s");
102 if (r < 0)
103 return bus_log_create_error(r);
104
105 STRV_FOREACH(p, extensions) {
106 _cleanup_free_ char *resolved_extension_image = NULL;
107
108 r = determine_image(*p, false, &resolved_extension_image);
109 if (r < 0)
110 return r;
111
112 r = sd_bus_message_append(m, "s", resolved_extension_image);
113 if (r < 0)
114 return bus_log_create_error(r);
115 }
116
117 r = sd_bus_message_close_container(m);
118 if (r < 0)
119 return bus_log_create_error(r);
120
121 return 0;
122 }
123
124 static int extract_prefix(const char *path, char **ret) {
125 _cleanup_free_ char *name = NULL;
126 const char *bn, *underscore;
127 size_t m;
128
129 bn = basename(path);
130
131 underscore = strchr(bn, '_');
132 if (underscore)
133 m = underscore - bn;
134 else {
135 const char *e;
136
137 e = endswith(bn, ".raw");
138 if (!e)
139 e = strchr(bn, 0);
140
141 m = e - bn;
142 }
143
144 name = strndup(bn, m);
145 if (!name)
146 return -ENOMEM;
147
148 /* A slightly reduced version of what's permitted in unit names. With ':' and '\' are removed, as well as '_'
149 * which we use as delimiter for the second part of the image string, which we ignore for now. */
150 if (!in_charset(name, DIGITS LETTERS "-."))
151 return -EINVAL;
152
153 if (!filename_is_valid(name))
154 return -EINVAL;
155
156 *ret = TAKE_PTR(name);
157
158 return 0;
159 }
160
161 static int determine_matches(const char *image, char **l, bool allow_any, char ***ret) {
162 _cleanup_strv_free_ char **k = NULL;
163 int r;
164
165 /* Determine the matches to apply. If the list is empty we derive the match from the image name. If the list
166 * contains exactly the "-" we return a wildcard list (which is the empty list), but only if this is expressly
167 * permitted. */
168
169 if (strv_isempty(l)) {
170 char *prefix;
171
172 r = extract_prefix(image, &prefix);
173 if (r < 0)
174 return log_error_errno(r, "Failed to extract prefix of image name '%s': %m", image);
175
176 if (!arg_quiet)
177 log_info("(Matching unit files with prefix '%s'.)", prefix);
178
179 r = strv_consume(&k, prefix);
180 if (r < 0)
181 return log_oom();
182
183 } else if (strv_equal(l, STRV_MAKE("-"))) {
184
185 if (!allow_any)
186 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
187 "Refusing all unit file match.");
188
189 if (!arg_quiet)
190 log_info("(Matching all unit files.)");
191 } else {
192
193 k = strv_copy(l);
194 if (!k)
195 return log_oom();
196
197 if (!arg_quiet) {
198 _cleanup_free_ char *joined = NULL;
199
200 joined = strv_join(k, "', '");
201 if (!joined)
202 return log_oom();
203
204 log_info("(Matching unit files with prefixes '%s'.)", joined);
205 }
206 }
207
208 *ret = TAKE_PTR(k);
209
210 return 0;
211 }
212
213 static int acquire_bus(sd_bus **bus) {
214 int r;
215
216 assert(bus);
217
218 if (*bus)
219 return 0;
220
221 r = bus_connect_transport(arg_transport, arg_host, false, bus);
222 if (r < 0)
223 return bus_log_connect_error(r, arg_transport);
224
225 (void) sd_bus_set_allow_interactive_authorization(*bus, arg_ask_password);
226
227 return 0;
228 }
229
230 static int maybe_reload(sd_bus **bus) {
231 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
232 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
233 int r;
234
235 if (!arg_reload)
236 return 0;
237
238 r = acquire_bus(bus);
239 if (r < 0)
240 return r;
241
242 r = sd_bus_message_new_method_call(
243 *bus,
244 &m,
245 "org.freedesktop.systemd1",
246 "/org/freedesktop/systemd1",
247 "org.freedesktop.systemd1.Manager",
248 "Reload");
249 if (r < 0)
250 return bus_log_create_error(r);
251
252 /* Reloading the daemon may take long, hence set a longer timeout here */
253 r = sd_bus_call(*bus, m, DEFAULT_TIMEOUT_USEC * 2, &error, NULL);
254 if (r < 0)
255 return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r));
256
257 return 0;
258 }
259
260 static int get_image_metadata(sd_bus *bus, const char *image, char **matches, sd_bus_message **reply) {
261 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
262 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
263 PortableFlags flags = PORTABLE_INSPECT_EXTENSION_RELEASES;
264 const char *method;
265 int r;
266
267 assert(bus);
268 assert(reply);
269
270 method = strv_isempty(arg_extension_images) ? "GetImageMetadata" : "GetImageMetadataWithExtensions";
271
272 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method);
273 if (r < 0)
274 return bus_log_create_error(r);
275
276 r = sd_bus_message_append(m, "s", image);
277 if (r < 0)
278 return bus_log_create_error(r);
279
280 r = attach_extensions_to_message(m, arg_extension_images);
281 if (r < 0)
282 return r;
283
284 r = sd_bus_message_append_strv(m, matches);
285 if (r < 0)
286 return bus_log_create_error(r);
287
288 if (!strv_isempty(arg_extension_images)) {
289 r = sd_bus_message_append(m, "t", flags);
290 if (r < 0)
291 return bus_log_create_error(r);
292 }
293
294 r = sd_bus_call(bus, m, 0, &error, reply);
295 if (r < 0)
296 return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(&error, r));
297
298 return 0;
299 }
300
301 static int inspect_image(int argc, char *argv[], void *userdata) {
302 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
303 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
304 _cleanup_strv_free_ char **matches = NULL;
305 _cleanup_free_ char *image = NULL;
306 bool nl = false, header = false;
307 const char *path;
308 const void *data;
309 size_t sz;
310 int r;
311
312 r = determine_image(argv[1], false, &image);
313 if (r < 0)
314 return r;
315
316 r = determine_matches(argv[1], argv + 2, true, &matches);
317 if (r < 0)
318 return r;
319
320 r = acquire_bus(&bus);
321 if (r < 0)
322 return r;
323
324 r = get_image_metadata(bus, image, matches, &reply);
325 if (r < 0)
326 return r;
327
328 r = sd_bus_message_read(reply, "s", &path);
329 if (r < 0)
330 return bus_log_parse_error(r);
331
332 r = sd_bus_message_read_array(reply, 'y', &data, &sz);
333 if (r < 0)
334 return bus_log_parse_error(r);
335
336 pager_open(arg_pager_flags);
337
338 if (arg_cat) {
339 printf("%s-- OS Release: --%s\n", ansi_highlight(), ansi_normal());
340 fwrite(data, sz, 1, stdout);
341 fflush(stdout);
342 nl = true;
343 } else {
344 _cleanup_free_ char *pretty_portable = NULL, *pretty_os = NULL;
345 _cleanup_fclose_ FILE *f = NULL;
346
347 f = fmemopen_unlocked((void*) data, sz, "re");
348 if (!f)
349 return log_error_errno(errno, "Failed to open /etc/os-release buffer: %m");
350
351 r = parse_env_file(f, "/etc/os-release",
352 "PORTABLE_PRETTY_NAME", &pretty_portable,
353 "PRETTY_NAME", &pretty_os);
354 if (r < 0)
355 return log_error_errno(r, "Failed to parse /etc/os-release: %m");
356
357 printf("Image:\n\t%s\n"
358 "Portable Service:\n\t%s\n"
359 "Operating System:\n\t%s\n",
360 path,
361 strna(pretty_portable),
362 strna(pretty_os));
363 }
364
365 r = sd_bus_message_enter_container(reply, 'a', "{say}");
366 if (r < 0)
367 return bus_log_parse_error(r);
368
369 /* If we specified any extensions, we'll first get back exactly the
370 * paths (and extension-release content) for each one of the arguments. */
371 for (size_t i = 0; i < strv_length(arg_extension_images); ++i) {
372 const char *name;
373
374 r = sd_bus_message_enter_container(reply, 'e', "say");
375 if (r < 0)
376 return bus_log_parse_error(r);
377 if (r == 0)
378 break;
379
380 r = sd_bus_message_read(reply, "s", &name);
381 if (r < 0)
382 return bus_log_parse_error(r);
383
384 r = sd_bus_message_read_array(reply, 'y', &data, &sz);
385 if (r < 0)
386 return bus_log_parse_error(r);
387
388 if (arg_cat) {
389 if (nl)
390 fputc('\n', stdout);
391
392 printf("%s-- Extension Release: %s --%s\n", ansi_highlight(), name, ansi_normal());
393 fwrite(data, sz, 1, stdout);
394 fflush(stdout);
395 nl = true;
396 } else {
397 _cleanup_free_ char *pretty_portable = NULL, *pretty_os = NULL, *sysext_level = NULL,
398 *id = NULL, *version_id = NULL, *sysext_scope = NULL, *portable_prefixes = NULL;
399 _cleanup_fclose_ FILE *f = NULL;
400
401 f = fmemopen_unlocked((void*) data, sz, "re");
402 if (!f)
403 return log_error_errno(errno, "Failed to open extension-release buffer: %m");
404
405 r = parse_env_file(f, name,
406 "ID", &id,
407 "VERSION_ID", &version_id,
408 "SYSEXT_SCOPE", &sysext_scope,
409 "SYSEXT_LEVEL", &sysext_level,
410 "PORTABLE_PRETTY_NAME", &pretty_portable,
411 "PORTABLE_PREFIXES", &portable_prefixes,
412 "PRETTY_NAME", &pretty_os);
413 if (r < 0)
414 return log_error_errno(r, "Failed to parse extension release from '%s': %m", name);
415
416 printf("Extension:\n\t%s\n"
417 "\tExtension Scope:\n\t\t%s\n"
418 "\tExtension Compatibility Level:\n\t\t%s\n"
419 "\tPortable Service:\n\t\t%s\n"
420 "\tPortable Prefixes:\n\t\t%s\n"
421 "\tOperating System:\n\t\t%s (%s %s)\n",
422 name,
423 strna(sysext_scope),
424 strna(sysext_level),
425 strna(pretty_portable),
426 strna(portable_prefixes),
427 strna(pretty_os),
428 strna(id),
429 strna(version_id));
430 }
431
432 r = sd_bus_message_exit_container(reply);
433 if (r < 0)
434 return bus_log_parse_error(r);
435 }
436
437 for (;;) {
438 const char *name;
439
440 r = sd_bus_message_enter_container(reply, 'e', "say");
441 if (r < 0)
442 return bus_log_parse_error(r);
443 if (r == 0)
444 break;
445
446 r = sd_bus_message_read(reply, "s", &name);
447 if (r < 0)
448 return bus_log_parse_error(r);
449
450 r = sd_bus_message_read_array(reply, 'y', &data, &sz);
451 if (r < 0)
452 return bus_log_parse_error(r);
453
454 if (arg_cat) {
455 if (nl)
456 fputc('\n', stdout);
457
458 printf("%s-- Unit file: %s --%s\n", ansi_highlight(), name, ansi_normal());
459 fwrite(data, sz, 1, stdout);
460 fflush(stdout);
461 nl = true;
462 } else {
463 if (!header) {
464 fputs("Unit files:\n", stdout);
465 header = true;
466 }
467
468 fputc('\t', stdout);
469 fputs(name, stdout);
470 fputc('\n', stdout);
471 }
472
473 r = sd_bus_message_exit_container(reply);
474 if (r < 0)
475 return bus_log_parse_error(r);
476 }
477
478 r = sd_bus_message_exit_container(reply);
479 if (r < 0)
480 return bus_log_parse_error(r);
481
482 return 0;
483 }
484
485 static int print_changes(sd_bus_message *m) {
486 int r;
487
488 if (arg_quiet)
489 return 0;
490
491 r = sd_bus_message_enter_container(m, 'a', "(sss)");
492 if (r < 0)
493 return bus_log_parse_error(r);
494
495 for (;;) {
496 const char *type, *path, *source;
497
498 r = sd_bus_message_read(m, "(sss)", &type, &path, &source);
499 if (r < 0)
500 return bus_log_parse_error(r);
501 if (r == 0)
502 break;
503
504 if (streq(type, "symlink"))
505 log_info("Created symlink %s %s %s.", path, special_glyph(SPECIAL_GLYPH_ARROW), source);
506 else if (streq(type, "copy")) {
507 if (isempty(source))
508 log_info("Copied %s.", path);
509 else
510 log_info("Copied %s %s %s.", source, special_glyph(SPECIAL_GLYPH_ARROW), path);
511 } else if (streq(type, "unlink"))
512 log_info("Removed %s.", path);
513 else if (streq(type, "write"))
514 log_info("Written %s.", path);
515 else if (streq(type, "mkdir"))
516 log_info("Created directory %s.", path);
517 else
518 log_error("Unexpected change: %s/%s/%s", type, path, source);
519 }
520
521 r = sd_bus_message_exit_container(m);
522 if (r < 0)
523 return r;
524
525 return 0;
526 }
527
528 static int maybe_enable_disable(sd_bus *bus, const char *path, bool enable) {
529 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
530 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
531 _cleanup_strv_free_ char **names = NULL;
532 UnitFileChange *changes = NULL;
533 const uint64_t flags = UNIT_FILE_PORTABLE | (arg_runtime ? UNIT_FILE_RUNTIME : 0);
534 size_t n_changes = 0;
535 int r;
536
537 if (!arg_enable)
538 return 0;
539
540 names = strv_new(path, NULL);
541 if (!names)
542 return log_oom();
543
544 r = sd_bus_message_new_method_call(
545 bus,
546 &m,
547 "org.freedesktop.systemd1",
548 "/org/freedesktop/systemd1",
549 "org.freedesktop.systemd1.Manager",
550 enable ? "EnableUnitFilesWithFlags" : "DisableUnitFilesWithFlags");
551 if (r < 0)
552 return bus_log_create_error(r);
553
554 r = sd_bus_message_append_strv(m, names);
555 if (r < 0)
556 return bus_log_create_error(r);
557
558 r = sd_bus_message_append(m, "t", flags);
559 if (r < 0)
560 return bus_log_create_error(r);
561
562 r = sd_bus_call(bus, m, 0, &error, &reply);
563 if (r < 0)
564 return log_error_errno(r, "Failed to %s the portable service %s: %s",
565 enable ? "enable" : "disable", path, bus_error_message(&error, r));
566
567 if (enable) {
568 r = sd_bus_message_skip(reply, "b");
569 if (r < 0)
570 return bus_log_parse_error(r);
571 }
572 (void) bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
573
574 return 0;
575 }
576
577 static int maybe_start_stop_restart(sd_bus *bus, const char *path, const char *method, BusWaitForJobs *wait) {
578 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
579 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
580 char *name = (char *)basename(path), *job = NULL;
581 int r;
582
583 assert(STR_IN_SET(method, "StartUnit", "StopUnit", "RestartUnit"));
584
585 if (!arg_now)
586 return 0;
587
588 r = sd_bus_call_method(
589 bus,
590 "org.freedesktop.systemd1",
591 "/org/freedesktop/systemd1",
592 "org.freedesktop.systemd1.Manager",
593 method,
594 &error,
595 &reply,
596 "ss", name, "replace");
597 if (r < 0)
598 return log_error_errno(r, "Failed to call %s on the portable service %s: %s",
599 method,
600 path,
601 bus_error_message(&error, r));
602
603 r = sd_bus_message_read(reply, "o", &job);
604 if (r < 0)
605 return bus_log_parse_error(r);
606
607 if (!arg_quiet)
608 log_info("Queued %s to call %s on portable service %s.", job, method, name);
609
610 if (wait) {
611 r = bus_wait_for_jobs_add(wait, job);
612 if (r < 0)
613 return log_error_errno(r, "Failed to watch %s job to call %s on %s: %m",
614 job, method, name);
615 }
616
617 return 0;
618 }
619
620 static int maybe_enable_start(sd_bus *bus, sd_bus_message *reply) {
621 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
622 int r;
623
624 if (!arg_enable && !arg_now)
625 return 0;
626
627 if (!arg_no_block) {
628 r = bus_wait_for_jobs_new(bus, &wait);
629 if (r < 0)
630 return log_error_errno(r, "Could not watch jobs: %m");
631 }
632
633 r = sd_bus_message_rewind(reply, true);
634 if (r < 0)
635 return r;
636 r = sd_bus_message_enter_container(reply, 'a', "(sss)");
637 if (r < 0)
638 return bus_log_parse_error(r);
639
640 for (;;) {
641 char *type, *path, *source;
642
643 r = sd_bus_message_read(reply, "(sss)", &type, &path, &source);
644 if (r < 0)
645 return bus_log_parse_error(r);
646 if (r == 0)
647 break;
648
649 if (STR_IN_SET(type, "symlink", "copy") && is_portable_managed(path)) {
650 (void) maybe_enable_disable(bus, path, true);
651 (void) maybe_start_stop_restart(bus, path, "StartUnit", wait);
652 }
653 }
654
655 r = sd_bus_message_exit_container(reply);
656 if (r < 0)
657 return r;
658
659 if (!arg_no_block) {
660 r = bus_wait_for_jobs(wait, arg_quiet, NULL);
661 if (r < 0)
662 return r;
663 }
664
665 return 0;
666 }
667
668 static int maybe_stop_enable_restart(sd_bus *bus, sd_bus_message *reply) {
669 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
670 int r;
671
672 if (!arg_enable && !arg_now)
673 return 0;
674
675 if (!arg_no_block) {
676 r = bus_wait_for_jobs_new(bus, &wait);
677 if (r < 0)
678 return log_error_errno(r, "Could not watch jobs: %m");
679 }
680
681 r = sd_bus_message_rewind(reply, true);
682 if (r < 0)
683 return r;
684
685 /* First we get a list of units that were definitely removed, not just re-attached,
686 * so we can also stop them if the user asked us to. */
687 r = sd_bus_message_enter_container(reply, 'a', "(sss)");
688 if (r < 0)
689 return bus_log_parse_error(r);
690
691 for (;;) {
692 char *type, *path, *source;
693
694 r = sd_bus_message_read(reply, "(sss)", &type, &path, &source);
695 if (r < 0)
696 return bus_log_parse_error(r);
697 if (r == 0)
698 break;
699
700 if (streq(type, "unlink") && is_portable_managed(path))
701 (void) maybe_start_stop_restart(bus, path, "StopUnit", wait);
702 }
703
704 r = sd_bus_message_exit_container(reply);
705 if (r < 0)
706 return r;
707
708 /* Then we get a list of units that were either added or changed, so that we can
709 * enable them and/or restart them if the user asked us to. */
710 r = sd_bus_message_enter_container(reply, 'a', "(sss)");
711 if (r < 0)
712 return bus_log_parse_error(r);
713
714 for (;;) {
715 char *type, *path, *source;
716
717 r = sd_bus_message_read(reply, "(sss)", &type, &path, &source);
718 if (r < 0)
719 return bus_log_parse_error(r);
720 if (r == 0)
721 break;
722
723 if (STR_IN_SET(type, "symlink", "copy") && is_portable_managed(path)) {
724 (void) maybe_enable_disable(bus, path, true);
725 (void) maybe_start_stop_restart(bus, path, "RestartUnit", wait);
726 }
727 }
728
729 r = sd_bus_message_exit_container(reply);
730 if (r < 0)
731 return r;
732
733 if (!arg_no_block) {
734 r = bus_wait_for_jobs(wait, arg_quiet, NULL);
735 if (r < 0)
736 return r;
737 }
738
739 return 0;
740 }
741
742 static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) {
743 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
744 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
745 _cleanup_strv_free_ char **matches = NULL;
746 int r;
747
748 if (!arg_enable && !arg_now)
749 return 0;
750
751 r = determine_matches(argv[1], argv + 2, true, &matches);
752 if (r < 0)
753 return r;
754
755 r = bus_wait_for_jobs_new(bus, &wait);
756 if (r < 0)
757 return log_error_errno(r, "Could not watch jobs: %m");
758
759 r = get_image_metadata(bus, image, matches, &reply);
760 if (r < 0)
761 return r;
762
763 r = sd_bus_message_skip(reply, "say");
764 if (r < 0)
765 return bus_log_parse_error(r);
766
767 r = sd_bus_message_enter_container(reply, 'a', "{say}");
768 if (r < 0)
769 return bus_log_parse_error(r);
770
771 /* If we specified any extensions, we'll first get back exactly the
772 * paths (and extension-release content) for each one of the arguments. */
773 for (size_t i = 0; i < strv_length(arg_extension_images); ++i) {
774 r = sd_bus_message_skip(reply, "{say}");
775 if (r < 0)
776 return bus_log_parse_error(r);
777 }
778
779 for (;;) {
780 const char *name;
781
782 r = sd_bus_message_enter_container(reply, 'e', "say");
783 if (r < 0)
784 return bus_log_parse_error(r);
785 if (r == 0)
786 break;
787
788 r = sd_bus_message_read(reply, "s", &name);
789 if (r < 0)
790 return bus_log_parse_error(r);
791
792 r = sd_bus_message_skip(reply, "ay");
793 if (r < 0)
794 return bus_log_parse_error(r);
795
796 r = sd_bus_message_exit_container(reply);
797 if (r < 0)
798 return bus_log_parse_error(r);
799
800 (void) maybe_start_stop_restart(bus, name, "StopUnit", wait);
801 (void) maybe_enable_disable(bus, name, false);
802 }
803
804 r = sd_bus_message_exit_container(reply);
805 if (r < 0)
806 return bus_log_parse_error(r);
807
808 /* Stopping must always block or the detach will fail if the unit is still running */
809 r = bus_wait_for_jobs(wait, arg_quiet, NULL);
810 if (r < 0)
811 return r;
812
813 return 0;
814 }
815
816 static int attach_reattach_image(int argc, char *argv[], const char *method) {
817 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
818 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
819 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
820 _cleanup_strv_free_ char **matches = NULL;
821 _cleanup_free_ char *image = NULL;
822 int r;
823
824 assert(method);
825 assert(STR_IN_SET(method, "AttachImage", "ReattachImage", "AttachImageWithExtensions", "ReattachImageWithExtensions"));
826
827 r = determine_image(argv[1], false, &image);
828 if (r < 0)
829 return r;
830
831 r = determine_matches(argv[1], argv + 2, false, &matches);
832 if (r < 0)
833 return r;
834
835 r = acquire_bus(&bus);
836 if (r < 0)
837 return r;
838
839 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
840
841 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method);
842 if (r < 0)
843 return bus_log_create_error(r);
844
845 r = sd_bus_message_append(m, "s", image);
846 if (r < 0)
847 return bus_log_create_error(r);
848
849 r = attach_extensions_to_message(m, arg_extension_images);
850 if (r < 0)
851 return r;
852
853 r = sd_bus_message_append_strv(m, matches);
854 if (r < 0)
855 return bus_log_create_error(r);
856
857 r = sd_bus_message_append(m, "s", arg_profile);
858 if (r < 0)
859 return bus_log_create_error(r);
860
861 if (STR_IN_SET(method, "AttachImageWithExtensions", "ReattachImageWithExtensions")) {
862 uint64_t flags = arg_runtime ? PORTABLE_RUNTIME : 0;
863
864 r = sd_bus_message_append(m, "st", arg_copy_mode, flags);
865 } else
866 r = sd_bus_message_append(m, "bs", arg_runtime, arg_copy_mode);
867 if (r < 0)
868 return bus_log_create_error(r);
869
870 r = sd_bus_call(bus, m, 0, &error, &reply);
871 if (r < 0)
872 return log_error_errno(r, "%s failed: %s", method, bus_error_message(&error, r));
873
874 (void) maybe_reload(&bus);
875
876 print_changes(reply);
877
878 if (STR_IN_SET(method, "AttachImage", "AttachImageWithExtensions"))
879 (void) maybe_enable_start(bus, reply);
880 else {
881 /* ReattachImage returns 2 lists - removed units first, and changed/added second */
882 print_changes(reply);
883 (void) maybe_stop_enable_restart(bus, reply);
884 }
885
886 return 0;
887 }
888
889 static int attach_image(int argc, char *argv[], void *userdata) {
890 return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) ? "AttachImage" : "AttachImageWithExtensions");
891 }
892
893 static int reattach_image(int argc, char *argv[], void *userdata) {
894 return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) ? "ReattachImage" : "ReattachImageWithExtensions");
895 }
896
897 static int detach_image(int argc, char *argv[], void *userdata) {
898 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
899 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
900 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
901 _cleanup_free_ char *image = NULL;
902 const char *method;
903 int r;
904
905 r = determine_image(argv[1], true, &image);
906 if (r < 0)
907 return r;
908
909 r = acquire_bus(&bus);
910 if (r < 0)
911 return r;
912
913 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
914
915 (void) maybe_stop_disable(bus, image, argv);
916
917 method = strv_isempty(arg_extension_images) ? "DetachImage" : "DetachImageWithExtensions";
918
919 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method);
920 if (r < 0)
921 return bus_log_create_error(r);
922
923 r = sd_bus_message_append(m, "s", image);
924 if (r < 0)
925 return bus_log_create_error(r);
926
927 r = attach_extensions_to_message(m, arg_extension_images);
928 if (r < 0)
929 return r;
930
931 if (!strv_isempty(arg_extension_images)) {
932 uint64_t flags = arg_runtime ? PORTABLE_RUNTIME : 0;
933
934 r = sd_bus_message_append(m, "t", flags);
935 } else
936 r = sd_bus_message_append(m, "b", arg_runtime);
937 if (r < 0)
938 return bus_log_create_error(r);
939
940 r = sd_bus_call(bus, m, 0, &error, &reply);
941 if (r < 0)
942 return log_error_errno(r, "%s failed: %s", method, bus_error_message(&error, r));
943
944 (void) maybe_reload(&bus);
945
946 print_changes(reply);
947 return 0;
948 }
949
950 static int list_images(int argc, char *argv[], void *userdata) {
951 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
952 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
953 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
954 _cleanup_(table_unrefp) Table *table = NULL;
955 int r;
956
957 r = acquire_bus(&bus);
958 if (r < 0)
959 return r;
960
961 r = bus_call_method(bus, bus_portable_mgr, "ListImages", &error, &reply, NULL);
962 if (r < 0)
963 return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r));
964
965 table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state");
966 if (!table)
967 return log_oom();
968
969 r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)");
970 if (r < 0)
971 return bus_log_parse_error(r);
972
973 for (;;) {
974 const char *name, *type, *state;
975 uint64_t crtime, mtime, usage;
976 int ro_int;
977
978 r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, NULL);
979 if (r < 0)
980 return bus_log_parse_error(r);
981 if (r == 0)
982 break;
983
984 r = table_add_many(table,
985 TABLE_STRING, name,
986 TABLE_STRING, type,
987 TABLE_BOOLEAN, ro_int,
988 TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL,
989 TABLE_TIMESTAMP, crtime,
990 TABLE_TIMESTAMP, mtime,
991 TABLE_SIZE, usage,
992 TABLE_STRING, state,
993 TABLE_SET_COLOR, !streq(state, "detached") ? ansi_highlight_green() : NULL);
994 if (r < 0)
995 return table_log_add_error(r);
996 }
997
998 r = sd_bus_message_exit_container(reply);
999 if (r < 0)
1000 return bus_log_parse_error(r);
1001
1002 if (table_get_rows(table) > 1) {
1003 r = table_set_sort(table, (size_t) 0);
1004 if (r < 0)
1005 return table_log_sort_error(r);
1006
1007 table_set_header(table, arg_legend);
1008
1009 r = table_print(table, NULL);
1010 if (r < 0)
1011 return table_log_print_error(r);
1012 }
1013
1014 if (arg_legend) {
1015 if (table_get_rows(table) > 1)
1016 printf("\n%zu images listed.\n", table_get_rows(table) - 1);
1017 else
1018 printf("No images.\n");
1019 }
1020
1021 return 0;
1022 }
1023
1024 static int remove_image(int argc, char *argv[], void *userdata) {
1025 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1026 int r, i;
1027
1028 r = acquire_bus(&bus);
1029 if (r < 0)
1030 return r;
1031
1032 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
1033
1034 for (i = 1; i < argc; i++) {
1035 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1036 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1037
1038 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "RemoveImage");
1039 if (r < 0)
1040 return bus_log_create_error(r);
1041
1042 r = sd_bus_message_append(m, "s", argv[i]);
1043 if (r < 0)
1044 return bus_log_create_error(r);
1045
1046 /* This is a slow operation, hence turn off any method call timeouts */
1047 r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
1048 if (r < 0)
1049 return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r));
1050 }
1051
1052 return 0;
1053 }
1054
1055 static int read_only_image(int argc, char *argv[], void *userdata) {
1056 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1057 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1058 int b = true, r;
1059
1060 if (argc > 2) {
1061 b = parse_boolean(argv[2]);
1062 if (b < 0)
1063 return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]);
1064 }
1065
1066 r = acquire_bus(&bus);
1067 if (r < 0)
1068 return r;
1069
1070 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
1071
1072 r = bus_call_method(bus, bus_portable_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b);
1073 if (r < 0)
1074 return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r));
1075
1076 return 0;
1077 }
1078
1079 static int set_limit(int argc, char *argv[], void *userdata) {
1080 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1081 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1082 uint64_t limit;
1083 int r;
1084
1085 r = acquire_bus(&bus);
1086 if (r < 0)
1087 return r;
1088
1089 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
1090
1091 if (STR_IN_SET(argv[argc-1], "-", "none", "infinity"))
1092 limit = UINT64_MAX;
1093 else {
1094 r = parse_size(argv[argc-1], 1024, &limit);
1095 if (r < 0)
1096 return log_error_errno(r, "Failed to parse size: %s", argv[argc-1]);
1097 }
1098
1099 if (argc > 2)
1100 /* With two arguments changes the quota limit of the specified image */
1101 r = bus_call_method(bus, bus_portable_mgr, "SetImageLimit", &error, NULL, "st", argv[1], limit);
1102 else
1103 /* With one argument changes the pool quota limit */
1104 r = bus_call_method(bus, bus_portable_mgr, "SetPoolLimit", &error, NULL, "t", limit);
1105
1106 if (r < 0)
1107 return log_error_errno(r, "Could not set limit: %s", bus_error_message(&error, r));
1108
1109 return 0;
1110 }
1111
1112 static int is_image_attached(int argc, char *argv[], void *userdata) {
1113 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
1114 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1115 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1116 _cleanup_free_ char *image = NULL;
1117 const char *state, *method;
1118 int r;
1119
1120 r = determine_image(argv[1], true, &image);
1121 if (r < 0)
1122 return r;
1123
1124 r = acquire_bus(&bus);
1125 if (r < 0)
1126 return r;
1127
1128 method = strv_isempty(arg_extension_images) ? "GetImageState" : "GetImageStateWithExtensions";
1129
1130 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method);
1131 if (r < 0)
1132 return bus_log_create_error(r);
1133
1134 r = sd_bus_message_append(m, "s", image);
1135 if (r < 0)
1136 return bus_log_create_error(r);
1137
1138 r = attach_extensions_to_message(m, arg_extension_images);
1139 if (r < 0)
1140 return r;
1141
1142 if (!strv_isempty(arg_extension_images)) {
1143 r = sd_bus_message_append(m, "t", 0);
1144 if (r < 0)
1145 return bus_log_create_error(r);
1146 }
1147
1148 r = sd_bus_call(bus, m, 0, &error, &reply);
1149 if (r < 0)
1150 return log_error_errno(r, "%s failed: %s", method, bus_error_message(&error, r));
1151
1152 r = sd_bus_message_read(reply, "s", &state);
1153 if (r < 0)
1154 return r;
1155
1156 if (!arg_quiet)
1157 puts(state);
1158
1159 return streq(state, "detached");
1160 }
1161
1162 static int dump_profiles(void) {
1163 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1164 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1165 _cleanup_strv_free_ char **l = NULL;
1166 char **i;
1167 int r;
1168
1169 r = acquire_bus(&bus);
1170 if (r < 0)
1171 return r;
1172
1173 r = bus_get_property_strv(bus, bus_portable_mgr, "Profiles", &error, &l);
1174 if (r < 0)
1175 return log_error_errno(r, "Failed to acquire list of profiles: %s", bus_error_message(&error, r));
1176
1177 if (arg_legend)
1178 log_info("Available unit profiles:");
1179
1180 STRV_FOREACH(i, l) {
1181 fputs(*i, stdout);
1182 fputc('\n', stdout);
1183 }
1184
1185 return 0;
1186 }
1187
1188 static int help(int argc, char *argv[], void *userdata) {
1189 _cleanup_free_ char *link = NULL;
1190 int r;
1191
1192 pager_open(arg_pager_flags);
1193
1194 r = terminal_urlify_man("portablectl", "1", &link);
1195 if (r < 0)
1196 return log_oom();
1197
1198 printf("%s [OPTIONS...] COMMAND ...\n\n"
1199 "%sAttach or detach portable services from the local system.%s\n"
1200 "\nCommands:\n"
1201 " list List available portable service images\n"
1202 " attach NAME|PATH [PREFIX...]\n"
1203 " Attach the specified portable service image\n"
1204 " detach NAME|PATH [PREFIX...]\n"
1205 " Detach the specified portable service image\n"
1206 " reattach NAME|PATH [PREFIX...]\n"
1207 " Reattach the specified portable service image\n"
1208 " inspect NAME|PATH [PREFIX...]\n"
1209 " Show details of specified portable service image\n"
1210 " is-attached NAME|PATH Query if portable service image is attached\n"
1211 " read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n"
1212 " remove NAME|PATH... Remove a portable service image\n"
1213 " set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n"
1214 "\nOptions:\n"
1215 " -h --help Show this help\n"
1216 " --version Show package version\n"
1217 " --no-pager Do not pipe output into a pager\n"
1218 " --no-legend Do not show the headers and footers\n"
1219 " --no-ask-password Do not ask for system passwords\n"
1220 " -H --host=[USER@]HOST Operate on remote host\n"
1221 " -M --machine=CONTAINER Operate on local container\n"
1222 " -q --quiet Suppress informational messages\n"
1223 " -p --profile=PROFILE Pick security profile for portable service\n"
1224 " --copy=copy|auto|symlink Prefer copying or symlinks if possible\n"
1225 " --runtime Attach portable service until next reboot only\n"
1226 " --no-reload Don't reload the system and service manager\n"
1227 " --cat When inspecting include unit and os-release file\n"
1228 " contents\n"
1229 " --enable Immediately enable/disable the portable service\n"
1230 " after attach/detach\n"
1231 " --now Immediately start/stop the portable service after\n"
1232 " attach/before detach\n"
1233 " --no-block Don't block waiting for attach --now to complete\n"
1234 " --extension=PATH Extend the image with an overlay\n"
1235 "\nSee the %s for details.\n",
1236 program_invocation_short_name,
1237 ansi_highlight(),
1238 ansi_normal(),
1239 link);
1240
1241 return 0;
1242 }
1243
1244 static int parse_argv(int argc, char *argv[]) {
1245 int r;
1246
1247 enum {
1248 ARG_VERSION = 0x100,
1249 ARG_NO_PAGER,
1250 ARG_NO_LEGEND,
1251 ARG_NO_ASK_PASSWORD,
1252 ARG_COPY,
1253 ARG_RUNTIME,
1254 ARG_NO_RELOAD,
1255 ARG_CAT,
1256 ARG_ENABLE,
1257 ARG_NOW,
1258 ARG_NO_BLOCK,
1259 ARG_EXTENSION,
1260 };
1261
1262 static const struct option options[] = {
1263 { "help", no_argument, NULL, 'h' },
1264 { "version", no_argument, NULL, ARG_VERSION },
1265 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
1266 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
1267 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
1268 { "host", required_argument, NULL, 'H' },
1269 { "machine", required_argument, NULL, 'M' },
1270 { "quiet", no_argument, NULL, 'q' },
1271 { "profile", required_argument, NULL, 'p' },
1272 { "copy", required_argument, NULL, ARG_COPY },
1273 { "runtime", no_argument, NULL, ARG_RUNTIME },
1274 { "no-reload", no_argument, NULL, ARG_NO_RELOAD },
1275 { "cat", no_argument, NULL, ARG_CAT },
1276 { "enable", no_argument, NULL, ARG_ENABLE },
1277 { "now", no_argument, NULL, ARG_NOW },
1278 { "no-block", no_argument, NULL, ARG_NO_BLOCK },
1279 { "extension", required_argument, NULL, ARG_EXTENSION },
1280 {}
1281 };
1282
1283 assert(argc >= 0);
1284 assert(argv);
1285
1286 for (;;) {
1287 int c;
1288
1289 c = getopt_long(argc, argv, "hH:M:qp:", options, NULL);
1290 if (c < 0)
1291 break;
1292
1293 switch (c) {
1294
1295 case 'h':
1296 return help(0, NULL, NULL);
1297
1298 case ARG_VERSION:
1299 return version();
1300
1301 case ARG_NO_PAGER:
1302 arg_pager_flags |= PAGER_DISABLE;
1303 break;
1304
1305 case ARG_NO_LEGEND:
1306 arg_legend = false;
1307 break;
1308
1309 case ARG_NO_ASK_PASSWORD:
1310 arg_ask_password = false;
1311 break;
1312
1313 case 'H':
1314 arg_transport = BUS_TRANSPORT_REMOTE;
1315 arg_host = optarg;
1316 break;
1317
1318 case 'M':
1319 arg_transport = BUS_TRANSPORT_MACHINE;
1320 arg_host = optarg;
1321 break;
1322
1323 case 'q':
1324 arg_quiet = true;
1325 break;
1326
1327 case 'p':
1328 if (streq(optarg, "help"))
1329 return dump_profiles();
1330
1331 if (!filename_is_valid(optarg))
1332 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1333 "Unit profile name not valid: %s", optarg);
1334
1335 arg_profile = optarg;
1336 break;
1337
1338 case ARG_COPY:
1339 if (streq(optarg, "auto"))
1340 arg_copy_mode = NULL;
1341 else if (STR_IN_SET(optarg, "copy", "symlink"))
1342 arg_copy_mode = optarg;
1343 else if (streq(optarg, "help")) {
1344 puts("auto\n"
1345 "copy\n"
1346 "symlink");
1347 return 0;
1348 } else
1349 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1350 "Failed to parse --copy= argument: %s", optarg);
1351
1352 break;
1353
1354 case ARG_RUNTIME:
1355 arg_runtime = true;
1356 break;
1357
1358 case ARG_NO_RELOAD:
1359 arg_reload = false;
1360 break;
1361
1362 case ARG_CAT:
1363 arg_cat = true;
1364 break;
1365
1366 case ARG_ENABLE:
1367 arg_enable = true;
1368 break;
1369
1370 case ARG_NOW:
1371 arg_now = true;
1372 break;
1373
1374 case ARG_NO_BLOCK:
1375 arg_no_block = true;
1376 break;
1377
1378 case ARG_EXTENSION:
1379 r = strv_extend(&arg_extension_images, optarg);
1380 if (r < 0)
1381 return log_oom();
1382 break;
1383
1384 case '?':
1385 return -EINVAL;
1386
1387 default:
1388 assert_not_reached();
1389 }
1390 }
1391
1392 return 1;
1393 }
1394
1395 static int run(int argc, char *argv[]) {
1396 static const Verb verbs[] = {
1397 { "help", VERB_ANY, VERB_ANY, 0, help },
1398 { "list", VERB_ANY, 1, VERB_DEFAULT, list_images },
1399 { "attach", 2, VERB_ANY, 0, attach_image },
1400 { "detach", 2, VERB_ANY, 0, detach_image },
1401 { "inspect", 2, VERB_ANY, 0, inspect_image },
1402 { "is-attached", 2, 2, 0, is_image_attached },
1403 { "read-only", 2, 3, 0, read_only_image },
1404 { "remove", 2, VERB_ANY, 0, remove_image },
1405 { "set-limit", 3, 3, 0, set_limit },
1406 { "reattach", 2, VERB_ANY, 0, reattach_image },
1407 {}
1408 };
1409
1410 int r;
1411
1412 log_setup();
1413
1414 r = parse_argv(argc, argv);
1415 if (r <= 0)
1416 return r;
1417
1418 return dispatch_verb(argc, argv, verbs, NULL);
1419 }
1420
1421 DEFINE_MAIN_FUNCTION(run);