#include "virlog.h"
#include "virerror.h"
#include "virfile.h"
+#include "virhash.h"
+#include "virsocketaddr.h"
#define VIR_FROM_THIS VIR_FROM_SYSTEMD
# define MSG_NOSIGNAL 0
#endif
+struct _virSystemdActivation {
+ virHashTablePtr fds;
+};
+
+typedef struct _virSystemdActivationEntry virSystemdActivationEntry;
+typedef virSystemdActivationEntry *virSystemdActivationEntryPtr;
+
+struct _virSystemdActivationEntry {
+ int *fds;
+ size_t nfds;
+};
+
static void virSystemdEscapeName(virBufferPtr buf,
const char *name)
{
{
return virSystemdPMSupportTarget("CanHybridSleep", result);
}
+
+
+static void
+virSystemdActivationEntryFree(void *data, const void *name)
+{
+ virSystemdActivationEntryPtr ent = data;
+ size_t i;
+
+ VIR_DEBUG("Closing activation FDs for %s", (const char *)name);
+ for (i = 0; i < ent->nfds; i++) {
+ VIR_DEBUG("Closing activation FD %d", ent->fds[i]);
+ VIR_FORCE_CLOSE(ent->fds[i]);
+ }
+
+ VIR_FREE(ent->fds);
+ VIR_FREE(ent);
+}
+
+
+static int
+virSystemdActivationAddFD(virSystemdActivationPtr act,
+ const char *name,
+ int fd)
+{
+ virSystemdActivationEntryPtr ent = virHashLookup(act->fds, name);
+
+ if (!ent) {
+ if (VIR_ALLOC(ent) < 0)
+ return -1;
+
+ if (VIR_ALLOC_N(ent->fds, 1) < 0) {
+ virSystemdActivationEntryFree(ent, name);
+ return -1;
+ }
+
+ ent->fds[ent->nfds++] = fd;
+
+ VIR_DEBUG("Record first FD %d with name %s", fd, name);
+ if (virHashAddEntry(act->fds, name, ent) < 0) {
+ virSystemdActivationEntryFree(ent, name);
+ return -1;
+ }
+
+ return 0;
+ }
+
+ if (VIR_EXPAND_N(ent->fds, ent->nfds, 1) < 0)
+ return -1;
+
+ VIR_DEBUG("Record extra FD %d with name %s", fd, name);
+ ent->fds[ent->nfds - 1] = fd;
+
+ return 0;
+}
+
+
+static int
+virSystemdActivationInitFromNames(virSystemdActivationPtr act,
+ int nfds,
+ const char *fdnames)
+{
+ VIR_AUTOSTRINGLIST fdnamelistptr = NULL;
+ char **fdnamelist;
+ size_t nfdnames;
+ size_t i;
+ int nextfd = STDERR_FILENO + 1;
+
+ VIR_DEBUG("FD names %s", fdnames);
+
+ if (!(fdnamelistptr = virStringSplitCount(fdnames, ":", 0, &nfdnames)))
+ goto error;
+
+ if (nfdnames != nfds) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Expecting %d FD names but got %zu"),
+ nfds, nfdnames);
+ goto error;
+ }
+
+ fdnamelist = fdnamelistptr;
+ while (nfds) {
+ if (virSystemdActivationAddFD(act, *fdnamelist, nextfd) < 0)
+ goto error;
+
+ fdnamelist++;
+ nextfd++;
+ nfds--;
+ }
+
+ return 0;
+
+ error:
+ for (i = 0; i < nfds; i++) {
+ int fd = nextfd + i;
+ VIR_FORCE_CLOSE(fd);
+ }
+ return -1;
+}
+
+
+/*
+ * Back compat for systemd < v227 which lacks LISTEN_FDNAMES.
+ * Delete when min systemd is increased ie RHEL7 dropped
+ */
+static int
+virSystemdActivationInitFromMap(virSystemdActivationPtr act,
+ int nfds,
+ virSystemdActivationMap *map,
+ size_t nmap)
+{
+ int nextfd = STDERR_FILENO + 1;
+ size_t i;
+
+ while (nfds) {
+ virSocketAddr addr;
+ const char *name = NULL;
+
+ memset(&addr, 0, sizeof(addr));
+
+ addr.len = sizeof(addr.data);
+ if (getsockname(nextfd, &addr.data.sa, &addr.len) < 0) {
+ virReportSystemError(errno, "%s", _("Unable to get local socket name"));
+ goto error;
+ }
+
+ for (i = 0; i < nmap && !name; i++) {
+ if (map[i].name == NULL)
+ continue;
+
+ if (addr.data.sa.sa_family == AF_INET) {
+ if (map[i].family == AF_INET &&
+ addr.data.inet4.sin_port == htons(map[i].port))
+ name = map[i].name;
+ } else if (addr.data.sa.sa_family == AF_INET6) {
+ /* NB use of AF_INET here is correct. The "map" struct
+ * only refers to AF_INET. The socket may be AF_INET
+ * or AF_INET6
+ */
+ if (map[i].family == AF_INET &&
+ addr.data.inet6.sin6_port == htons(map[i].port))
+ name = map[i].name;
+#ifndef WIN32
+ } else if (addr.data.sa.sa_family == AF_UNIX) {
+ if (map[i].family == AF_UNIX &&
+ STREQLEN(map[i].path,
+ addr.data.un.sun_path,
+ sizeof(addr.data.un.sun_path)))
+ name = map[i].name;
+#endif
+ } else {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unexpected socket family %d"),
+ addr.data.sa.sa_family);
+ goto error;
+ }
+ }
+
+ if (!name) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Cannot find name for FD %d socket family %d"),
+ nextfd, addr.data.sa.sa_family);
+ goto error;
+ }
+
+ if (virSystemdActivationAddFD(act, name, nextfd) < 0)
+ goto error;
+
+ nfds--;
+ nextfd++;
+ }
+
+ return 0;
+
+ error:
+ for (i = 0; i < nfds; i++) {
+ int fd = nextfd + i;
+ VIR_FORCE_CLOSE(fd);
+ }
+ return -1;
+}
+
+
+static virSystemdActivationPtr
+virSystemdActivationNew(virSystemdActivationMap *map,
+ size_t nmap,
+ int nfds)
+{
+ virSystemdActivationPtr act;
+ const char *fdnames;
+
+ VIR_DEBUG("Activated with %d FDs", nfds);
+ if (VIR_ALLOC(act) < 0)
+ return NULL;
+
+ if (!(act->fds = virHashCreate(10, virSystemdActivationEntryFree)))
+ goto error;
+
+ fdnames = virGetEnvAllowSUID("LISTEN_FDNAMES");
+ if (fdnames) {
+ if (virSystemdActivationInitFromNames(act, nfds, fdnames) < 0)
+ goto error;
+ } else {
+ if (virSystemdActivationInitFromMap(act, nfds, map, nmap) < 0)
+ goto error;
+ }
+
+ VIR_DEBUG("Created activation object for %d FDs", nfds);
+ return act;
+
+ error:
+ virSystemdActivationFree(&act);
+ return NULL;
+}
+
+
+/**
+ * virSystemdGetActivation:
+ * @map: mapping of socket addresses to names
+ * @nmap: number of entries in @map
+ * @act: filled with allocated activation object
+ *
+ * Acquire an object for handling systemd activation.
+ * If no activation FDs have been provided the returned object
+ * will be NULL, indicating normal sevice setup can be performed
+ * If the returned object is non-NULL then at least one file
+ * descriptor will be present. No normal service setup should
+ * be performed.
+ *
+ * Returns: 0 on success, -1 on failure
+ */
+int
+virSystemdGetActivation(virSystemdActivationMap *map,
+ size_t nmap,
+ virSystemdActivationPtr *act)
+{
+ int nfds = 0;
+
+ if ((nfds = virGetListenFDs()) < 0)
+ return -1;
+
+ if (nfds == 0) {
+ VIR_DEBUG("No activation FDs present");
+ *act = NULL;
+ return 0;
+ }
+
+ *act = virSystemdActivationNew(map, nmap, nfds);
+ return 0;
+}
+
+
+/**
+ * virSystemdActivationHasName:
+ * @act: the activation object
+ * @name: the file descriptor name
+ *
+ * Check whether there is a file descriptor present
+ * for the requested name.
+ *
+ * Returns: true if a FD is present, false otherwise
+ */
+bool
+virSystemdActivationHasName(virSystemdActivationPtr act,
+ const char *name)
+{
+ return virHashLookup(act->fds, name) != NULL;
+}
+
+
+/**
+ * virSystemdActivationComplete:
+ * @act: the activation object
+ *
+ * Indicate that processing of activation has been
+ * completed. All provided file descriptors should
+ * have been claimed. If any are unclaimed then
+ * an error will be reported
+ *
+ * Returns: 0 on success, -1 if some FDs are unclaimed
+ */
+int
+virSystemdActivationComplete(virSystemdActivationPtr act)
+{
+ if (virHashSize(act->fds) != 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Some activation file descriptors are unclaimed"));
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/**
+ * virSystemdActivationClaimFDs:
+ * @act: the activation object
+ * @name: the file descriptor name
+ * @fds: to be filled with claimed FDs
+ * @nfds: to be filled with number of FDs in @fds
+ *
+ * Claims the file descriptors associated with
+ * @name.
+ *
+ * The caller is responsible for closing all
+ * returned file descriptors when they are no
+ * longer required. The caller must also free
+ * the array memory in @fds.
+ */
+void
+virSystemdActivationClaimFDs(virSystemdActivationPtr act,
+ const char *name,
+ int **fds,
+ size_t *nfds)
+{
+ virSystemdActivationEntryPtr ent = virHashSteal(act->fds, name);
+
+ if (!ent) {
+ *fds = NULL;
+ *nfds = 0;
+ VIR_DEBUG("No FD with name %s", name);
+ return;
+ }
+
+ VIR_DEBUG("Found %zu FDs with name %s", ent->nfds, name);
+ *fds = ent->fds;
+ *nfds = ent->nfds;
+
+ VIR_FREE(ent);
+}
+
+
+/**
+ * virSystemdActivationFree:
+ * @act: the activation object
+ *
+ * Free memory and close unclaimed file descriptors
+ * associated with the activation object
+ */
+void
+virSystemdActivationFree(virSystemdActivationPtr *act)
+{
+ if (!*act)
+ return;
+
+ virHashFree((*act)->fds);
+
+ VIR_FREE(*act);
+}
# include "virdbus.h"
# include "virlog.h"
# include "virmock.h"
+# include "rpc/virnetsocket.h"
+# include "intprops.h"
# define VIR_FROM_THIS VIR_FROM_NONE
VIR_LOG_INIT("tests.systemdtest");
return 0;
}
+
+static int
+testActivationCreateFDs(virNetSocketPtr *sockUNIX,
+ virNetSocketPtr **sockIP,
+ size_t *nsockIP)
+{
+ *sockUNIX = NULL;
+ *sockIP = NULL;
+ *nsockIP = 0;
+
+ if (virNetSocketNewListenUNIX("virsystemdtest.sock",
+ 0777,
+ 0,
+ 0,
+ sockUNIX) < 0)
+ return -1;
+
+ if (virNetSocketNewListenTCP("localhost",
+ NULL,
+ AF_UNSPEC,
+ sockIP,
+ nsockIP) < 0) {
+ virObjectUnref(*sockUNIX);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int
+testActivation(bool useNames)
+{
+ virNetSocketPtr sockUNIX;
+ virNetSocketPtr *sockIP;
+ size_t nsockIP;
+ int ret = -1;
+ size_t i;
+ const char *names2 = "demo-unix.socket:demo-ip.socket";
+ const char *names3 = "demo-unix.socket:demo-ip.socket:demo-ip.socket";
+ char nfdstr[INT_BUFSIZE_BOUND(size_t)];
+ char pidstr[INT_BUFSIZE_BOUND(pid_t)];
+ virSystemdActivationMap map[2];
+ int *fds = NULL;
+ size_t nfds = 0;
+ VIR_AUTOPTR(virSystemdActivation) act = NULL;
+
+ if (testActivationCreateFDs(&sockUNIX, &sockIP, &nsockIP) < 0)
+ return -1;
+
+ if (nsockIP != 1 && nsockIP != 2) {
+ fprintf(stderr, "Got %zu IP sockets but expected only 1 or 2\n", nsockIP);
+ goto cleanup;
+ }
+
+ snprintf(nfdstr, sizeof(nfdstr), "%zu", 1 + nsockIP);
+ snprintf(pidstr, sizeof(pidstr), "%lld", (long long)getpid());
+
+ setenv("LISTEN_FDS", nfdstr, 1);
+ setenv("LISTEN_PID", pidstr, 1);
+
+ if (useNames)
+ setenv("LISTEN_FDNAMES", nsockIP == 1 ? names2 : names3, 1);
+ else
+ unsetenv("LISTEN_FDNAMES");
+
+ map[0].name = "demo-unix.socket";
+ map[0].family = AF_UNIX;
+ map[0].path = virNetSocketGetPath(sockUNIX);
+
+ map[1].name = "demo-ip.socket";
+ map[1].family = AF_INET;
+ map[1].port = virNetSocketGetPort(sockIP[0]);
+
+ if (virSystemdGetActivation(map, ARRAY_CARDINALITY(map), &act) < 0)
+ goto cleanup;
+
+ if (act == NULL) {
+ fprintf(stderr, "Activation object was not created: %s", virGetLastErrorMessage());
+ goto cleanup;
+ }
+
+ if (virSystemdActivationComplete(act) == 0) {
+ fprintf(stderr, "Activation did not report unclaimed FDs");
+ goto cleanup;
+ }
+
+ virSystemdActivationClaimFDs(act, "demo-unix.socket", &fds, &nfds);
+
+ if (nfds != 1) {
+ fprintf(stderr, "Expected 1 UNIX fd, but got %zu\n", nfds);
+ goto cleanup;
+ }
+ VIR_FREE(fds);
+
+ virSystemdActivationClaimFDs(act, "demo-ip.socket", &fds, &nfds);
+
+ if (nfds != nsockIP) {
+ fprintf(stderr, "Expected %zu IP fd, but got %zu\n", nsockIP, nfds);
+ goto cleanup;
+ }
+ VIR_FREE(fds);
+
+ virSystemdActivationClaimFDs(act, "demo-ip-alt.socket", &fds, &nfds);
+
+ if (nfds != 0) {
+ fprintf(stderr, "Expected 0 IP fd, but got %zu\n", nfds);
+ goto cleanup;
+ }
+
+ if (virSystemdActivationComplete(act) < 0) {
+ fprintf(stderr, "Action was not complete: %s\n", virGetLastErrorMessage());
+ goto cleanup;
+ }
+
+ ret = 0;
+ cleanup:
+ virObjectUnref(sockUNIX);
+ for (i = 0; i < nsockIP; i++)
+ virObjectUnref(sockIP[i]);
+ VIR_FREE(sockIP);
+ VIR_FREE(fds);
+ return ret;
+}
+
+
+static int
+testActivationEmpty(const void *opaque ATTRIBUTE_UNUSED)
+{
+ virSystemdActivationPtr act;
+
+ unsetenv("LISTEN_FDS");
+
+ if (virSystemdGetActivation(NULL, 0, &act) < 0)
+ return -1;
+
+ if (act != NULL) {
+ fprintf(stderr, "Unexpectedly got activation object");
+ virSystemdActivationFree(&act);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int
+testActivationFDNames(const void *opaque ATTRIBUTE_UNUSED)
+{
+ return testActivation(true);
+}
+
+
+static int
+testActivationFDAddrs(const void *opaque ATTRIBUTE_UNUSED)
+{
+ return testActivation(false);
+}
+
+
static int
mymain(void)
{
TESTS_PM_SUPPORT_HELPER("canHibernate", &virSystemdCanHibernate);
TESTS_PM_SUPPORT_HELPER("canHybridSleep", &virSystemdCanHybridSleep);
+ if (virTestRun("Test activation empty", testActivationEmpty, NULL) < 0)
+ ret = -1;
+ if (virTestRun("Test activation names", testActivationFDNames, NULL) < 0)
+ ret = -1;
+ if (virTestRun("Test activation addrs", testActivationFDAddrs, NULL) < 0)
+ ret = -1;
+
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}