libvirt/src/util/virsystemd.c

1115 lines
30 KiB
C
Raw Normal View History

/*
* 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
* <http://www.gnu.org/licenses/>.
*
*/
#include <config.h>
#define LIBVIRT_VIRSYSTEMDPRIV_H_ALLOW
#include "virsystemdpriv.h"
#include "virsystemd.h"
systemd: Modernize machine naming So, systemd-machined has this philosophy that machine names are like hostnames and hence should follow the same rules. But we always allowed international characters in domain names. Thus we need to modify the machine name we are passing to systemd. In order to change some machine names that we will be passing to systemd, we also need to call TerminateMachine at the end of a lifetime of a domain. Even for domains that were started with older libvirt. That can be achieved thanks to virSystemdGetMachineNameByPID(). And because we can change machine names, we can get rid of the inconsistent and pointless escaping of domain names when creating machine names. So this patch modifies the naming in the following way. It creates the name as <drivername>-<id>-<name> where invalid hostname characters are stripped out of the name and if the resulting name is longer, it truncates it to 64 characters. That way we can start domains we couldn't start before. Well, at least on systemd. To make it work all together, the machineName (which is needed only with systemd) is saved in domain's private data. That way the generation is moved to the driver and we don't need to pass various unnecessary arguments to cgroup functions. The only thing this complicates a bit is the scope generation when validating a cgroup where we must check both old and new naming, so a slight modification was needed there. Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1282846 Signed-off-by: Martin Kletzander <mkletzan@redhat.com>
2016-02-01 15:50:54 +00:00
#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,
systemd: Modernize machine naming So, systemd-machined has this philosophy that machine names are like hostnames and hence should follow the same rules. But we always allowed international characters in domain names. Thus we need to modify the machine name we are passing to systemd. In order to change some machine names that we will be passing to systemd, we also need to call TerminateMachine at the end of a lifetime of a domain. Even for domains that were started with older libvirt. That can be achieved thanks to virSystemdGetMachineNameByPID(). And because we can change machine names, we can get rid of the inconsistent and pointless escaping of domain names when creating machine names. So this patch modifies the naming in the following way. It creates the name as <drivername>-<id>-<name> where invalid hostname characters are stripped out of the name and if the resulting name is longer, it truncates it to 64 characters. That way we can start domains we couldn't start before. Well, at least on systemd. To make it work all together, the machineName (which is needed only with systemd) is saved in domain's private data. That way the generation is moved to the driver and we don't need to pass various unnecessary arguments to cgroup functions. The only thing this complicates a bit is the scope generation when validating a cgroup where we must check both old and new naming, so a slight modification was needed there. Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1282846 Signed-off-by: Martin Kletzander <mkletzan@redhat.com>
2016-02-01 15:50:54 +00:00
const char *drivername,
bool legacy_behaviour)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAddLit(&buf, "machine-");
systemd: Modernize machine naming So, systemd-machined has this philosophy that machine names are like hostnames and hence should follow the same rules. But we always allowed international characters in domain names. Thus we need to modify the machine name we are passing to systemd. In order to change some machine names that we will be passing to systemd, we also need to call TerminateMachine at the end of a lifetime of a domain. Even for domains that were started with older libvirt. That can be achieved thanks to virSystemdGetMachineNameByPID(). And because we can change machine names, we can get rid of the inconsistent and pointless escaping of domain names when creating machine names. So this patch modifies the naming in the following way. It creates the name as <drivername>-<id>-<name> where invalid hostname characters are stripped out of the name and if the resulting name is longer, it truncates it to 64 characters. That way we can start domains we couldn't start before. Well, at least on systemd. To make it work all together, the machineName (which is needed only with systemd) is saved in domain's private data. That way the generation is moved to the driver and we don't need to pass various unnecessary arguments to cgroup functions. The only thing this complicates a bit is the scope generation when validating a cgroup where we must check both old and new naming, so a slight modification was needed there. Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1282846 Signed-off-by: Martin Kletzander <mkletzan@redhat.com>
2016-02-01 15:50:54 +00:00
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;
}
systemd: Modernize machine naming So, systemd-machined has this philosophy that machine names are like hostnames and hence should follow the same rules. But we always allowed international characters in domain names. Thus we need to modify the machine name we are passing to systemd. In order to change some machine names that we will be passing to systemd, we also need to call TerminateMachine at the end of a lifetime of a domain. Even for domains that were started with older libvirt. That can be achieved thanks to virSystemdGetMachineNameByPID(). And because we can change machine names, we can get rid of the inconsistent and pointless escaping of domain names when creating machine names. So this patch modifies the naming in the following way. It creates the name as <drivername>-<id>-<name> where invalid hostname characters are stripped out of the name and if the resulting name is longer, it truncates it to 64 characters. That way we can start domains we couldn't start before. Well, at least on systemd. To make it work all together, the machineName (which is needed only with systemd) is saved in domain's private data. That way the generation is moved to the driver and we don't need to pass various unnecessary arguments to cgroup functions. The only thing this complicates a bit is the scope generation when validating a cgroup where we must check both old and new naming, so a slight modification was needed there. Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1282846 Signed-off-by: Martin Kletzander <mkletzan@redhat.com>
2016-02-01 15:50:54 +00:00
int virSystemdTerminateMachine(const char *name)
{
int rc;
GDBusConnection *conn;
g_autoptr(GVariant) message = NULL;
g_autoptr(virError) error = NULL;
systemd: Modernize machine naming So, systemd-machined has this philosophy that machine names are like hostnames and hence should follow the same rules. But we always allowed international characters in domain names. Thus we need to modify the machine name we are passing to systemd. In order to change some machine names that we will be passing to systemd, we also need to call TerminateMachine at the end of a lifetime of a domain. Even for domains that were started with older libvirt. That can be achieved thanks to virSystemdGetMachineNameByPID(). And because we can change machine names, we can get rid of the inconsistent and pointless escaping of domain names when creating machine names. So this patch modifies the naming in the following way. It creates the name as <drivername>-<id>-<name> where invalid hostname characters are stripped out of the name and if the resulting name is longer, it truncates it to 64 characters. That way we can start domains we couldn't start before. Well, at least on systemd. To make it work all together, the machineName (which is needed only with systemd) is saved in domain's private data. That way the generation is moved to the driver and we don't need to pass various unnecessary arguments to cgroup functions. The only thing this complicates a bit is the scope generation when validating a cgroup where we must check both old and new naming, so a slight modification was needed there. Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1282846 Signed-off-by: Martin Kletzander <mkletzan@redhat.com>
2016-02-01 15:50:54 +00:00
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)
{
g_autoptr(virSystemdActivation) act = g_new0(virSystemdActivation, 1);
const char *fdnames;
VIR_DEBUG("Activated with %d FDs", nfds);
act->fds = virHashNew(virSystemdActivationEntryFree);
fdnames = getenv("LISTEN_FDNAMES");
if (fdnames) {
if (virSystemdActivationInitFromNames(act, nfds, fdnames) < 0)
return NULL;
} else {
if (virSystemdActivationInitFromMap(act, nfds, map, nmap) < 0)
return NULL;
}
VIR_DEBUG("Created activation object for %d FDs", nfds);
return g_steal_pointer(&act);
}
/**
* 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;
g_clear_pointer(&act->fds, g_hash_table_unref);
g_free(act);
}