diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 02d5b7acce..a19ba1d798 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3086,10 +3086,15 @@ virSysinfoReadS390; # util/virsystemd.h +virSystemdActivationClaimFDs; +virSystemdActivationComplete; +virSystemdActivationFree; +virSystemdActivationHasName; virSystemdCanHibernate; virSystemdCanHybridSleep; virSystemdCanSuspend; virSystemdCreateMachine; +virSystemdGetActivation; virSystemdGetMachineNameByPID; virSystemdHasMachinedResetCachedValue; virSystemdMakeScopeName; diff --git a/src/util/virsystemd.c b/src/util/virsystemd.c index 3f03e3bd63..ae8401343d 100644 --- a/src/util/virsystemd.c +++ b/src/util/virsystemd.c @@ -39,6 +39,8 @@ #include "virlog.h" #include "virerror.h" #include "virfile.h" +#include "virhash.h" +#include "virsocketaddr.h" #define VIR_FROM_THIS VIR_FROM_SYSTEMD @@ -48,6 +50,18 @@ VIR_LOG_INIT("util.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) { @@ -561,3 +575,351 @@ int virSystemdCanHybridSleep(bool *result) { 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); +} diff --git a/src/util/virsystemd.h b/src/util/virsystemd.h index db4ecbff60..5d56c78835 100644 --- a/src/util/virsystemd.h +++ b/src/util/virsystemd.h @@ -23,6 +23,20 @@ #include "internal.h" +typedef struct _virSystemdActivation virSystemdActivation; +typedef virSystemdActivation *virSystemdActivationPtr; + +/* + * Back compat for systemd < v227 which lacks LISTEN_FDNAMES. + * Delete when min systemd is increased ie RHEL7 dropped + */ +typedef struct _virSystemdActivationMap { + const char *name; + int family; + int port; /* if family == AF_INET/AF_INET6 */ + const char *path; /* if family == AF_UNIX */ +} virSystemdActivationMap; + char *virSystemdMakeScopeName(const char *name, const char *drivername, bool legacy_behaviour); @@ -49,3 +63,21 @@ int virSystemdCanHibernate(bool *result); int virSystemdCanHybridSleep(bool *result); char *virSystemdGetMachineNameByPID(pid_t pid); + +int virSystemdGetActivation(virSystemdActivationMap *map, + size_t nmap, + virSystemdActivationPtr *act); + +bool virSystemdActivationHasName(virSystemdActivationPtr act, + const char *name); + +int virSystemdActivationComplete(virSystemdActivationPtr act); + +void virSystemdActivationClaimFDs(virSystemdActivationPtr act, + const char *name, + int **fds, + size_t *nfds); + +void virSystemdActivationFree(virSystemdActivationPtr *act); + +#define virSystemdActivationAutoPtrFree virSystemdActivationFree diff --git a/tests/virsystemdtest.c b/tests/virsystemdtest.c index 82c02decd1..586c512fca 100644 --- a/tests/virsystemdtest.c +++ b/tests/virsystemdtest.c @@ -31,6 +31,8 @@ # 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"); @@ -507,6 +509,166 @@ static int testPMSupportSystemdNotRunning(const void *opaque) 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) { @@ -598,6 +760,13 @@ 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; }