1 /*
2 * virsystemd.c: helpers for using systemd APIs
3 *
4 * Copyright (C) 2013, 2014 Red Hat, Inc.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see
18 * <http://www.gnu.org/licenses/>.
19 *
20 */
21
22 #include <config.h>
23
24 #define LIBVIRT_VIRSYSTEMDPRIV_H_ALLOW
25 #include "virsystemdpriv.h"
26
27 #include "virsystemd.h"
28 #include "virbuffer.h"
29 #include "virgdbus.h"
30 #include "virstring.h"
31 #include "viralloc.h"
32 #include "virutil.h"
33 #include "virlog.h"
34 #include "virerror.h"
35 #include "virfile.h"
36 #include "virhash.h"
37 #include "virsocketaddr.h"
38
39 #define VIR_FROM_THIS VIR_FROM_SYSTEMD
40
41 VIR_LOG_INIT("util.systemd");
42
43 #ifndef MSG_NOSIGNAL
44 # define MSG_NOSIGNAL 0
45 #endif
46
47 struct _virSystemdActivation {
48 GHashTable *fds;
49 };
50
51 typedef struct _virSystemdActivationEntry virSystemdActivationEntry;
52 struct _virSystemdActivationEntry {
53 int *fds;
54 size_t nfds;
55 };
56
virSystemdEscapeName(virBuffer * buf,const char * name)57 static void virSystemdEscapeName(virBuffer *buf,
58 const char *name)
59 {
60 static const char hextable[16] = "0123456789abcdef";
61
62 #define ESCAPE(c) \
63 do { \
64 virBufferAddChar(buf, '\\'); \
65 virBufferAddChar(buf, 'x'); \
66 virBufferAddChar(buf, hextable[(c >> 4) & 15]); \
67 virBufferAddChar(buf, hextable[c & 15]); \
68 } while (0)
69
70 #define VALID_CHARS \
71 "0123456789" \
72 "abcdefghijklmnopqrstuvwxyz" \
73 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
74 ":-_.\\"
75
76 if (*name == '.') {
77 ESCAPE(*name);
78 name++;
79 }
80
81 while (*name) {
82 if (*name == '/')
83 virBufferAddChar(buf, '-');
84 else if (*name == '-' ||
85 *name == '\\' ||
86 !strchr(VALID_CHARS, *name))
87 ESCAPE(*name);
88 else
89 virBufferAddChar(buf, *name);
90 name++;
91 }
92
93 #undef ESCAPE
94 #undef VALID_CHARS
95 }
96
virSystemdMakeScopeName(const char * name,const char * drivername,bool legacy_behaviour)97 char *virSystemdMakeScopeName(const char *name,
98 const char *drivername,
99 bool legacy_behaviour)
100 {
101 g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
102
103 virBufferAddLit(&buf, "machine-");
104 if (legacy_behaviour) {
105 virSystemdEscapeName(&buf, drivername);
106 virBufferAddLit(&buf, "\\x2d");
107 }
108 virSystemdEscapeName(&buf, name);
109 virBufferAddLit(&buf, ".scope");
110
111 return virBufferContentAndReset(&buf);
112 }
113
114
virSystemdMakeSliceName(const char * partition)115 char *virSystemdMakeSliceName(const char *partition)
116 {
117 g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
118
119 if (*partition == '/')
120 partition++;
121
122 virSystemdEscapeName(&buf, partition);
123 virBufferAddLit(&buf, ".slice");
124
125 return virBufferContentAndReset(&buf);
126 }
127
128 static int virSystemdHasMachinedCachedValue = -1;
129 static int virSystemdHasLogindCachedValue = -1;
130
131 /* Reset the cache from tests for testing the underlying dbus calls
132 * as well */
virSystemdHasMachinedResetCachedValue(void)133 void virSystemdHasMachinedResetCachedValue(void)
134 {
135 virSystemdHasMachinedCachedValue = -1;
136 }
137
virSystemdHasLogindResetCachedValue(void)138 void virSystemdHasLogindResetCachedValue(void)
139 {
140 virSystemdHasLogindCachedValue = -1;
141 }
142
143
144 /* -2 = machine1 is not supported on this machine
145 * -1 = error
146 * 0 = machine1 is available
147 */
148 int
virSystemdHasMachined(void)149 virSystemdHasMachined(void)
150 {
151 int ret;
152 int val;
153
154 val = g_atomic_int_get(&virSystemdHasMachinedCachedValue);
155 if (val != -1)
156 return val;
157
158 if ((ret = virGDBusIsServiceEnabled("org.freedesktop.machine1")) < 0) {
159 if (ret == -2)
160 g_atomic_int_set(&virSystemdHasMachinedCachedValue, -2);
161 return ret;
162 }
163
164 if ((ret = virGDBusIsServiceRegistered("org.freedesktop.systemd1")) == -1)
165 return ret;
166 g_atomic_int_set(&virSystemdHasMachinedCachedValue, ret);
167 return ret;
168 }
169
170 int
virSystemdHasLogind(void)171 virSystemdHasLogind(void)
172 {
173 int ret;
174 int val;
175
176 val = g_atomic_int_get(&virSystemdHasLogindCachedValue);
177 if (val != -1)
178 return val;
179
180 ret = virGDBusIsServiceEnabled("org.freedesktop.login1");
181 if (ret < 0) {
182 if (ret == -2)
183 g_atomic_int_set(&virSystemdHasLogindCachedValue, -2);
184 return ret;
185 }
186
187 if ((ret = virGDBusIsServiceRegistered("org.freedesktop.login1")) == -1)
188 return ret;
189
190 g_atomic_int_set(&virSystemdHasLogindCachedValue, ret);
191 return ret;
192 }
193
194
195 /**
196 * virSystemdGetMachineByPID:
197 * @conn: dbus connection
198 * @pid: pid of running VM
199 *
200 * Returns dbus object path to VM registered with machined.
201 * On error returns NULL.
202 */
203 static char *
virSystemdGetMachineByPID(GDBusConnection * conn,pid_t pid)204 virSystemdGetMachineByPID(GDBusConnection *conn,
205 pid_t pid)
206 {
207 g_autoptr(GVariant) message = NULL;
208 g_autoptr(GVariant) reply = NULL;
209 char *object = NULL;
210
211 message = g_variant_new("(u)", pid);
212
213 if (virGDBusCallMethod(conn,
214 &reply,
215 G_VARIANT_TYPE("(o)"),
216 NULL,
217 "org.freedesktop.machine1",
218 "/org/freedesktop/machine1",
219 "org.freedesktop.machine1.Manager",
220 "GetMachineByPID",
221 message) < 0)
222 return NULL;
223
224 g_variant_get(reply, "(o)", &object);
225
226 VIR_DEBUG("Domain with pid %lld has object path '%s'",
227 (long long) pid, object);
228
229 return object;
230 }
231
232
233 char *
virSystemdGetMachineNameByPID(pid_t pid)234 virSystemdGetMachineNameByPID(pid_t pid)
235 {
236 GDBusConnection *conn;
237 g_autoptr(GVariant) message = NULL;
238 g_autoptr(GVariant) reply = NULL;
239 g_autoptr(GVariant) gvar = NULL;
240 g_autofree char *object = NULL;
241 char *name = NULL;
242
243 if (virSystemdHasMachined() < 0)
244 return NULL;
245
246 if (!(conn = virGDBusGetSystemBus()))
247 return NULL;
248
249 object = virSystemdGetMachineByPID(conn, pid);
250 if (!object)
251 return NULL;
252
253 message = g_variant_new("(ss)",
254 "org.freedesktop.machine1.Machine", "Name");
255
256 if (virGDBusCallMethod(conn,
257 &reply,
258 G_VARIANT_TYPE("(v)"),
259 NULL,
260 "org.freedesktop.machine1",
261 object,
262 "org.freedesktop.DBus.Properties",
263 "Get",
264 message) < 0)
265 return NULL;
266
267 g_variant_get(reply, "(v)", &gvar);
268 g_variant_get(gvar, "s", &name);
269
270 VIR_DEBUG("Domain with pid %lld has machine name '%s'",
271 (long long) pid, name);
272
273 return name;
274 }
275
276
277 /**
278 * virSystemdGetMachineUnitByPID:
279 * @pid: pid of running VM
280 *
281 * Returns systemd Unit name of a running VM registered with machined.
282 * On error returns NULL.
283 */
284 char *
virSystemdGetMachineUnitByPID(pid_t pid)285 virSystemdGetMachineUnitByPID(pid_t pid)
286 {
287 GDBusConnection *conn;
288 g_autoptr(GVariant) message = NULL;
289 g_autoptr(GVariant) reply = NULL;
290 g_autoptr(GVariant) gvar = NULL;
291 g_autofree char *object = NULL;
292 char *unit = NULL;
293
294 if (virSystemdHasMachined() < 0)
295 return NULL;
296
297 if (!(conn = virGDBusGetSystemBus()))
298 return NULL;
299
300 object = virSystemdGetMachineByPID(conn, pid);
301 if (!object)
302 return NULL;
303
304 message = g_variant_new("(ss)",
305 "org.freedesktop.machine1.Machine", "Unit");
306
307 if (virGDBusCallMethod(conn,
308 &reply,
309 G_VARIANT_TYPE("(v)"),
310 NULL,
311 "org.freedesktop.machine1",
312 object,
313 "org.freedesktop.DBus.Properties",
314 "Get",
315 message) < 0)
316 return NULL;
317
318 g_variant_get(reply, "(v)", &gvar);
319 g_variant_get(gvar, "s", &unit);
320
321 VIR_DEBUG("Domain with pid %lld has unit name '%s'",
322 (long long) pid, unit);
323
324 return unit;
325 }
326
327
328 /**
329 * virSystemdCreateMachine:
330 * @name: driver unique name of the machine
331 * @drivername: name of the virt driver
332 * @privileged: whether driver is running privileged or per user
333 * @uuid: globally unique UUID of the machine
334 * @rootdir: root directory of machine filesystem
335 * @pidleader: PID of the leader process
336 * @iscontainer: true if a container, false if a VM
337 * @nnicindexes: number of network interface indexes in list
338 * @nicindexes: list of network interface indexes
339 * @partition: name of the slice to place the machine in
340 *
341 * Returns 0 on success, -1 on fatal error, or -2 if systemd-machine is not available
342 */
virSystemdCreateMachine(const char * name,const char * drivername,const unsigned char * uuid,const char * rootdir,pid_t pidleader,bool iscontainer,size_t nnicindexes,int * nicindexes,const char * partition,unsigned int maxthreads)343 int virSystemdCreateMachine(const char *name,
344 const char *drivername,
345 const unsigned char *uuid,
346 const char *rootdir,
347 pid_t pidleader,
348 bool iscontainer,
349 size_t nnicindexes,
350 int *nicindexes,
351 const char *partition,
352 unsigned int maxthreads)
353 {
354 int rc;
355 GDBusConnection *conn;
356 GVariant *guuid;
357 GVariant *gnicindexes;
358 GVariant *gprops;
359 GVariant *message;
360 g_autofree char *creatorname = NULL;
361 g_autofree char *slicename = NULL;
362 g_autofree char *scopename = NULL;
363 static int hasCreateWithNetwork = 1;
364
365 if ((rc = virSystemdHasMachined()) < 0)
366 return rc;
367
368 if (!(conn = virGDBusGetSystemBus()))
369 return -1;
370
371 creatorname = g_strdup_printf("libvirt-%s", drivername);
372
373 if (partition) {
374 if (!(slicename = virSystemdMakeSliceName(partition)))
375 return -1;
376 } else {
377 slicename = g_strdup("");
378 }
379
380 /*
381 * The systemd DBus APIs we're invoking have the
382 * following signature(s)
383 *
384 * CreateMachineWithNetwork(in s name,
385 * in ay id,
386 * in s service,
387 * in s class,
388 * in u leader,
389 * in s root_directory,
390 * in ai nicindexes
391 * in a(sv) scope_properties,
392 * out o path);
393 *
394 * CreateMachine(in s name,
395 * in ay id,
396 * in s service,
397 * in s class,
398 * in u leader,
399 * in s root_directory,
400 * in a(sv) scope_properties,
401 * out o path);
402 *
403 * @name a host unique name for the machine. shows up
404 * in 'ps' listing & similar
405 *
406 * @id: a UUID of the machine, ideally matching /etc/machine-id
407 * for containers
408 *
409 * @service: identifier of the client ie "libvirt-lxc"
410 *
411 * @class: either the string "container" or "vm" depending
412 * on the type of machine
413 *
414 * @leader: main PID of the machine, either the host emulator
415 * process, or the 'init' PID of the container
416 *
417 * @root_directory: the root directory of the container, if
418 * this is known & visible in the host filesystem, or empty string
419 *
420 * @nicindexes: list of network interface indexes for the
421 * host end of the VETH device pairs.
422 *
423 * @scope_properties:an array (not a dict!) of properties that are
424 * passed on to PID 1 when creating a scope unit for your machine.
425 * Will allow initial settings for the cgroup & similar.
426 *
427 * @path: a bus path returned for the machine object created, to
428 * allow further API calls to be made against the object.
429 *
430 */
431
432 VIR_DEBUG("Attempting to create machine via systemd");
433 if (g_atomic_int_get(&hasCreateWithNetwork)) {
434 g_autoptr(virError) error = NULL;
435
436 error = g_new0(virError, 1);
437
438 guuid = g_variant_new_fixed_array(G_VARIANT_TYPE("y"),
439 uuid, 16, sizeof(unsigned char));
440 gnicindexes = g_variant_new_fixed_array(G_VARIANT_TYPE("i"),
441 nicindexes, nnicindexes, sizeof(int));
442 gprops = g_variant_new_parsed("[('Slice', <%s>),"
443 " ('After', <['libvirtd.service']>),"
444 " ('Before', <['virt-guest-shutdown.target']>)]",
445 slicename);
446 message = g_variant_new("(s@ayssus@ai@a(sv))",
447 name,
448 guuid,
449 creatorname,
450 iscontainer ? "container" : "vm",
451 (unsigned int)pidleader,
452 NULLSTR_EMPTY(rootdir),
453 gnicindexes,
454 gprops);
455
456 rc = virGDBusCallMethod(conn,
457 NULL,
458 NULL,
459 error,
460 "org.freedesktop.machine1",
461 "/org/freedesktop/machine1",
462 "org.freedesktop.machine1.Manager",
463 "CreateMachineWithNetwork",
464 message);
465
466 g_variant_unref(message);
467
468 if (rc < 0)
469 return -1;
470
471 if (error->level == VIR_ERR_ERROR) {
472 if (virGDBusErrorIsUnknownMethod(error)) {
473 VIR_INFO("CreateMachineWithNetwork isn't supported, switching "
474 "to legacy CreateMachine method for systemd-machined");
475 virResetError(error);
476 g_atomic_int_set(&hasCreateWithNetwork, 0);
477 /* Could re-structure without Using goto, but this
478 * avoids another atomic read which would trigger
479 * another memory barrier */
480 goto fallback;
481 }
482 virReportErrorObject(error);
483 virResetError(error);
484 return -1;
485 }
486 } else {
487 fallback:
488 guuid = g_variant_new_fixed_array(G_VARIANT_TYPE("y"),
489 uuid, 16, sizeof(unsigned char));
490 gprops = g_variant_new_parsed("[('Slice', <%s>),"
491 " ('After', <['libvirtd.service']>),"
492 " ('Before', <['virt-guest-shutdown.target']>)]",
493 slicename);
494 message = g_variant_new("(s@ayssus@a(sv))",
495 name,
496 guuid,
497 creatorname,
498 iscontainer ? "container" : "vm",
499 (unsigned int)pidleader,
500 NULLSTR_EMPTY(rootdir),
501 gprops);
502
503 rc = virGDBusCallMethod(conn,
504 NULL,
505 NULL,
506 NULL,
507 "org.freedesktop.machine1",
508 "/org/freedesktop/machine1",
509 "org.freedesktop.machine1.Manager",
510 "CreateMachine",
511 message);
512
513 g_variant_unref(message);
514
515 if (rc < 0)
516 return -1;
517 }
518
519 if (maxthreads > 0) {
520 uint64_t max = maxthreads;
521
522 if (!(scopename = virSystemdMakeScopeName(name, drivername, false)))
523 return -1;
524
525 gprops = g_variant_new_parsed("[('TasksMax', <%t>)]", max);
526
527 message = g_variant_new("(sb@a(sv))",
528 scopename,
529 true,
530 gprops);
531
532 rc = virGDBusCallMethod(conn,
533 NULL,
534 NULL,
535 NULL,
536 "org.freedesktop.systemd1",
537 "/org/freedesktop/systemd1",
538 "org.freedesktop.systemd1.Manager",
539 "SetUnitProperties",
540 message);
541
542 g_variant_unref(message);
543
544 if (rc < 0)
545 return -1;
546 }
547
548 return 0;
549 }
550
virSystemdTerminateMachine(const char * name)551 int virSystemdTerminateMachine(const char *name)
552 {
553 int rc;
554 GDBusConnection *conn;
555 g_autoptr(GVariant) message = NULL;
556 g_autoptr(virError) error = NULL;
557
558 if (!name)
559 return 0;
560
561 if ((rc = virSystemdHasMachined()) < 0)
562 return rc;
563
564 if (!(conn = virGDBusGetSystemBus()))
565 return -1;
566
567 error = g_new0(virError, 1);
568
569 /*
570 * The systemd DBus API we're invoking has the
571 * following signature
572 *
573 * TerminateMachine(in s name);
574 *
575 * @name a host unique name for the machine. shows up
576 * in 'ps' listing & similar
577 */
578
579 message = g_variant_new("(s)", name);
580
581 VIR_DEBUG("Attempting to terminate machine via systemd");
582 if (virGDBusCallMethod(conn,
583 NULL,
584 NULL,
585 error,
586 "org.freedesktop.machine1",
587 "/org/freedesktop/machine1",
588 "org.freedesktop.machine1.Manager",
589 "TerminateMachine",
590 message) < 0)
591 return -1;
592
593 if (error->level == VIR_ERR_ERROR &&
594 STRNEQ_NULLABLE("org.freedesktop.machine1.NoSuchMachine",
595 error->str1)) {
596 virReportErrorObject(error);
597 return -1;
598 }
599
600 return 0;
601 }
602
603 void
virSystemdNotifyStartup(void)604 virSystemdNotifyStartup(void)
605 {
606 #ifndef WIN32
607 const char *path;
608 const char *msg = "READY=1";
609 int fd;
610 struct sockaddr_un un = {
611 .sun_family = AF_UNIX,
612 };
613 struct iovec iov = {
614 .iov_base = (char *)msg,
615 .iov_len = strlen(msg),
616 };
617 struct msghdr mh = {
618 .msg_name = &un,
619 .msg_iov = &iov,
620 .msg_iovlen = 1,
621 };
622
623 if (!(path = getenv("NOTIFY_SOCKET"))) {
624 VIR_DEBUG("Skipping systemd notify, not requested");
625 return;
626 }
627
628 /* NB sun_path field is *not* NUL-terminated, hence >, not >= */
629 if (strlen(path) > sizeof(un.sun_path)) {
630 VIR_WARN("Systemd notify socket path '%s' too long", path);
631 return;
632 }
633
634 memcpy(un.sun_path, path, strlen(path));
635 if (un.sun_path[0] == '@')
636 un.sun_path[0] = '\0';
637
638 mh.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(path);
639
640 fd = socket(AF_UNIX, SOCK_DGRAM, 0);
641 if (fd < 0) {
642 VIR_WARN("Unable to create socket FD");
643 return;
644 }
645
646 if (sendmsg(fd, &mh, MSG_NOSIGNAL) < 0)
647 VIR_WARN("Failed to notify systemd");
648
649 VIR_FORCE_CLOSE(fd);
650 #endif /* !WIN32 */
651 }
652
653 static int
virSystemdPMSupportTarget(const char * methodName,bool * result)654 virSystemdPMSupportTarget(const char *methodName, bool *result)
655 {
656 int rc;
657 GDBusConnection *conn;
658 g_autoptr(GVariant) reply = NULL;
659 char *response;
660
661 if ((rc = virSystemdHasLogind()) < 0)
662 return rc;
663
664 if (!(conn = virGDBusGetSystemBus()))
665 return -1;
666
667 if (virGDBusCallMethod(conn,
668 &reply,
669 G_VARIANT_TYPE("(s)"),
670 NULL,
671 "org.freedesktop.login1",
672 "/org/freedesktop/login1",
673 "org.freedesktop.login1.Manager",
674 methodName,
675 NULL) < 0)
676 return -1;
677
678 g_variant_get(reply, "(&s)", &response);
679
680 *result = STREQ("yes", response) || STREQ("challenge", response);
681
682 return 0;
683 }
684
virSystemdCanSuspend(bool * result)685 int virSystemdCanSuspend(bool *result)
686 {
687 return virSystemdPMSupportTarget("CanSuspend", result);
688 }
689
virSystemdCanHibernate(bool * result)690 int virSystemdCanHibernate(bool *result)
691 {
692 return virSystemdPMSupportTarget("CanHibernate", result);
693 }
694
virSystemdCanHybridSleep(bool * result)695 int virSystemdCanHybridSleep(bool *result)
696 {
697 return virSystemdPMSupportTarget("CanHybridSleep", result);
698 }
699
700
701 static void
virSystemdActivationEntryFree(void * data)702 virSystemdActivationEntryFree(void *data)
703 {
704 virSystemdActivationEntry *ent = data;
705 size_t i;
706
707 VIR_DEBUG("Closing activation FDs");
708 for (i = 0; i < ent->nfds; i++) {
709 VIR_DEBUG("Closing activation FD %d", ent->fds[i]);
710 VIR_FORCE_CLOSE(ent->fds[i]);
711 }
712
713 g_free(ent->fds);
714 g_free(ent);
715 }
716
717
718 static int
virSystemdActivationAddFD(virSystemdActivation * act,const char * name,int fd)719 virSystemdActivationAddFD(virSystemdActivation *act,
720 const char *name,
721 int fd)
722 {
723 virSystemdActivationEntry *ent = virHashLookup(act->fds, name);
724
725 if (!ent) {
726 ent = g_new0(virSystemdActivationEntry, 1);
727 ent->fds = g_new0(int, 1);
728 ent->fds[ent->nfds++] = fd;
729
730 VIR_DEBUG("Record first FD %d with name %s", fd, name);
731 if (virHashAddEntry(act->fds, name, ent) < 0) {
732 virSystemdActivationEntryFree(ent);
733 return -1;
734 }
735
736 return 0;
737 }
738
739 VIR_EXPAND_N(ent->fds, ent->nfds, 1);
740
741 VIR_DEBUG("Record extra FD %d with name %s", fd, name);
742 ent->fds[ent->nfds - 1] = fd;
743
744 return 0;
745 }
746
747
748 static int
virSystemdActivationInitFromNames(virSystemdActivation * act,int nfds,const char * fdnames)749 virSystemdActivationInitFromNames(virSystemdActivation *act,
750 int nfds,
751 const char *fdnames)
752 {
753 g_auto(GStrv) fdnamelistptr = NULL;
754 char **fdnamelist;
755 size_t i;
756 int nextfd = STDERR_FILENO + 1;
757
758 VIR_DEBUG("FD names %s", fdnames);
759
760 if (!(fdnamelistptr = g_strsplit(fdnames, ":", 0)))
761 goto error;
762
763 if (g_strv_length(fdnamelistptr) != nfds) {
764 virReportError(VIR_ERR_INTERNAL_ERROR,
765 _("Expecting %d FD names but got %u"),
766 nfds, g_strv_length(fdnamelistptr));
767 goto error;
768 }
769
770 fdnamelist = fdnamelistptr;
771 while (nfds) {
772 if (virSystemdActivationAddFD(act, *fdnamelist, nextfd) < 0)
773 goto error;
774
775 fdnamelist++;
776 nextfd++;
777 nfds--;
778 }
779
780 return 0;
781
782 error:
783 for (i = 0; i < nfds; i++) {
784 int fd = nextfd + i;
785 VIR_FORCE_CLOSE(fd);
786 }
787 return -1;
788 }
789
790
791 /*
792 * Back compat for systemd < v227 which lacks LISTEN_FDNAMES.
793 * Delete when min systemd is increased ie RHEL7 dropped
794 */
795 static int
virSystemdActivationInitFromMap(virSystemdActivation * act,int nfds,virSystemdActivationMap * map,size_t nmap)796 virSystemdActivationInitFromMap(virSystemdActivation *act,
797 int nfds,
798 virSystemdActivationMap *map,
799 size_t nmap)
800 {
801 int nextfd = STDERR_FILENO + 1;
802 size_t i;
803
804 while (nfds) {
805 virSocketAddr addr;
806 const char *name = NULL;
807
808 memset(&addr, 0, sizeof(addr));
809
810 addr.len = sizeof(addr.data);
811 if (getsockname(nextfd, &addr.data.sa, &addr.len) < 0) {
812 virReportSystemError(errno, "%s", _("Unable to get local socket name"));
813 goto error;
814 }
815
816 VIR_DEBUG("Got socket family %d for FD %d",
817 addr.data.sa.sa_family, nextfd);
818
819 for (i = 0; i < nmap && !name; i++) {
820 if (map[i].name == NULL)
821 continue;
822
823 if (addr.data.sa.sa_family == AF_INET) {
824 if (map[i].family == AF_INET) {
825 VIR_DEBUG("Expect %d got %d",
826 map[i].port, ntohs(addr.data.inet4.sin_port));
827 if (addr.data.inet4.sin_port == htons(map[i].port))
828 name = map[i].name;
829 }
830 } else if (addr.data.sa.sa_family == AF_INET6) {
831 /* NB use of AF_INET here is correct. The "map" struct
832 * only refers to AF_INET. The socket may be AF_INET
833 * or AF_INET6
834 */
835 if (map[i].family == AF_INET) {
836 VIR_DEBUG("Expect %d got %d",
837 map[i].port, ntohs(addr.data.inet6.sin6_port));
838 if (addr.data.inet6.sin6_port == htons(map[i].port))
839 name = map[i].name;
840 }
841 #ifndef WIN32
842 } else if (addr.data.sa.sa_family == AF_UNIX) {
843 if (map[i].family == AF_UNIX) {
844 VIR_DEBUG("Expect %s got %s", map[i].path, addr.data.un.sun_path);
845 if (STREQLEN(map[i].path,
846 addr.data.un.sun_path,
847 sizeof(addr.data.un.sun_path)))
848 name = map[i].name;
849 }
850 #endif
851 } else {
852 virReportError(VIR_ERR_INTERNAL_ERROR,
853 _("Unexpected socket family %d"),
854 addr.data.sa.sa_family);
855 goto error;
856 }
857 }
858
859 if (!name) {
860 virReportError(VIR_ERR_INTERNAL_ERROR,
861 _("Cannot find name for FD %d socket family %d"),
862 nextfd, addr.data.sa.sa_family);
863 goto error;
864 }
865
866 if (virSystemdActivationAddFD(act, name, nextfd) < 0)
867 goto error;
868
869 nfds--;
870 nextfd++;
871 }
872
873 return 0;
874
875 error:
876 for (i = 0; i < nfds; i++) {
877 int fd = nextfd + i;
878 VIR_FORCE_CLOSE(fd);
879 }
880 return -1;
881 }
882
883 #ifndef WIN32
884
885 /**
886 * virSystemdGetListenFDs:
887 *
888 * Parse LISTEN_PID and LISTEN_FDS passed from caller.
889 *
890 * Returns number of passed FDs.
891 */
892 static unsigned int
virSystemdGetListenFDs(void)893 virSystemdGetListenFDs(void)
894 {
895 const char *pidstr;
896 const char *fdstr;
897 size_t i = 0;
898 unsigned long long procid;
899 unsigned int nfds;
900
901 VIR_DEBUG("Setting up networking from caller");
902
903 if (!(pidstr = getenv("LISTEN_PID"))) {
904 VIR_DEBUG("No LISTEN_PID from caller");
905 return 0;
906 }
907
908 if (virStrToLong_ull(pidstr, NULL, 10, &procid) < 0) {
909 VIR_DEBUG("Malformed LISTEN_PID from caller %s", pidstr);
910 return 0;
911 }
912
913 if ((pid_t)procid != getpid()) {
914 VIR_DEBUG("LISTEN_PID %s is not for us %lld",
915 pidstr, (long long) getpid());
916 return 0;
917 }
918
919 if (!(fdstr = getenv("LISTEN_FDS"))) {
920 VIR_DEBUG("No LISTEN_FDS from caller");
921 return 0;
922 }
923
924 if (virStrToLong_ui(fdstr, NULL, 10, &nfds) < 0) {
925 VIR_DEBUG("Malformed LISTEN_FDS from caller %s", fdstr);
926 return 0;
927 }
928
929 g_unsetenv("LISTEN_PID");
930 g_unsetenv("LISTEN_FDS");
931
932 VIR_DEBUG("Got %u file descriptors", nfds);
933
934 for (i = 0; i < nfds; i++) {
935 int fd = STDERR_FILENO + i + 1;
936
937 VIR_DEBUG("Disabling inheritance of passed FD %d", fd);
938
939 if (virSetInherit(fd, false) < 0)
940 VIR_WARN("Couldn't disable inheritance of passed FD %d", fd);
941 }
942
943 return nfds;
944 }
945
946 #else /* WIN32 */
947
948 static unsigned int
virSystemdGetListenFDs(void)949 virSystemdGetListenFDs(void)
950 {
951 return 0;
952 }
953
954 #endif /* WIN32 */
955
956 static virSystemdActivation *
virSystemdActivationNew(virSystemdActivationMap * map,size_t nmap,int nfds)957 virSystemdActivationNew(virSystemdActivationMap *map,
958 size_t nmap,
959 int nfds)
960 {
961 g_autoptr(virSystemdActivation) act = g_new0(virSystemdActivation, 1);
962 const char *fdnames;
963
964 VIR_DEBUG("Activated with %d FDs", nfds);
965
966 act->fds = virHashNew(virSystemdActivationEntryFree);
967
968 fdnames = getenv("LISTEN_FDNAMES");
969 if (fdnames) {
970 if (virSystemdActivationInitFromNames(act, nfds, fdnames) < 0)
971 return NULL;
972 } else {
973 if (virSystemdActivationInitFromMap(act, nfds, map, nmap) < 0)
974 return NULL;
975 }
976
977 VIR_DEBUG("Created activation object for %d FDs", nfds);
978 return g_steal_pointer(&act);
979 }
980
981
982 /**
983 * virSystemdGetActivation:
984 * @map: mapping of socket addresses to names
985 * @nmap: number of entries in @map
986 * @act: filled with allocated activation object
987 *
988 * Acquire an object for handling systemd activation.
989 * If no activation FDs have been provided the returned object
990 * will be NULL, indicating normal service setup can be performed
991 * If the returned object is non-NULL then at least one file
992 * descriptor will be present. No normal service setup should
993 * be performed.
994 *
995 * Returns: 0 on success, -1 on failure
996 */
997 int
virSystemdGetActivation(virSystemdActivationMap * map,size_t nmap,virSystemdActivation ** act)998 virSystemdGetActivation(virSystemdActivationMap *map,
999 size_t nmap,
1000 virSystemdActivation **act)
1001 {
1002 int nfds = 0;
1003
1004 if ((nfds = virSystemdGetListenFDs()) < 0)
1005 return -1;
1006
1007 if (nfds == 0) {
1008 VIR_DEBUG("No activation FDs present");
1009 *act = NULL;
1010 return 0;
1011 }
1012
1013 *act = virSystemdActivationNew(map, nmap, nfds);
1014 return 0;
1015 }
1016
1017
1018 /**
1019 * virSystemdActivationHasName:
1020 * @act: the activation object
1021 * @name: the file descriptor name
1022 *
1023 * Check whether there is a file descriptor present
1024 * for the requested name.
1025 *
1026 * Returns: true if a FD is present, false otherwise
1027 */
1028 bool
virSystemdActivationHasName(virSystemdActivation * act,const char * name)1029 virSystemdActivationHasName(virSystemdActivation *act,
1030 const char *name)
1031 {
1032 return virHashLookup(act->fds, name) != NULL;
1033 }
1034
1035
1036 /**
1037 * virSystemdActivationComplete:
1038 * @act: the activation object
1039 *
1040 * Indicate that processing of activation has been
1041 * completed. All provided file descriptors should
1042 * have been claimed. If any are unclaimed then
1043 * an error will be reported
1044 *
1045 * Returns: 0 on success, -1 if some FDs are unclaimed
1046 */
1047 int
virSystemdActivationComplete(virSystemdActivation * act)1048 virSystemdActivationComplete(virSystemdActivation *act)
1049 {
1050 if (virHashSize(act->fds) != 0) {
1051 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
1052 _("Some activation file descriptors are unclaimed"));
1053 return -1;
1054 }
1055
1056 return 0;
1057 }
1058
1059
1060 /**
1061 * virSystemdActivationClaimFDs:
1062 * @act: the activation object
1063 * @name: the file descriptor name
1064 * @fds: to be filled with claimed FDs
1065 * @nfds: to be filled with number of FDs in @fds
1066 *
1067 * Claims the file descriptors associated with
1068 * @name.
1069 *
1070 * The caller is responsible for closing all
1071 * returned file descriptors when they are no
1072 * longer required. The caller must also free
1073 * the array memory in @fds.
1074 */
1075 void
virSystemdActivationClaimFDs(virSystemdActivation * act,const char * name,int ** fds,size_t * nfds)1076 virSystemdActivationClaimFDs(virSystemdActivation *act,
1077 const char *name,
1078 int **fds,
1079 size_t *nfds)
1080 {
1081 virSystemdActivationEntry *ent = virHashSteal(act->fds, name);
1082
1083 if (!ent) {
1084 *fds = NULL;
1085 *nfds = 0;
1086 VIR_DEBUG("No FD with name %s", name);
1087 return;
1088 }
1089
1090 VIR_DEBUG("Found %zu FDs with name %s", ent->nfds, name);
1091 *fds = ent->fds;
1092 *nfds = ent->nfds;
1093
1094 VIR_FREE(ent);
1095 }
1096
1097
1098 /**
1099 * virSystemdActivationFree:
1100 * @act: the activation object
1101 *
1102 * Free memory and close unclaimed file descriptors
1103 * associated with the activation object
1104 */
1105 void
virSystemdActivationFree(virSystemdActivation * act)1106 virSystemdActivationFree(virSystemdActivation *act)
1107 {
1108 if (!act)
1109 return;
1110
1111 virHashFree(act->fds);
1112
1113 g_free(act);
1114 }
1115