Skip to content

Commit 26de229

Browse files
stefanhaRHmdroth
authored andcommitted
qga: add systemd socket activation support
AF_UNIX and AF_VSOCK listen sockets can be passed in by systemd on startup. This allows systemd to manage the listen socket until the first client connects and between restarts. Advantages of socket activation are that parallel startup of network services becomes possible and that unused daemons do not consume memory. The key to achieving this is the LISTEN_FDS environment variable, which is a stable ABI as shown here: https://www.freedesktop.org/wiki/Software/systemd/InterfacePortabilityAndStabilityChart/ We could link against libsystemd and use sd_listen_fds(3) but it's easy to implement the tiny LISTEN_FDS ABI so that qemu-ga does not depend on libsystemd. Some systems may not have systemd installed and wish to avoid the dependency. Other init systems or socket activation servers may implement the same ABI without systemd involvement. Test as follows: $ cat ~/.config/systemd/user/qga.service [Unit] Description=qga [Service] WorkingDirectory=/tmp ExecStart=/path/to/qemu-ga --logfile=/tmp/qga.log --pidfile=/tmp/qga.pid --statedir=/tmp $ cat ~/.config/systemd/user/qga.socket [Socket] ListenStream=/tmp/qga.sock [Install] WantedBy=default.target $ systemctl --user daemon-reload $ systemctl --user start qga.socket $ nc -U /tmp/qga.sock Signed-off-by: Stefan Hajnoczi <[email protected]> Reviewed-by: Daniel P. Berrange <[email protected]> Signed-off-by: Michael Roth <[email protected]>
1 parent 17783ac commit 26de229

File tree

4 files changed

+110
-37
lines changed

4 files changed

+110
-37
lines changed

qga/channel-posix.c

+38-30
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,16 @@ static int ga_channel_client_add(GAChannel *c, int fd)
118118
return 0;
119119
}
120120

121-
static gboolean ga_channel_open(GAChannel *c, const gchar *path, GAChannelMethod method)
121+
static gboolean ga_channel_open(GAChannel *c, const gchar *path,
122+
GAChannelMethod method, int fd)
122123
{
123124
int ret;
124125
c->method = method;
125126

126127
switch (c->method) {
127128
case GA_CHANNEL_VIRTIO_SERIAL: {
128-
int fd = qemu_open(path, O_RDWR | O_NONBLOCK
129+
assert(fd < 0);
130+
fd = qemu_open(path, O_RDWR | O_NONBLOCK
129131
#ifndef CONFIG_SOLARIS
130132
| O_ASYNC
131133
#endif
@@ -153,7 +155,9 @@ static gboolean ga_channel_open(GAChannel *c, const gchar *path, GAChannelMethod
153155
}
154156
case GA_CHANNEL_ISA_SERIAL: {
155157
struct termios tio;
156-
int fd = qemu_open(path, O_RDWR | O_NOCTTY | O_NONBLOCK);
158+
159+
assert(fd < 0);
160+
fd = qemu_open(path, O_RDWR | O_NOCTTY | O_NONBLOCK);
157161
if (fd == -1) {
158162
g_critical("error opening channel: %s", strerror(errno));
159163
return false;
@@ -183,37 +187,41 @@ static gboolean ga_channel_open(GAChannel *c, const gchar *path, GAChannelMethod
183187
break;
184188
}
185189
case GA_CHANNEL_UNIX_LISTEN: {
186-
Error *local_err = NULL;
187-
int fd = unix_listen(path, NULL, strlen(path), &local_err);
188-
if (local_err != NULL) {
189-
g_critical("%s", error_get_pretty(local_err));
190-
error_free(local_err);
191-
return false;
190+
if (fd < 0) {
191+
Error *local_err = NULL;
192+
193+
fd = unix_listen(path, NULL, strlen(path), &local_err);
194+
if (local_err != NULL) {
195+
g_critical("%s", error_get_pretty(local_err));
196+
error_free(local_err);
197+
return false;
198+
}
192199
}
193200
ga_channel_listen_add(c, fd, true);
194201
break;
195202
}
196203
case GA_CHANNEL_VSOCK_LISTEN: {
197-
Error *local_err = NULL;
198-
SocketAddress *addr;
199-
char *addr_str;
200-
int fd;
201-
202-
addr_str = g_strdup_printf("vsock:%s", path);
203-
addr = socket_parse(addr_str, &local_err);
204-
g_free(addr_str);
205-
if (local_err != NULL) {
206-
g_critical("%s", error_get_pretty(local_err));
207-
error_free(local_err);
208-
return false;
209-
}
204+
if (fd < 0) {
205+
Error *local_err = NULL;
206+
SocketAddress *addr;
207+
char *addr_str;
210208

211-
fd = socket_listen(addr, &local_err);
212-
qapi_free_SocketAddress(addr);
213-
if (local_err != NULL) {
214-
g_critical("%s", error_get_pretty(local_err));
215-
error_free(local_err);
216-
return false;
209+
addr_str = g_strdup_printf("vsock:%s", path);
210+
addr = socket_parse(addr_str, &local_err);
211+
g_free(addr_str);
212+
if (local_err != NULL) {
213+
g_critical("%s", error_get_pretty(local_err));
214+
error_free(local_err);
215+
return false;
216+
}
217+
218+
fd = socket_listen(addr, &local_err);
219+
qapi_free_SocketAddress(addr);
220+
if (local_err != NULL) {
221+
g_critical("%s", error_get_pretty(local_err));
222+
error_free(local_err);
223+
return false;
224+
}
217225
}
218226
ga_channel_listen_add(c, fd, true);
219227
break;
@@ -262,13 +270,13 @@ GIOStatus ga_channel_read(GAChannel *c, gchar *buf, gsize size, gsize *count)
262270
}
263271

264272
GAChannel *ga_channel_new(GAChannelMethod method, const gchar *path,
265-
GAChannelCallback cb, gpointer opaque)
273+
int listen_fd, GAChannelCallback cb, gpointer opaque)
266274
{
267275
GAChannel *c = g_new0(GAChannel, 1);
268276
c->event_cb = cb;
269277
c->user_data = opaque;
270278

271-
if (!ga_channel_open(c, path, method)) {
279+
if (!ga_channel_open(c, path, method, listen_fd)) {
272280
g_critical("error opening channel");
273281
ga_channel_free(c);
274282
return NULL;

qga/channel-win32.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ static gboolean ga_channel_open(GAChannel *c, GAChannelMethod method,
316316
}
317317

318318
GAChannel *ga_channel_new(GAChannelMethod method, const gchar *path,
319-
GAChannelCallback cb, gpointer opaque)
319+
int listen_fd, GAChannelCallback cb, gpointer opaque)
320320
{
321321
GAChannel *c = g_new0(GAChannel, 1);
322322
SECURITY_ATTRIBUTES sec_attrs;

qga/channel.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ typedef enum {
2525
typedef gboolean (*GAChannelCallback)(GIOCondition condition, gpointer opaque);
2626

2727
GAChannel *ga_channel_new(GAChannelMethod method, const gchar *path,
28-
GAChannelCallback cb, gpointer opaque);
28+
int listen_fd, GAChannelCallback cb,
29+
gpointer opaque);
2930
void ga_channel_free(GAChannel *c);
3031
GIOStatus ga_channel_read(GAChannel *c, gchar *buf, gsize size, gsize *count);
3132
GIOStatus ga_channel_write_all(GAChannel *c, const gchar *buf, gsize size);

qga/main.c

+69-5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "qga/channel.h"
2929
#include "qemu/bswap.h"
3030
#include "qemu/help_option.h"
31+
#include "qemu/sockets.h"
3132
#ifdef _WIN32
3233
#include "qga/service-win32.h"
3334
#include "qga/vss-win32.h"
@@ -184,6 +185,37 @@ void reopen_fd_to_null(int fd)
184185
}
185186
#endif
186187

188+
/**
189+
* get_listen_fd:
190+
* @consume: true to prevent future calls from succeeding
191+
*
192+
* Fetch a listen file descriptor that was passed via systemd socket
193+
* activation. Use @consume to prevent child processes from thinking a file
194+
* descriptor was passed.
195+
*
196+
* Returns: file descriptor or -1 if no fd was passed
197+
*/
198+
static int get_listen_fd(bool consume)
199+
{
200+
#ifdef _WIN32
201+
return -1; /* no fd passing expected, unsetenv(3) not available */
202+
#else
203+
const char *listen_fds = getenv("LISTEN_FDS");
204+
int fd = STDERR_FILENO + 1;
205+
206+
if (!listen_fds || strcmp(listen_fds, "1") != 0) {
207+
return -1;
208+
}
209+
210+
if (consume) {
211+
unsetenv("LISTEN_FDS");
212+
}
213+
214+
qemu_set_cloexec(fd);
215+
return fd;
216+
#endif /* !_WIN32 */
217+
}
218+
187219
static void usage(const char *cmd)
188220
{
189221
printf(
@@ -648,7 +680,8 @@ static gboolean channel_event_cb(GIOCondition condition, gpointer data)
648680
return true;
649681
}
650682

651-
static gboolean channel_init(GAState *s, const gchar *method, const gchar *path)
683+
static gboolean channel_init(GAState *s, const gchar *method, const gchar *path,
684+
int listen_fd)
652685
{
653686
GAChannelMethod channel_method;
654687

@@ -666,7 +699,8 @@ static gboolean channel_init(GAState *s, const gchar *method, const gchar *path)
666699
return false;
667700
}
668701

669-
s->channel = ga_channel_new(channel_method, path, channel_event_cb, s);
702+
s->channel = ga_channel_new(channel_method, path, listen_fd,
703+
channel_event_cb, s);
670704
if (!s->channel) {
671705
g_critical("failed to create guest agent channel");
672706
return false;
@@ -1025,7 +1059,9 @@ static void config_dump(GAConfig *config)
10251059

10261060
g_key_file_set_boolean(keyfile, "general", "daemon", config->daemonize);
10271061
g_key_file_set_string(keyfile, "general", "method", config->method);
1028-
g_key_file_set_string(keyfile, "general", "path", config->channel_path);
1062+
if (config->channel_path) {
1063+
g_key_file_set_string(keyfile, "general", "path", config->channel_path);
1064+
}
10291065
if (config->log_filepath) {
10301066
g_key_file_set_string(keyfile, "general", "logfile",
10311067
config->log_filepath);
@@ -1294,7 +1330,9 @@ static int run_agent(GAState *s, GAConfig *config)
12941330
#endif
12951331

12961332
s->main_loop = g_main_loop_new(NULL, false);
1297-
if (!channel_init(ga_state, config->method, config->channel_path)) {
1333+
1334+
if (!channel_init(ga_state, config->method, config->channel_path,
1335+
get_listen_fd(true))) {
12981336
g_critical("failed to initialize guest agent channel");
12991337
return EXIT_FAILURE;
13001338
}
@@ -1318,6 +1356,7 @@ int main(int argc, char **argv)
13181356
int ret = EXIT_SUCCESS;
13191357
GAState *s = g_new0(GAState, 1);
13201358
GAConfig *config = g_new0(GAConfig, 1);
1359+
int listen_fd;
13211360

13221361
config->log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL;
13231362

@@ -1339,7 +1378,32 @@ int main(int argc, char **argv)
13391378
config->method = g_strdup("virtio-serial");
13401379
}
13411380

1342-
if (config->channel_path == NULL) {
1381+
listen_fd = get_listen_fd(false);
1382+
if (listen_fd >= 0) {
1383+
SocketAddress *addr;
1384+
1385+
g_free(config->method);
1386+
g_free(config->channel_path);
1387+
config->method = NULL;
1388+
config->channel_path = NULL;
1389+
1390+
addr = socket_local_address(listen_fd, NULL);
1391+
if (addr) {
1392+
if (addr->type == SOCKET_ADDRESS_KIND_UNIX) {
1393+
config->method = g_strdup("unix-listen");
1394+
} else if (addr->type == SOCKET_ADDRESS_KIND_VSOCK) {
1395+
config->method = g_strdup("vsock-listen");
1396+
}
1397+
1398+
qapi_free_SocketAddress(addr);
1399+
}
1400+
1401+
if (!config->method) {
1402+
g_critical("unsupported listen fd type");
1403+
ret = EXIT_FAILURE;
1404+
goto end;
1405+
}
1406+
} else if (config->channel_path == NULL) {
13431407
if (strcmp(config->method, "virtio-serial") == 0) {
13441408
/* try the default path for the virtio-serial port */
13451409
config->channel_path = g_strdup(QGA_VIRTIO_PATH_DEFAULT);

0 commit comments

Comments
 (0)