]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/machine/machine-dbus.c
Merge pull request #30284 from YHNdnzj/fstab-wantedby-defaultdeps
[thirdparty/systemd.git] / src / machine / machine-dbus.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <sys/mount.h>
5 #include <sys/wait.h>
6
7 #include "alloc-util.h"
8 #include "bus-common-errors.h"
9 #include "bus-get-properties.h"
10 #include "bus-internal.h"
11 #include "bus-label.h"
12 #include "bus-locator.h"
13 #include "bus-polkit.h"
14 #include "copy.h"
15 #include "env-file.h"
16 #include "env-util.h"
17 #include "fd-util.h"
18 #include "fileio.h"
19 #include "format-util.h"
20 #include "fs-util.h"
21 #include "in-addr-util.h"
22 #include "iovec-util.h"
23 #include "local-addresses.h"
24 #include "machine-dbus.h"
25 #include "machine.h"
26 #include "missing_capability.h"
27 #include "mkdir.h"
28 #include "mount-util.h"
29 #include "mountpoint-util.h"
30 #include "namespace-util.h"
31 #include "os-util.h"
32 #include "path-util.h"
33 #include "process-util.h"
34 #include "signal-util.h"
35 #include "strv.h"
36 #include "terminal-util.h"
37 #include "tmpfile-util.h"
38 #include "user-util.h"
39
40 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_class, machine_class, MachineClass);
41 static BUS_DEFINE_PROPERTY_GET2(property_get_state, "s", Machine, machine_get_state, machine_state_to_string);
42
43 static int property_get_netif(
44 sd_bus *bus,
45 const char *path,
46 const char *interface,
47 const char *property,
48 sd_bus_message *reply,
49 void *userdata,
50 sd_bus_error *error) {
51
52 Machine *m = ASSERT_PTR(userdata);
53
54 assert(bus);
55 assert(reply);
56
57 assert_cc(sizeof(int) == sizeof(int32_t));
58
59 return sd_bus_message_append_array(reply, 'i', m->netif, m->n_netif * sizeof(int));
60 }
61
62 int bus_machine_method_unregister(sd_bus_message *message, void *userdata, sd_bus_error *error) {
63 Machine *m = ASSERT_PTR(userdata);
64 int r;
65
66 assert(message);
67
68 const char *details[] = {
69 "machine", m->name,
70 "verb", "unregister",
71 NULL
72 };
73
74 r = bus_verify_polkit_async(
75 message,
76 "org.freedesktop.machine1.manage-machines",
77 details,
78 &m->manager->polkit_registry,
79 error);
80 if (r < 0)
81 return r;
82 if (r == 0)
83 return 1; /* Will call us back */
84
85 r = machine_finalize(m);
86 if (r < 0)
87 return r;
88
89 return sd_bus_reply_method_return(message, NULL);
90 }
91
92 int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error) {
93 Machine *m = ASSERT_PTR(userdata);
94 int r;
95
96 assert(message);
97
98 const char *details[] = {
99 "machine", m->name,
100 "verb", "terminate",
101 NULL
102 };
103
104 r = bus_verify_polkit_async(
105 message,
106 "org.freedesktop.machine1.manage-machines",
107 details,
108 &m->manager->polkit_registry,
109 error);
110 if (r < 0)
111 return r;
112 if (r == 0)
113 return 1; /* Will call us back */
114
115 r = machine_stop(m);
116 if (r < 0)
117 return r;
118
119 return sd_bus_reply_method_return(message, NULL);
120 }
121
122 int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) {
123 Machine *m = ASSERT_PTR(userdata);
124 const char *swho;
125 int32_t signo;
126 KillWho who;
127 int r;
128
129 assert(message);
130
131 r = sd_bus_message_read(message, "si", &swho, &signo);
132 if (r < 0)
133 return r;
134
135 if (isempty(swho))
136 who = KILL_ALL;
137 else {
138 who = kill_who_from_string(swho);
139 if (who < 0)
140 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid kill parameter '%s'", swho);
141 }
142
143 if (!SIGNAL_VALID(signo))
144 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid signal %i", signo);
145
146 const char *details[] = {
147 "machine", m->name,
148 "verb", "kill",
149 NULL
150 };
151
152 r = bus_verify_polkit_async(
153 message,
154 "org.freedesktop.machine1.manage-machines",
155 details,
156 &m->manager->polkit_registry,
157 error);
158 if (r < 0)
159 return r;
160 if (r == 0)
161 return 1; /* Will call us back */
162
163 r = machine_kill(m, who, signo);
164 if (r < 0)
165 return r;
166
167 return sd_bus_reply_method_return(message, NULL);
168 }
169
170 int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) {
171 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
172 Machine *m = ASSERT_PTR(userdata);
173 int r;
174
175 assert(message);
176
177 r = sd_bus_message_new_method_return(message, &reply);
178 if (r < 0)
179 return r;
180
181 r = sd_bus_message_open_container(reply, 'a', "(iay)");
182 if (r < 0)
183 return r;
184
185 switch (m->class) {
186
187 case MACHINE_HOST: {
188 _cleanup_free_ struct local_address *addresses = NULL;
189 int n;
190
191 n = local_addresses(NULL, 0, AF_UNSPEC, &addresses);
192 if (n < 0)
193 return n;
194
195 for (int i = 0; i < n; i++) {
196 r = sd_bus_message_open_container(reply, 'r', "iay");
197 if (r < 0)
198 return r;
199
200 r = sd_bus_message_append(reply, "i", addresses[i].family);
201 if (r < 0)
202 return r;
203
204 r = sd_bus_message_append_array(reply, 'y', &addresses[i].address, FAMILY_ADDRESS_SIZE(addresses[i].family));
205 if (r < 0)
206 return r;
207
208 r = sd_bus_message_close_container(reply);
209 if (r < 0)
210 return r;
211 }
212
213 break;
214 }
215
216 case MACHINE_CONTAINER: {
217 _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
218 _cleanup_free_ char *us = NULL, *them = NULL;
219 _cleanup_close_ int netns_fd = -EBADF;
220 const char *p;
221 pid_t child;
222
223 r = readlink_malloc("/proc/self/ns/net", &us);
224 if (r < 0)
225 return r;
226
227 p = procfs_file_alloca(m->leader.pid, "ns/net");
228 r = readlink_malloc(p, &them);
229 if (r < 0)
230 return r;
231
232 if (streq(us, them))
233 return sd_bus_error_setf(error, BUS_ERROR_NO_PRIVATE_NETWORKING, "Machine %s does not use private networking", m->name);
234
235 r = namespace_open(m->leader.pid, NULL, NULL, &netns_fd, NULL, NULL);
236 if (r < 0)
237 return r;
238
239 if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0)
240 return -errno;
241
242 r = namespace_fork("(sd-addrns)", "(sd-addr)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL,
243 -1, -1, netns_fd, -1, -1, &child);
244 if (r < 0)
245 return sd_bus_error_set_errnof(error, r, "Failed to fork(): %m");
246 if (r == 0) {
247 _cleanup_free_ struct local_address *addresses = NULL;
248 struct local_address *a;
249 int i, n;
250
251 pair[0] = safe_close(pair[0]);
252
253 n = local_addresses(NULL, 0, AF_UNSPEC, &addresses);
254 if (n < 0)
255 _exit(EXIT_FAILURE);
256
257 for (a = addresses, i = 0; i < n; a++, i++) {
258 struct iovec iov[2] = {
259 { .iov_base = &a->family, .iov_len = sizeof(a->family) },
260 { .iov_base = &a->address, .iov_len = FAMILY_ADDRESS_SIZE(a->family) },
261 };
262
263 r = writev(pair[1], iov, 2);
264 if (r < 0)
265 _exit(EXIT_FAILURE);
266 }
267
268 pair[1] = safe_close(pair[1]);
269
270 _exit(EXIT_SUCCESS);
271 }
272
273 pair[1] = safe_close(pair[1]);
274
275 for (;;) {
276 int family;
277 ssize_t n;
278 union in_addr_union in_addr;
279 struct iovec iov[2];
280 struct msghdr mh = {
281 .msg_iov = iov,
282 .msg_iovlen = 2,
283 };
284
285 iov[0] = IOVEC_MAKE(&family, sizeof(family));
286 iov[1] = IOVEC_MAKE(&in_addr, sizeof(in_addr));
287
288 n = recvmsg(pair[0], &mh, 0);
289 if (n < 0)
290 return -errno;
291 if ((size_t) n < sizeof(family))
292 break;
293
294 r = sd_bus_message_open_container(reply, 'r', "iay");
295 if (r < 0)
296 return r;
297
298 r = sd_bus_message_append(reply, "i", family);
299 if (r < 0)
300 return r;
301
302 switch (family) {
303
304 case AF_INET:
305 if (n != sizeof(struct in_addr) + sizeof(family))
306 return -EIO;
307
308 r = sd_bus_message_append_array(reply, 'y', &in_addr.in, sizeof(in_addr.in));
309 break;
310
311 case AF_INET6:
312 if (n != sizeof(struct in6_addr) + sizeof(family))
313 return -EIO;
314
315 r = sd_bus_message_append_array(reply, 'y', &in_addr.in6, sizeof(in_addr.in6));
316 break;
317 }
318 if (r < 0)
319 return r;
320
321 r = sd_bus_message_close_container(reply);
322 if (r < 0)
323 return r;
324 }
325
326 r = wait_for_terminate_and_check("(sd-addrns)", child, 0);
327 if (r < 0)
328 return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m");
329 if (r != EXIT_SUCCESS)
330 return sd_bus_error_set(error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
331 break;
332 }
333
334 default:
335 return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting IP address data is only supported on container machines.");
336 }
337
338 r = sd_bus_message_close_container(reply);
339 if (r < 0)
340 return r;
341
342 return sd_bus_send(NULL, reply, NULL);
343 }
344
345 #define EXIT_NOT_FOUND 2
346
347 int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
348 _cleanup_strv_free_ char **l = NULL;
349 Machine *m = ASSERT_PTR(userdata);
350 int r;
351
352 assert(message);
353
354 switch (m->class) {
355
356 case MACHINE_HOST:
357 r = load_os_release_pairs(NULL, &l);
358 if (r < 0)
359 return r;
360
361 break;
362
363 case MACHINE_CONTAINER: {
364 _cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF, pidns_fd = -EBADF;
365 _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
366 _cleanup_fclose_ FILE *f = NULL;
367 pid_t child;
368
369 r = namespace_open(m->leader.pid, &pidns_fd, &mntns_fd, NULL, NULL, &root_fd);
370 if (r < 0)
371 return r;
372
373 if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0)
374 return -errno;
375
376 r = namespace_fork("(sd-osrelns)", "(sd-osrel)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL,
377 pidns_fd, mntns_fd, -1, -1, root_fd,
378 &child);
379 if (r < 0)
380 return sd_bus_error_set_errnof(error, r, "Failed to fork(): %m");
381 if (r == 0) {
382 int fd = -EBADF;
383
384 pair[0] = safe_close(pair[0]);
385
386 r = open_os_release(NULL, NULL, &fd);
387 if (r == -ENOENT)
388 _exit(EXIT_NOT_FOUND);
389 if (r < 0)
390 _exit(EXIT_FAILURE);
391
392 r = copy_bytes(fd, pair[1], UINT64_MAX, 0);
393 if (r < 0)
394 _exit(EXIT_FAILURE);
395
396 _exit(EXIT_SUCCESS);
397 }
398
399 pair[1] = safe_close(pair[1]);
400
401 f = take_fdopen(&pair[0], "r");
402 if (!f)
403 return -errno;
404
405 r = load_env_file_pairs(f, "/etc/os-release", &l);
406 if (r < 0)
407 return r;
408
409 r = wait_for_terminate_and_check("(sd-osrelns)", child, 0);
410 if (r < 0)
411 return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m");
412 if (r == EXIT_NOT_FOUND)
413 return sd_bus_error_set(error, SD_BUS_ERROR_FAILED, "Machine does not contain OS release information");
414 if (r != EXIT_SUCCESS)
415 return sd_bus_error_set(error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
416
417 break;
418 }
419
420 default:
421 return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting OS release data is only supported on container machines.");
422 }
423
424 return bus_reply_pair_array(message, l);
425 }
426
427 int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) {
428 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
429 _cleanup_free_ char *pty_name = NULL;
430 _cleanup_close_ int master = -EBADF;
431 Machine *m = ASSERT_PTR(userdata);
432 int r;
433
434 assert(message);
435
436 const char *details[] = {
437 "machine", m->name,
438 NULL
439 };
440
441 r = bus_verify_polkit_async(
442 message,
443 m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-open-pty" : "org.freedesktop.machine1.open-pty",
444 details,
445 &m->manager->polkit_registry,
446 error);
447 if (r < 0)
448 return r;
449 if (r == 0)
450 return 1; /* Will call us back */
451
452 master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC, &pty_name);
453 if (master < 0)
454 return master;
455
456 r = sd_bus_message_new_method_return(message, &reply);
457 if (r < 0)
458 return r;
459
460 r = sd_bus_message_append(reply, "hs", master, pty_name);
461 if (r < 0)
462 return r;
463
464 return sd_bus_send(NULL, reply, NULL);
465 }
466
467 static int container_bus_new(Machine *m, sd_bus_error *error, sd_bus **ret) {
468 int r;
469
470 assert(m);
471 assert(ret);
472
473 switch (m->class) {
474
475 case MACHINE_HOST:
476 *ret = NULL;
477 break;
478
479 case MACHINE_CONTAINER: {
480 _cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL;
481 char *address;
482
483 r = sd_bus_new(&bus);
484 if (r < 0)
485 return r;
486
487 if (asprintf(&address, "x-machine-unix:pid=%" PID_PRI, m->leader.pid) < 0)
488 return -ENOMEM;
489
490 bus->address = address;
491 bus->bus_client = true;
492 bus->trusted = false;
493 bus->runtime_scope = RUNTIME_SCOPE_SYSTEM;
494
495 r = sd_bus_start(bus);
496 if (r == -ENOENT)
497 return sd_bus_error_set_errnof(error, r, "There is no system bus in container %s.", m->name);
498 if (r < 0)
499 return r;
500
501 *ret = TAKE_PTR(bus);
502 break;
503 }
504
505 default:
506 return -EOPNOTSUPP;
507 }
508
509 return 0;
510 }
511
512 int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error) {
513 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
514 _cleanup_free_ char *pty_name = NULL;
515 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL;
516 _cleanup_close_ int master = -EBADF;
517 sd_bus *container_bus = NULL;
518 Machine *m = ASSERT_PTR(userdata);
519 const char *p, *getty;
520 int r;
521
522 assert(message);
523
524 const char *details[] = {
525 "machine", m->name,
526 "verb", "login",
527 NULL
528 };
529
530 r = bus_verify_polkit_async(
531 message,
532 m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-login" : "org.freedesktop.machine1.login",
533 details,
534 &m->manager->polkit_registry,
535 error);
536 if (r < 0)
537 return r;
538 if (r == 0)
539 return 1; /* Will call us back */
540
541 master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC, &pty_name);
542 if (master < 0)
543 return master;
544
545 p = path_startswith(pty_name, "/dev/pts/");
546 assert(p);
547
548 r = container_bus_new(m, error, &allocated_bus);
549 if (r < 0)
550 return r;
551
552 container_bus = allocated_bus ?: m->manager->bus;
553
554 getty = strjoina("container-getty@", p, ".service");
555
556 r = bus_call_method(container_bus, bus_systemd_mgr, "StartUnit", error, NULL, "ss", getty, "replace");
557 if (r < 0)
558 return r;
559
560 r = sd_bus_message_new_method_return(message, &reply);
561 if (r < 0)
562 return r;
563
564 r = sd_bus_message_append(reply, "hs", master, pty_name);
565 if (r < 0)
566 return r;
567
568 return sd_bus_send(NULL, reply, NULL);
569 }
570
571 int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) {
572 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *tm = NULL;
573 _cleanup_free_ char *pty_name = NULL;
574 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL;
575 sd_bus *container_bus = NULL;
576 _cleanup_close_ int master = -EBADF, slave = -EBADF;
577 _cleanup_strv_free_ char **env = NULL, **args_wire = NULL, **args = NULL;
578 _cleanup_free_ char *command_line = NULL;
579 Machine *m = ASSERT_PTR(userdata);
580 const char *p, *unit, *user, *path, *description, *utmp_id;
581 int r;
582
583 assert(message);
584
585 r = sd_bus_message_read(message, "ss", &user, &path);
586 if (r < 0)
587 return r;
588 user = isempty(user) ? "root" : user;
589 r = sd_bus_message_read_strv(message, &args_wire);
590 if (r < 0)
591 return r;
592 if (isempty(path)) {
593 path = "/bin/sh";
594
595 args = new0(char*, 3 + 1);
596 if (!args)
597 return -ENOMEM;
598 args[0] = strdup("sh");
599 if (!args[0])
600 return -ENOMEM;
601 args[1] = strdup("-c");
602 if (!args[1])
603 return -ENOMEM;
604 r = asprintf(&args[2],
605 "shell=$(getent passwd %s 2>/dev/null | { IFS=: read _ _ _ _ _ _ x; echo \"$x\"; })\n"\
606 "exec \"${shell:-/bin/sh}\" -l", /* -l is means --login */
607 user);
608 if (r < 0) {
609 args[2] = NULL;
610 return -ENOMEM;
611 }
612 } else {
613 if (!path_is_absolute(path))
614 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified path '%s' is not absolute", path);
615 args = TAKE_PTR(args_wire);
616 if (strv_isempty(args)) {
617 args = strv_free(args);
618
619 args = strv_new(path);
620 if (!args)
621 return -ENOMEM;
622 }
623 }
624
625 r = sd_bus_message_read_strv(message, &env);
626 if (r < 0)
627 return r;
628 if (!strv_env_is_valid(env))
629 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments");
630
631 command_line = strv_join(args, " ");
632 if (!command_line)
633 return -ENOMEM;
634 const char *details[] = {
635 "machine", m->name,
636 "user", user,
637 "program", path,
638 "command_line", command_line,
639 NULL
640 };
641
642 r = bus_verify_polkit_async(
643 message,
644 m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-shell" : "org.freedesktop.machine1.shell",
645 details,
646 &m->manager->polkit_registry,
647 error);
648 if (r < 0)
649 return r;
650 if (r == 0)
651 return 1; /* Will call us back */
652
653 master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC, &pty_name);
654 if (master < 0)
655 return master;
656
657 p = path_startswith(pty_name, "/dev/pts/");
658 assert(p);
659
660 slave = machine_open_terminal(m, pty_name, O_RDWR|O_NOCTTY|O_CLOEXEC);
661 if (slave < 0)
662 return slave;
663
664 utmp_id = path_startswith(pty_name, "/dev/");
665 assert(utmp_id);
666
667 r = container_bus_new(m, error, &allocated_bus);
668 if (r < 0)
669 return r;
670
671 container_bus = allocated_bus ?: m->manager->bus;
672
673 r = bus_message_new_method_call(container_bus, &tm, bus_systemd_mgr, "StartTransientUnit");
674 if (r < 0)
675 return r;
676
677 /* Name and mode */
678 unit = strjoina("container-shell@", p, ".service");
679 r = sd_bus_message_append(tm, "ss", unit, "fail");
680 if (r < 0)
681 return r;
682
683 /* Properties */
684 r = sd_bus_message_open_container(tm, 'a', "(sv)");
685 if (r < 0)
686 return r;
687
688 description = strjoina("Shell for User ", user);
689 r = sd_bus_message_append(tm,
690 "(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)",
691 "Description", "s", description,
692 "StandardInputFileDescriptor", "h", slave,
693 "StandardOutputFileDescriptor", "h", slave,
694 "StandardErrorFileDescriptor", "h", slave,
695 "SendSIGHUP", "b", true,
696 "IgnoreSIGPIPE", "b", false,
697 "KillMode", "s", "mixed",
698 "TTYPath", "s", pty_name,
699 "TTYReset", "b", true,
700 "UtmpIdentifier", "s", utmp_id,
701 "UtmpMode", "s", "user",
702 "PAMName", "s", "login",
703 "WorkingDirectory", "s", "-~");
704 if (r < 0)
705 return r;
706
707 r = sd_bus_message_append(tm, "(sv)", "User", "s", user);
708 if (r < 0)
709 return r;
710
711 if (!strv_isempty(env)) {
712 r = sd_bus_message_open_container(tm, 'r', "sv");
713 if (r < 0)
714 return r;
715
716 r = sd_bus_message_append(tm, "s", "Environment");
717 if (r < 0)
718 return r;
719
720 r = sd_bus_message_open_container(tm, 'v', "as");
721 if (r < 0)
722 return r;
723
724 r = sd_bus_message_append_strv(tm, env);
725 if (r < 0)
726 return r;
727
728 r = sd_bus_message_close_container(tm);
729 if (r < 0)
730 return r;
731
732 r = sd_bus_message_close_container(tm);
733 if (r < 0)
734 return r;
735 }
736
737 /* Exec container */
738 r = sd_bus_message_open_container(tm, 'r', "sv");
739 if (r < 0)
740 return r;
741
742 r = sd_bus_message_append(tm, "s", "ExecStart");
743 if (r < 0)
744 return r;
745
746 r = sd_bus_message_open_container(tm, 'v', "a(sasb)");
747 if (r < 0)
748 return r;
749
750 r = sd_bus_message_open_container(tm, 'a', "(sasb)");
751 if (r < 0)
752 return r;
753
754 r = sd_bus_message_open_container(tm, 'r', "sasb");
755 if (r < 0)
756 return r;
757
758 r = sd_bus_message_append(tm, "s", path);
759 if (r < 0)
760 return r;
761
762 r = sd_bus_message_append_strv(tm, args);
763 if (r < 0)
764 return r;
765
766 r = sd_bus_message_append(tm, "b", true);
767 if (r < 0)
768 return r;
769
770 r = sd_bus_message_close_container(tm);
771 if (r < 0)
772 return r;
773
774 r = sd_bus_message_close_container(tm);
775 if (r < 0)
776 return r;
777
778 r = sd_bus_message_close_container(tm);
779 if (r < 0)
780 return r;
781
782 r = sd_bus_message_close_container(tm);
783 if (r < 0)
784 return r;
785
786 r = sd_bus_message_close_container(tm);
787 if (r < 0)
788 return r;
789
790 /* Auxiliary units */
791 r = sd_bus_message_append(tm, "a(sa(sv))", 0);
792 if (r < 0)
793 return r;
794
795 r = sd_bus_call(container_bus, tm, 0, error, NULL);
796 if (r < 0)
797 return r;
798
799 slave = safe_close(slave);
800
801 r = sd_bus_message_new_method_return(message, &reply);
802 if (r < 0)
803 return r;
804
805 r = sd_bus_message_append(reply, "hs", master, pty_name);
806 if (r < 0)
807 return r;
808
809 return sd_bus_send(NULL, reply, NULL);
810 }
811
812 int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) {
813 int read_only, make_file_or_directory;
814 const char *dest, *src, *propagate_directory;
815 Machine *m = ASSERT_PTR(userdata);
816 uid_t uid;
817 int r;
818
819 assert(message);
820
821 if (m->class != MACHINE_CONTAINER)
822 return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Bind mounting is only supported on container machines.");
823
824 r = sd_bus_message_read(message, "ssbb", &src, &dest, &read_only, &make_file_or_directory);
825 if (r < 0)
826 return r;
827
828 if (!path_is_absolute(src) || !path_is_normalized(src))
829 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and normalized.");
830
831 if (isempty(dest))
832 dest = src;
833 else if (!path_is_absolute(dest) || !path_is_normalized(dest))
834 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and normalized.");
835
836 const char *details[] = {
837 "machine", m->name,
838 "verb", "bind",
839 "src", src,
840 "dest", dest,
841 NULL
842 };
843
844 r = bus_verify_polkit_async(
845 message,
846 "org.freedesktop.machine1.manage-machines",
847 details,
848 &m->manager->polkit_registry,
849 error);
850 if (r < 0)
851 return r;
852 if (r == 0)
853 return 1; /* Will call us back */
854
855 r = machine_get_uid_shift(m, &uid);
856 if (r < 0)
857 return r;
858 if (uid != 0)
859 return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Can't bind mount on container with user namespacing applied.");
860
861 propagate_directory = strjoina("/run/systemd/nspawn/propagate/", m->name);
862 r = bind_mount_in_namespace(
863 &m->leader,
864 propagate_directory,
865 "/run/host/incoming/",
866 src, dest,
867 read_only,
868 make_file_or_directory);
869 if (r < 0)
870 return sd_bus_error_set_errnof(error, r, "Failed to mount %s on %s in machine's namespace: %m", src, dest);
871
872 return sd_bus_reply_method_return(message, NULL);
873 }
874
875 int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error) {
876 _cleanup_free_ char *host_basename = NULL, *container_basename = NULL;
877 const char *src, *dest, *host_path, *container_path;
878 _cleanup_close_pair_ int errno_pipe_fd[2] = EBADF_PAIR;
879 CopyFlags copy_flags = COPY_REFLINK|COPY_MERGE|COPY_HARDLINKS;
880 _cleanup_close_ int hostfd = -EBADF;
881 Machine *m = ASSERT_PTR(userdata);
882 bool copy_from;
883 pid_t child;
884 uid_t uid_shift;
885 int r;
886
887 assert(message);
888
889 if (m->manager->n_operations >= OPERATIONS_MAX)
890 return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing copies.");
891
892 if (m->class != MACHINE_CONTAINER)
893 return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Copying files is only supported on container machines.");
894
895 r = sd_bus_message_read(message, "ss", &src, &dest);
896 if (r < 0)
897 return r;
898
899 if (endswith(sd_bus_message_get_member(message), "WithFlags")) {
900 uint64_t raw_flags;
901
902 r = sd_bus_message_read(message, "t", &raw_flags);
903 if (r < 0)
904 return r;
905
906 if ((raw_flags & ~_MACHINE_COPY_FLAGS_MASK_PUBLIC) != 0)
907 return -EINVAL;
908
909 if (raw_flags & MACHINE_COPY_REPLACE)
910 copy_flags |= COPY_REPLACE;
911 }
912
913 if (!path_is_absolute(src))
914 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute.");
915
916 if (isempty(dest))
917 dest = src;
918 else if (!path_is_absolute(dest))
919 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute.");
920
921 const char *details[] = {
922 "machine", m->name,
923 "verb", "copy",
924 "src", src,
925 "dest", dest,
926 NULL
927 };
928
929 r = bus_verify_polkit_async(
930 message,
931 "org.freedesktop.machine1.manage-machines",
932 details,
933 &m->manager->polkit_registry,
934 error);
935 if (r < 0)
936 return r;
937 if (r == 0)
938 return 1; /* Will call us back */
939
940 r = machine_get_uid_shift(m, &uid_shift);
941 if (r < 0)
942 return r;
943
944 copy_from = strstr(sd_bus_message_get_member(message), "CopyFrom");
945
946 if (copy_from) {
947 container_path = src;
948 host_path = dest;
949 } else {
950 host_path = src;
951 container_path = dest;
952 }
953
954 r = path_extract_filename(host_path, &host_basename);
955 if (r < 0)
956 return sd_bus_error_set_errnof(error, r, "Failed to extract file name of '%s' path: %m", host_path);
957
958 r = path_extract_filename(container_path, &container_basename);
959 if (r < 0)
960 return sd_bus_error_set_errnof(error, r, "Failed to extract file name of '%s' path: %m", container_path);
961
962 hostfd = open_parent(host_path, O_CLOEXEC, 0);
963 if (hostfd < 0)
964 return sd_bus_error_set_errnof(error, hostfd, "Failed to open host directory %s: %m", host_path);
965
966 if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
967 return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m");
968
969 r = safe_fork("(sd-copy)", FORK_RESET_SIGNALS, &child);
970 if (r < 0)
971 return sd_bus_error_set_errnof(error, r, "Failed to fork(): %m");
972 if (r == 0) {
973 int containerfd;
974 const char *q;
975 int mntfd;
976
977 errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
978
979 q = procfs_file_alloca(m->leader.pid, "ns/mnt");
980 mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC);
981 if (mntfd < 0) {
982 r = log_error_errno(errno, "Failed to open mount namespace of leader: %m");
983 goto child_fail;
984 }
985
986 if (setns(mntfd, CLONE_NEWNS) < 0) {
987 r = log_error_errno(errno, "Failed to join namespace of leader: %m");
988 goto child_fail;
989 }
990
991 containerfd = open_parent(container_path, O_CLOEXEC, 0);
992 if (containerfd < 0) {
993 r = log_error_errno(containerfd, "Failed to open destination directory: %m");
994 goto child_fail;
995 }
996
997 /* Run the actual copy operation. Note that when a UID shift is set we'll either clamp the UID/GID to
998 * 0 or to the actual UID shift depending on the direction we copy. If no UID shift is set we'll copy
999 * the UID/GIDs as they are. */
1000 if (copy_from)
1001 r = copy_tree_at(containerfd, container_basename, hostfd, host_basename, uid_shift == 0 ? UID_INVALID : 0, uid_shift == 0 ? GID_INVALID : 0, copy_flags, NULL, NULL);
1002 else
1003 r = copy_tree_at(hostfd, host_basename, containerfd, container_basename, uid_shift == 0 ? UID_INVALID : uid_shift, uid_shift == 0 ? GID_INVALID : uid_shift, copy_flags, NULL, NULL);
1004
1005 hostfd = safe_close(hostfd);
1006 containerfd = safe_close(containerfd);
1007
1008 if (r < 0) {
1009 r = log_error_errno(r, "Failed to copy tree: %m");
1010 goto child_fail;
1011 }
1012
1013 _exit(EXIT_SUCCESS);
1014
1015 child_fail:
1016 (void) write(errno_pipe_fd[1], &r, sizeof(r));
1017 _exit(EXIT_FAILURE);
1018 }
1019
1020 errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
1021
1022 /* Copying might take a while, hence install a watch on the child, and return */
1023
1024 r = operation_new(m->manager, m, child, message, errno_pipe_fd[0], NULL);
1025 if (r < 0) {
1026 (void) sigkill_wait(child);
1027 return r;
1028 }
1029 errno_pipe_fd[0] = -EBADF;
1030
1031 return 1;
1032 }
1033
1034 int bus_machine_method_open_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error) {
1035 _cleanup_close_ int fd = -EBADF;
1036 Machine *m = ASSERT_PTR(userdata);
1037 int r;
1038
1039 assert(message);
1040
1041 const char *details[] = {
1042 "machine", m->name,
1043 "verb", "open_root_directory",
1044 NULL
1045 };
1046
1047 r = bus_verify_polkit_async(
1048 message,
1049 "org.freedesktop.machine1.manage-machines",
1050 details,
1051 &m->manager->polkit_registry,
1052 error);
1053 if (r < 0)
1054 return r;
1055 if (r == 0)
1056 return 1; /* Will call us back */
1057
1058 switch (m->class) {
1059
1060 case MACHINE_HOST:
1061 fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
1062 if (fd < 0)
1063 return -errno;
1064
1065 break;
1066
1067 case MACHINE_CONTAINER: {
1068 _cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF;
1069 _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
1070 pid_t child;
1071
1072 r = namespace_open(m->leader.pid, NULL, &mntns_fd, NULL, NULL, &root_fd);
1073 if (r < 0)
1074 return r;
1075
1076 if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
1077 return -errno;
1078
1079 r = namespace_fork("(sd-openrootns)", "(sd-openroot)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL,
1080 -1, mntns_fd, -1, -1, root_fd, &child);
1081 if (r < 0)
1082 return sd_bus_error_set_errnof(error, r, "Failed to fork(): %m");
1083 if (r == 0) {
1084 _cleanup_close_ int dfd = -EBADF;
1085
1086 pair[0] = safe_close(pair[0]);
1087
1088 dfd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
1089 if (dfd < 0)
1090 _exit(EXIT_FAILURE);
1091
1092 r = send_one_fd(pair[1], dfd, 0);
1093 dfd = safe_close(dfd);
1094 if (r < 0)
1095 _exit(EXIT_FAILURE);
1096
1097 _exit(EXIT_SUCCESS);
1098 }
1099
1100 pair[1] = safe_close(pair[1]);
1101
1102 r = wait_for_terminate_and_check("(sd-openrootns)", child, 0);
1103 if (r < 0)
1104 return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m");
1105 if (r != EXIT_SUCCESS)
1106 return sd_bus_error_set(error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
1107
1108 fd = receive_one_fd(pair[0], MSG_DONTWAIT);
1109 if (fd < 0)
1110 return fd;
1111
1112 break;
1113 }
1114
1115 default:
1116 return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Opening the root directory is only supported on container machines.");
1117 }
1118
1119 return sd_bus_reply_method_return(message, "h", fd);
1120 }
1121
1122 int bus_machine_method_get_uid_shift(sd_bus_message *message, void *userdata, sd_bus_error *error) {
1123 Machine *m = ASSERT_PTR(userdata);
1124 uid_t shift = 0;
1125 int r;
1126
1127 assert(message);
1128
1129 /* You wonder why this is a method and not a property? Well, properties are not supposed to return errors, but
1130 * we kinda have to for this. */
1131
1132 if (m->class == MACHINE_HOST)
1133 return sd_bus_reply_method_return(message, "u", UINT32_C(0));
1134
1135 if (m->class != MACHINE_CONTAINER)
1136 return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "UID/GID shift may only be determined for container machines.");
1137
1138 r = machine_get_uid_shift(m, &shift);
1139 if (r == -ENXIO)
1140 return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Machine %s uses a complex UID/GID mapping, cannot determine shift", m->name);
1141 if (r < 0)
1142 return r;
1143
1144 return sd_bus_reply_method_return(message, "u", (uint32_t) shift);
1145 }
1146
1147 static int machine_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
1148 Manager *m = ASSERT_PTR(userdata);
1149 Machine *machine;
1150 int r;
1151
1152 assert(bus);
1153 assert(path);
1154 assert(interface);
1155 assert(found);
1156
1157 if (streq(path, "/org/freedesktop/machine1/machine/self")) {
1158 _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
1159 sd_bus_message *message;
1160 pid_t pid;
1161
1162 message = sd_bus_get_current_message(bus);
1163 if (!message)
1164 return 0;
1165
1166 r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds);
1167 if (r < 0)
1168 return r;
1169
1170 r = sd_bus_creds_get_pid(creds, &pid);
1171 if (r < 0)
1172 return r;
1173
1174 r = manager_get_machine_by_pid(m, pid, &machine);
1175 if (r <= 0)
1176 return 0;
1177 } else {
1178 _cleanup_free_ char *e = NULL;
1179 const char *p;
1180
1181 p = startswith(path, "/org/freedesktop/machine1/machine/");
1182 if (!p)
1183 return 0;
1184
1185 e = bus_label_unescape(p);
1186 if (!e)
1187 return -ENOMEM;
1188
1189 machine = hashmap_get(m->machines, e);
1190 if (!machine)
1191 return 0;
1192 }
1193
1194 *found = machine;
1195 return 1;
1196 }
1197
1198 char *machine_bus_path(Machine *m) {
1199 _cleanup_free_ char *e = NULL;
1200
1201 assert(m);
1202
1203 e = bus_label_escape(m->name);
1204 if (!e)
1205 return NULL;
1206
1207 return strjoin("/org/freedesktop/machine1/machine/", e);
1208 }
1209
1210 static int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
1211 _cleanup_strv_free_ char **l = NULL;
1212 Machine *machine = NULL;
1213 Manager *m = userdata;
1214 int r;
1215
1216 assert(bus);
1217 assert(path);
1218 assert(nodes);
1219
1220 HASHMAP_FOREACH(machine, m->machines) {
1221 char *p;
1222
1223 p = machine_bus_path(machine);
1224 if (!p)
1225 return -ENOMEM;
1226
1227 r = strv_consume(&l, p);
1228 if (r < 0)
1229 return r;
1230 }
1231
1232 *nodes = TAKE_PTR(l);
1233
1234 return 1;
1235 }
1236
1237 static const sd_bus_vtable machine_vtable[] = {
1238 SD_BUS_VTABLE_START(0),
1239 SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Machine, name), SD_BUS_VTABLE_PROPERTY_CONST),
1240 SD_BUS_PROPERTY("Id", "ay", bus_property_get_id128, offsetof(Machine, id), SD_BUS_VTABLE_PROPERTY_CONST),
1241 BUS_PROPERTY_DUAL_TIMESTAMP("Timestamp", offsetof(Machine, timestamp), SD_BUS_VTABLE_PROPERTY_CONST),
1242 SD_BUS_PROPERTY("Service", "s", NULL, offsetof(Machine, service), SD_BUS_VTABLE_PROPERTY_CONST),
1243 SD_BUS_PROPERTY("Unit", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST),
1244 SD_BUS_PROPERTY("Scope", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
1245 SD_BUS_PROPERTY("Leader", "u", NULL, offsetof(Machine, leader.pid), SD_BUS_VTABLE_PROPERTY_CONST),
1246 SD_BUS_PROPERTY("Class", "s", property_get_class, offsetof(Machine, class), SD_BUS_VTABLE_PROPERTY_CONST),
1247 SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(Machine, root_directory), SD_BUS_VTABLE_PROPERTY_CONST),
1248 SD_BUS_PROPERTY("NetworkInterfaces", "ai", property_get_netif, 0, SD_BUS_VTABLE_PROPERTY_CONST),
1249 SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0),
1250
1251 SD_BUS_METHOD("Terminate",
1252 NULL,
1253 NULL,
1254 bus_machine_method_terminate,
1255 SD_BUS_VTABLE_UNPRIVILEGED),
1256 SD_BUS_METHOD_WITH_ARGS("Kill",
1257 SD_BUS_ARGS("s", who, "i", signal),
1258 SD_BUS_NO_RESULT,
1259 bus_machine_method_kill,
1260 SD_BUS_VTABLE_UNPRIVILEGED),
1261 SD_BUS_METHOD_WITH_ARGS("GetAddresses",
1262 SD_BUS_NO_ARGS,
1263 SD_BUS_RESULT("a(iay)", addresses),
1264 bus_machine_method_get_addresses,
1265 SD_BUS_VTABLE_UNPRIVILEGED),
1266 SD_BUS_METHOD_WITH_ARGS("GetOSRelease",
1267 SD_BUS_NO_ARGS,
1268 SD_BUS_RESULT("a{ss}", fields),
1269 bus_machine_method_get_os_release,
1270 SD_BUS_VTABLE_UNPRIVILEGED),
1271 SD_BUS_METHOD_WITH_ARGS("GetUIDShift",
1272 SD_BUS_NO_ARGS,
1273 SD_BUS_RESULT("u", shift),
1274 bus_machine_method_get_uid_shift,
1275 SD_BUS_VTABLE_UNPRIVILEGED),
1276 SD_BUS_METHOD_WITH_ARGS("OpenPTY",
1277 SD_BUS_NO_ARGS,
1278 SD_BUS_RESULT("h", pty, "s", pty_path),
1279 bus_machine_method_open_pty,
1280 SD_BUS_VTABLE_UNPRIVILEGED),
1281 SD_BUS_METHOD_WITH_ARGS("OpenLogin",
1282 SD_BUS_NO_ARGS,
1283 SD_BUS_RESULT("h", pty, "s", pty_path),
1284 bus_machine_method_open_login,
1285 SD_BUS_VTABLE_UNPRIVILEGED),
1286 SD_BUS_METHOD_WITH_ARGS("OpenShell",
1287 SD_BUS_ARGS("s", user, "s", path, "as", args, "as", environment),
1288 SD_BUS_RESULT("h", pty, "s", pty_path),
1289 bus_machine_method_open_shell,
1290 SD_BUS_VTABLE_UNPRIVILEGED),
1291 SD_BUS_METHOD_WITH_ARGS("BindMount",
1292 SD_BUS_ARGS("s", source, "s", destination, "b", read_only, "b", mkdir),
1293 SD_BUS_NO_RESULT,
1294 bus_machine_method_bind_mount,
1295 SD_BUS_VTABLE_UNPRIVILEGED),
1296 SD_BUS_METHOD_WITH_ARGS("CopyFrom",
1297 SD_BUS_ARGS("s", source, "s", destination),
1298 SD_BUS_NO_RESULT,
1299 bus_machine_method_copy,
1300 SD_BUS_VTABLE_UNPRIVILEGED),
1301 SD_BUS_METHOD_WITH_ARGS("CopyTo",
1302 SD_BUS_ARGS("s", source, "s", destination),
1303 SD_BUS_NO_RESULT,
1304 bus_machine_method_copy,
1305 SD_BUS_VTABLE_UNPRIVILEGED),
1306 SD_BUS_METHOD_WITH_ARGS("CopyFromWithFlags",
1307 SD_BUS_ARGS("s", source, "s", destination, "t", flags),
1308 SD_BUS_NO_RESULT,
1309 bus_machine_method_copy,
1310 SD_BUS_VTABLE_UNPRIVILEGED),
1311 SD_BUS_METHOD_WITH_ARGS("CopyToWithFlags",
1312 SD_BUS_ARGS("s", source, "s", destination, "t", flags),
1313 SD_BUS_NO_RESULT,
1314 bus_machine_method_copy,
1315 SD_BUS_VTABLE_UNPRIVILEGED),
1316 SD_BUS_METHOD_WITH_ARGS("OpenRootDirectory",
1317 SD_BUS_NO_ARGS,
1318 SD_BUS_RESULT("h", fd),
1319 bus_machine_method_open_root_directory,
1320 SD_BUS_VTABLE_UNPRIVILEGED),
1321
1322 SD_BUS_VTABLE_END
1323 };
1324
1325 const BusObjectImplementation machine_object = {
1326 "/org/freedesktop/machine1/machine",
1327 "org.freedesktop.machine1.Machine",
1328 .fallback_vtables = BUS_FALLBACK_VTABLES({machine_vtable, machine_object_find}),
1329 .node_enumerator = machine_node_enumerator,
1330 };
1331
1332 int machine_send_signal(Machine *m, bool new_machine) {
1333 _cleanup_free_ char *p = NULL;
1334
1335 assert(m);
1336
1337 p = machine_bus_path(m);
1338 if (!p)
1339 return -ENOMEM;
1340
1341 return sd_bus_emit_signal(
1342 m->manager->bus,
1343 "/org/freedesktop/machine1",
1344 "org.freedesktop.machine1.Manager",
1345 new_machine ? "MachineNew" : "MachineRemoved",
1346 "so", m->name, p);
1347 }
1348
1349 int machine_send_create_reply(Machine *m, sd_bus_error *error) {
1350 _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL;
1351 _cleanup_free_ char *p = NULL;
1352
1353 assert(m);
1354
1355 if (!m->create_message)
1356 return 0;
1357
1358 c = TAKE_PTR(m->create_message);
1359
1360 if (error)
1361 return sd_bus_reply_method_error(c, error);
1362
1363 /* Update the machine state file before we notify the client
1364 * about the result. */
1365 machine_save(m);
1366
1367 p = machine_bus_path(m);
1368 if (!p)
1369 return -ENOMEM;
1370
1371 return sd_bus_reply_method_return(c, "o", p);
1372 }