/*
* virsystemd.c: helpers for using systemd APIs
*
* Copyright (C) 2013, 2014 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* .
*
*/
#include
#define LIBVIRT_VIRSYSTEMDPRIV_H_ALLOW
#include "virsystemdpriv.h"
#include "virsystemd.h"
#include "virbuffer.h"
#include "virgdbus.h"
#include "virstring.h"
#include "viralloc.h"
#include "virutil.h"
#include "virlog.h"
#include "virerror.h"
#include "virfile.h"
#include "virhash.h"
#include "virsocketaddr.h"
#define VIR_FROM_THIS VIR_FROM_SYSTEMD
VIR_LOG_INIT("util.systemd");
#ifndef MSG_NOSIGNAL
# define MSG_NOSIGNAL 0
#endif
struct _virSystemdActivation {
GHashTable *fds;
};
typedef struct _virSystemdActivationEntry virSystemdActivationEntry;
struct _virSystemdActivationEntry {
int *fds;
size_t nfds;
};
static void virSystemdEscapeName(virBuffer *buf,
const char *name)
{
static const char hextable[16] = "0123456789abcdef";
#define ESCAPE(c) \
do { \
virBufferAddChar(buf, '\\'); \
virBufferAddChar(buf, 'x'); \
virBufferAddChar(buf, hextable[(c >> 4) & 15]); \
virBufferAddChar(buf, hextable[c & 15]); \
} while (0)
#define VALID_CHARS \
"0123456789" \
"abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
":-_.\\"
if (*name == '.') {
ESCAPE(*name);
name++;
}
while (*name) {
if (*name == '/')
virBufferAddChar(buf, '-');
else if (*name == '-' ||
*name == '\\' ||
!strchr(VALID_CHARS, *name))
ESCAPE(*name);
else
virBufferAddChar(buf, *name);
name++;
}
#undef ESCAPE
#undef VALID_CHARS
}
char *virSystemdMakeScopeName(const char *name,
const char *drivername,
bool legacy_behaviour)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAddLit(&buf, "machine-");
if (legacy_behaviour) {
virSystemdEscapeName(&buf, drivername);
virBufferAddLit(&buf, "\\x2d");
}
virSystemdEscapeName(&buf, name);
virBufferAddLit(&buf, ".scope");
return virBufferContentAndReset(&buf);
}
char *virSystemdMakeSliceName(const char *partition)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
if (*partition == '/')
partition++;
virSystemdEscapeName(&buf, partition);
virBufferAddLit(&buf, ".slice");
return virBufferContentAndReset(&buf);
}
static int virSystemdHasMachinedCachedValue = -1;
static int virSystemdHasLogindCachedValue = -1;
/* Reset the cache from tests for testing the underlying dbus calls
* as well */
void virSystemdHasMachinedResetCachedValue(void)
{
virSystemdHasMachinedCachedValue = -1;
}
void virSystemdHasLogindResetCachedValue(void)
{
virSystemdHasLogindCachedValue = -1;
}
/* -2 = machine1 is not supported on this machine
* -1 = error
* 0 = machine1 is available
*/
int
virSystemdHasMachined(void)
{
int ret;
int val;
val = g_atomic_int_get(&virSystemdHasMachinedCachedValue);
if (val != -1)
return val;
if ((ret = virGDBusIsServiceEnabled("org.freedesktop.machine1")) < 0) {
if (ret == -2)
g_atomic_int_set(&virSystemdHasMachinedCachedValue, -2);
return ret;
}
if ((ret = virGDBusIsServiceRegistered("org.freedesktop.systemd1")) == -1)
return ret;
g_atomic_int_set(&virSystemdHasMachinedCachedValue, ret);
return ret;
}
int
virSystemdHasLogind(void)
{
int ret;
int val;
val = g_atomic_int_get(&virSystemdHasLogindCachedValue);
if (val != -1)
return val;
ret = virGDBusIsServiceEnabled("org.freedesktop.login1");
if (ret < 0) {
if (ret == -2)
g_atomic_int_set(&virSystemdHasLogindCachedValue, -2);
return ret;
}
if ((ret = virGDBusIsServiceRegistered("org.freedesktop.login1")) == -1)
return ret;
g_atomic_int_set(&virSystemdHasLogindCachedValue, ret);
return ret;
}
/**
* virSystemdGetMachineByPID:
* @conn: dbus connection
* @pid: pid of running VM
*
* Returns dbus object path to VM registered with machined.
* On error returns NULL.
*/
static char *
virSystemdGetMachineByPID(GDBusConnection *conn,
pid_t pid)
{
g_autoptr(GVariant) message = NULL;
g_autoptr(GVariant) reply = NULL;
char *object = NULL;
message = g_variant_new("(u)", pid);
if (virGDBusCallMethod(conn,
&reply,
G_VARIANT_TYPE("(o)"),
NULL,
"org.freedesktop.machine1",
"/org/freedesktop/machine1",
"org.freedesktop.machine1.Manager",
"GetMachineByPID",
message) < 0)
return NULL;
g_variant_get(reply, "(o)", &object);
VIR_DEBUG("Domain with pid %lld has object path '%s'",
(long long) pid, object);
return object;
}
char *
virSystemdGetMachineNameByPID(pid_t pid)
{
GDBusConnection *conn;
g_autoptr(GVariant) message = NULL;
g_autoptr(GVariant) reply = NULL;
g_autoptr(GVariant) gvar = NULL;
g_autofree char *object = NULL;
char *name = NULL;
if (virSystemdHasMachined() < 0)
return NULL;
if (!(conn = virGDBusGetSystemBus()))
return NULL;
object = virSystemdGetMachineByPID(conn, pid);
if (!object)
return NULL;
message = g_variant_new("(ss)",
"org.freedesktop.machine1.Machine", "Name");
if (virGDBusCallMethod(conn,
&reply,
G_VARIANT_TYPE("(v)"),
NULL,
"org.freedesktop.machine1",
object,
"org.freedesktop.DBus.Properties",
"Get",
message) < 0)
return NULL;
g_variant_get(reply, "(v)", &gvar);
g_variant_get(gvar, "s", &name);
VIR_DEBUG("Domain with pid %lld has machine name '%s'",
(long long) pid, name);
return name;
}
/**
* virSystemdGetMachineUnitByPID:
* @pid: pid of running VM
*
* Returns systemd Unit name of a running VM registered with machined.
* On error returns NULL.
*/
char *
virSystemdGetMachineUnitByPID(pid_t pid)
{
GDBusConnection *conn;
g_autoptr(GVariant) message = NULL;
g_autoptr(GVariant) reply = NULL;
g_autoptr(GVariant) gvar = NULL;
g_autofree char *object = NULL;
char *unit = NULL;
if (virSystemdHasMachined() < 0)
return NULL;
if (!(conn = virGDBusGetSystemBus()))
return NULL;
object = virSystemdGetMachineByPID(conn, pid);
if (!object)
return NULL;
message = g_variant_new("(ss)",
"org.freedesktop.machine1.Machine", "Unit");
if (virGDBusCallMethod(conn,
&reply,
G_VARIANT_TYPE("(v)"),
NULL,
"org.freedesktop.machine1",
object,
"org.freedesktop.DBus.Properties",
"Get",
message) < 0)
return NULL;
g_variant_get(reply, "(v)", &gvar);
g_variant_get(gvar, "s", &unit);
VIR_DEBUG("Domain with pid %lld has unit name '%s'",
(long long) pid, unit);
return unit;
}
/**
* virSystemdCreateMachine:
* @name: driver unique name of the machine
* @drivername: name of the virt driver
* @privileged: whether driver is running privileged or per user
* @uuid: globally unique UUID of the machine
* @rootdir: root directory of machine filesystem
* @pidleader: PID of the leader process
* @iscontainer: true if a container, false if a VM
* @nnicindexes: number of network interface indexes in list
* @nicindexes: list of network interface indexes
* @partition: name of the slice to place the machine in
*
* Returns 0 on success, -1 on fatal error, or -2 if systemd-machine is not available
*/
int 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)
{
int rc;
GDBusConnection *conn;
GVariant *guuid;
GVariant *gnicindexes;
GVariant *gprops;
GVariant *message;
g_autofree char *creatorname = NULL;
g_autofree char *slicename = NULL;
g_autofree char *scopename = NULL;
static int hasCreateWithNetwork = 1;
if ((rc = virSystemdHasMachined()) < 0)
return rc;
if (!(conn = virGDBusGetSystemBus()))
return -1;
creatorname = g_strdup_printf("libvirt-%s", drivername);
if (partition) {
if (!(slicename = virSystemdMakeSliceName(partition)))
return -1;
} else {
slicename = g_strdup("");
}
/*
* The systemd DBus APIs we're invoking have the
* following signature(s)
*
* CreateMachineWithNetwork(in s name,
* in ay id,
* in s service,
* in s class,
* in u leader,
* in s root_directory,
* in ai nicindexes
* in a(sv) scope_properties,
* out o path);
*
* CreateMachine(in s name,
* in ay id,
* in s service,
* in s class,
* in u leader,
* in s root_directory,
* in a(sv) scope_properties,
* out o path);
*
* @name a host unique name for the machine. shows up
* in 'ps' listing & similar
*
* @id: a UUID of the machine, ideally matching /etc/machine-id
* for containers
*
* @service: identifier of the client ie "libvirt-lxc"
*
* @class: either the string "container" or "vm" depending
* on the type of machine
*
* @leader: main PID of the machine, either the host emulator
* process, or the 'init' PID of the container
*
* @root_directory: the root directory of the container, if
* this is known & visible in the host filesystem, or empty string
*
* @nicindexes: list of network interface indexes for the
* host end of the VETH device pairs.
*
* @scope_properties:an array (not a dict!) of properties that are
* passed on to PID 1 when creating a scope unit for your machine.
* Will allow initial settings for the cgroup & similar.
*
* @path: a bus path returned for the machine object created, to
* allow further API calls to be made against the object.
*
*/
VIR_DEBUG("Attempting to create machine via systemd");
if (g_atomic_int_get(&hasCreateWithNetwork)) {
g_autoptr(virError) error = NULL;
error = g_new0(virError, 1);
guuid = g_variant_new_fixed_array(G_VARIANT_TYPE("y"),
uuid, 16, sizeof(unsigned char));
gnicindexes = g_variant_new_fixed_array(G_VARIANT_TYPE("i"),
nicindexes, nnicindexes, sizeof(int));
gprops = g_variant_new_parsed("[('Slice', <%s>),"
" ('After', <['libvirtd.service']>),"
" ('Before', <['virt-guest-shutdown.target']>)]",
slicename);
message = g_variant_new("(s@ayssus@ai@a(sv))",
name,
guuid,
creatorname,
iscontainer ? "container" : "vm",
(unsigned int)pidleader,
NULLSTR_EMPTY(rootdir),
gnicindexes,
gprops);
rc = virGDBusCallMethod(conn,
NULL,
NULL,
error,
"org.freedesktop.machine1",
"/org/freedesktop/machine1",
"org.freedesktop.machine1.Manager",
"CreateMachineWithNetwork",
message);
g_variant_unref(message);
if (rc < 0)
return -1;
if (error->level == VIR_ERR_ERROR) {
if (virGDBusErrorIsUnknownMethod(error)) {
VIR_INFO("CreateMachineWithNetwork isn't supported, switching "
"to legacy CreateMachine method for systemd-machined");
virResetError(error);
g_atomic_int_set(&hasCreateWithNetwork, 0);
/* Could re-structure without Using goto, but this
* avoids another atomic read which would trigger
* another memory barrier */
goto fallback;
}
virReportErrorObject(error);
virResetError(error);
return -1;
}
} else {
fallback:
guuid = g_variant_new_fixed_array(G_VARIANT_TYPE("y"),
uuid, 16, sizeof(unsigned char));
gprops = g_variant_new_parsed("[('Slice', <%s>),"
" ('After', <['libvirtd.service']>),"
" ('Before', <['virt-guest-shutdown.target']>)]",
slicename);
message = g_variant_new("(s@ayssus@a(sv))",
name,
guuid,
creatorname,
iscontainer ? "container" : "vm",
(unsigned int)pidleader,
NULLSTR_EMPTY(rootdir),
gprops);
rc = virGDBusCallMethod(conn,
NULL,
NULL,
NULL,
"org.freedesktop.machine1",
"/org/freedesktop/machine1",
"org.freedesktop.machine1.Manager",
"CreateMachine",
message);
g_variant_unref(message);
if (rc < 0)
return -1;
}
if (maxthreads > 0) {
uint64_t max = maxthreads;
if (!(scopename = virSystemdMakeScopeName(name, drivername, false)))
return -1;
gprops = g_variant_new_parsed("[('TasksMax', <%t>)]", max);
message = g_variant_new("(sb@a(sv))",
scopename,
true,
gprops);
rc = virGDBusCallMethod(conn,
NULL,
NULL,
NULL,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"SetUnitProperties",
message);
g_variant_unref(message);
if (rc < 0)
return -1;
}
return 0;
}
int virSystemdTerminateMachine(const char *name)
{
int rc;
GDBusConnection *conn;
g_autoptr(GVariant) message = NULL;
g_autoptr(virError) error = NULL;
if (!name)
return 0;
if ((rc = virSystemdHasMachined()) < 0)
return rc;
if (!(conn = virGDBusGetSystemBus()))
return -1;
error = g_new0(virError, 1);
/*
* The systemd DBus API we're invoking has the
* following signature
*
* TerminateMachine(in s name);
*
* @name a host unique name for the machine. shows up
* in 'ps' listing & similar
*/
message = g_variant_new("(s)", name);
VIR_DEBUG("Attempting to terminate machine via systemd");
if (virGDBusCallMethod(conn,
NULL,
NULL,
error,
"org.freedesktop.machine1",
"/org/freedesktop/machine1",
"org.freedesktop.machine1.Manager",
"TerminateMachine",
message) < 0)
return -1;
if (error->level == VIR_ERR_ERROR &&
STRNEQ_NULLABLE("org.freedesktop.machine1.NoSuchMachine",
error->str1)) {
virReportErrorObject(error);
return -1;
}
return 0;
}
void
virSystemdNotifyStartup(void)
{
#ifndef WIN32
const char *path;
const char *msg = "READY=1";
int fd;
struct sockaddr_un un = {
.sun_family = AF_UNIX,
};
struct iovec iov = {
.iov_base = (char *)msg,
.iov_len = strlen(msg),
};
struct msghdr mh = {
.msg_name = &un,
.msg_iov = &iov,
.msg_iovlen = 1,
};
if (!(path = getenv("NOTIFY_SOCKET"))) {
VIR_DEBUG("Skipping systemd notify, not requested");
return;
}
/* NB sun_path field is *not* NUL-terminated, hence >, not >= */
if (strlen(path) > sizeof(un.sun_path)) {
VIR_WARN("Systemd notify socket path '%s' too long", path);
return;
}
memcpy(un.sun_path, path, strlen(path));
if (un.sun_path[0] == '@')
un.sun_path[0] = '\0';
mh.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(path);
fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (fd < 0) {
VIR_WARN("Unable to create socket FD");
return;
}
if (sendmsg(fd, &mh, MSG_NOSIGNAL) < 0)
VIR_WARN("Failed to notify systemd");
VIR_FORCE_CLOSE(fd);
#endif /* !WIN32 */
}
static int
virSystemdPMSupportTarget(const char *methodName, bool *result)
{
int rc;
GDBusConnection *conn;
g_autoptr(GVariant) reply = NULL;
char *response;
if ((rc = virSystemdHasLogind()) < 0)
return rc;
if (!(conn = virGDBusGetSystemBus()))
return -1;
if (virGDBusCallMethod(conn,
&reply,
G_VARIANT_TYPE("(s)"),
NULL,
"org.freedesktop.login1",
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
methodName,
NULL) < 0)
return -1;
g_variant_get(reply, "(&s)", &response);
*result = STREQ("yes", response) || STREQ("challenge", response);
return 0;
}
int virSystemdCanSuspend(bool *result)
{
return virSystemdPMSupportTarget("CanSuspend", result);
}
int virSystemdCanHibernate(bool *result)
{
return virSystemdPMSupportTarget("CanHibernate", result);
}
int virSystemdCanHybridSleep(bool *result)
{
return virSystemdPMSupportTarget("CanHybridSleep", result);
}
static void
virSystemdActivationEntryFree(void *data)
{
virSystemdActivationEntry *ent = data;
size_t i;
VIR_DEBUG("Closing activation FDs");
for (i = 0; i < ent->nfds; i++) {
VIR_DEBUG("Closing activation FD %d", ent->fds[i]);
VIR_FORCE_CLOSE(ent->fds[i]);
}
g_free(ent->fds);
g_free(ent);
}
static int
virSystemdActivationAddFD(virSystemdActivation *act,
const char *name,
int fd)
{
virSystemdActivationEntry *ent = virHashLookup(act->fds, name);
if (!ent) {
ent = g_new0(virSystemdActivationEntry, 1);
ent->fds = g_new0(int, 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);
return -1;
}
return 0;
}
VIR_EXPAND_N(ent->fds, ent->nfds, 1);
VIR_DEBUG("Record extra FD %d with name %s", fd, name);
ent->fds[ent->nfds - 1] = fd;
return 0;
}
static int
virSystemdActivationInitFromNames(virSystemdActivation *act,
int nfds,
const char *fdnames)
{
g_auto(GStrv) fdnamelistptr = NULL;
char **fdnamelist;
size_t i;
int nextfd = STDERR_FILENO + 1;
VIR_DEBUG("FD names %s", fdnames);
if (!(fdnamelistptr = g_strsplit(fdnames, ":", 0)))
goto error;
if (g_strv_length(fdnamelistptr) != nfds) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Expecting %d FD names but got %u"),
nfds, g_strv_length(fdnamelistptr));
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(virSystemdActivation *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;
}
VIR_DEBUG("Got socket family %d for FD %d",
addr.data.sa.sa_family, nextfd);
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) {
VIR_DEBUG("Expect %d got %d",
map[i].port, ntohs(addr.data.inet4.sin_port));
if (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) {
VIR_DEBUG("Expect %d got %d",
map[i].port, ntohs(addr.data.inet6.sin6_port));
if (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) {
VIR_DEBUG("Expect %s got %s", map[i].path, addr.data.un.sun_path);
if (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;
}
#ifndef WIN32
/**
* virSystemdGetListenFDs:
*
* Parse LISTEN_PID and LISTEN_FDS passed from caller.
*
* Returns number of passed FDs.
*/
static unsigned int
virSystemdGetListenFDs(void)
{
const char *pidstr;
const char *fdstr;
size_t i = 0;
unsigned long long procid;
unsigned int nfds;
VIR_DEBUG("Setting up networking from caller");
if (!(pidstr = getenv("LISTEN_PID"))) {
VIR_DEBUG("No LISTEN_PID from caller");
return 0;
}
if (virStrToLong_ull(pidstr, NULL, 10, &procid) < 0) {
VIR_DEBUG("Malformed LISTEN_PID from caller %s", pidstr);
return 0;
}
if ((pid_t)procid != getpid()) {
VIR_DEBUG("LISTEN_PID %s is not for us %lld",
pidstr, (long long) getpid());
return 0;
}
if (!(fdstr = getenv("LISTEN_FDS"))) {
VIR_DEBUG("No LISTEN_FDS from caller");
return 0;
}
if (virStrToLong_ui(fdstr, NULL, 10, &nfds) < 0) {
VIR_DEBUG("Malformed LISTEN_FDS from caller %s", fdstr);
return 0;
}
g_unsetenv("LISTEN_PID");
g_unsetenv("LISTEN_FDS");
VIR_DEBUG("Got %u file descriptors", nfds);
for (i = 0; i < nfds; i++) {
int fd = STDERR_FILENO + i + 1;
VIR_DEBUG("Disabling inheritance of passed FD %d", fd);
if (virSetInherit(fd, false) < 0)
VIR_WARN("Couldn't disable inheritance of passed FD %d", fd);
}
return nfds;
}
#else /* WIN32 */
static unsigned int
virSystemdGetListenFDs(void)
{
return 0;
}
#endif /* WIN32 */
static virSystemdActivation *
virSystemdActivationNew(virSystemdActivationMap *map,
size_t nmap,
int nfds)
{
virSystemdActivation *act;
const char *fdnames;
VIR_DEBUG("Activated with %d FDs", nfds);
act = g_new0(virSystemdActivation, 1);
if (!(act->fds = virHashNew(virSystemdActivationEntryFree)))
goto error;
fdnames = getenv("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 service 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,
virSystemdActivation **act)
{
int nfds = 0;
if ((nfds = virSystemdGetListenFDs()) < 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(virSystemdActivation *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(virSystemdActivation *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(virSystemdActivation *act,
const char *name,
int **fds,
size_t *nfds)
{
virSystemdActivationEntry *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(virSystemdActivation *act)
{
if (!act)
return;
virHashFree(act->fds);
g_free(act);
}