mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-12-28 08:35:22 +00:00
c8238579fb
Historically, we declared pointer type to our types: typedef struct _virXXX virXXX; typedef virXXX *virXXXPtr; But usefulness of such declaration is questionable, at best. Unfortunately, we can't drop every such declaration - we have to carry some over, because they are part of public API (e.g. virDomainPtr). But for internal types - we can do drop them and use what every other C project uses 'virXXX *'. This change was generated by a very ugly shell script that generated sed script which was then called over each file in the repository. For the shell script refer to the cover letter: https://listman.redhat.com/archives/libvir-list/2021-March/msg00537.html Signed-off-by: Michal Privoznik <mprivozn@redhat.com> Reviewed-by: Peter Krempa <pkrempa@redhat.com>
1121 lines
30 KiB
C
1121 lines
30 KiB
C
/*
|
|
* 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"
|
|
#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);
|
|
}
|