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