root / qemu-ga.c @ 6ce2d77a
History | View | Annotate | Download (16.8 kB)
1 |
/*
|
---|---|
2 |
* QEMU Guest Agent
|
3 |
*
|
4 |
* Copyright IBM Corp. 2011
|
5 |
*
|
6 |
* Authors:
|
7 |
* Adam Litke <aglitke@linux.vnet.ibm.com>
|
8 |
* Michael Roth <mdroth@linux.vnet.ibm.com>
|
9 |
*
|
10 |
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
11 |
* See the COPYING file in the top-level directory.
|
12 |
*/
|
13 |
#include <stdlib.h> |
14 |
#include <stdio.h> |
15 |
#include <stdbool.h> |
16 |
#include <glib.h> |
17 |
#include <getopt.h> |
18 |
#ifndef _WIN32
|
19 |
#include <syslog.h> |
20 |
#endif
|
21 |
#include "json-streamer.h" |
22 |
#include "json-parser.h" |
23 |
#include "qint.h" |
24 |
#include "qjson.h" |
25 |
#include "qga/guest-agent-core.h" |
26 |
#include "module.h" |
27 |
#include "signal.h" |
28 |
#include "qerror.h" |
29 |
#include "error_int.h" |
30 |
#include "qapi/qmp-core.h" |
31 |
#include "qga/channel.h" |
32 |
#ifdef _WIN32
|
33 |
#include "qga/service-win32.h" |
34 |
#include <windows.h> |
35 |
#endif
|
36 |
|
37 |
#ifndef _WIN32
|
38 |
#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent.0" |
39 |
#else
|
40 |
#define QGA_VIRTIO_PATH_DEFAULT "\\\\.\\Global\\org.qemu.guest_agent.0" |
41 |
#endif
|
42 |
#define QGA_PIDFILE_DEFAULT "/var/run/qemu-ga.pid" |
43 |
|
44 |
struct GAState {
|
45 |
JSONMessageParser parser; |
46 |
GMainLoop *main_loop; |
47 |
GAChannel *channel; |
48 |
bool virtio; /* fastpath to check for virtio to deal with poll() quirks */ |
49 |
GACommandState *command_state; |
50 |
GLogLevelFlags log_level; |
51 |
FILE *log_file; |
52 |
bool logging_enabled;
|
53 |
#ifdef _WIN32
|
54 |
GAService service; |
55 |
#endif
|
56 |
}; |
57 |
|
58 |
static struct GAState *ga_state; |
59 |
|
60 |
#ifdef _WIN32
|
61 |
DWORD WINAPI service_ctrl_handler(DWORD ctrl, DWORD type, LPVOID data, |
62 |
LPVOID ctx); |
63 |
VOID WINAPI service_main(DWORD argc, TCHAR *argv[]); |
64 |
#endif
|
65 |
|
66 |
static void quit_handler(int sig) |
67 |
{ |
68 |
g_debug("received signal num %d, quitting", sig);
|
69 |
|
70 |
if (g_main_loop_is_running(ga_state->main_loop)) {
|
71 |
g_main_loop_quit(ga_state->main_loop); |
72 |
} |
73 |
} |
74 |
|
75 |
#ifndef _WIN32
|
76 |
static gboolean register_signal_handlers(void) |
77 |
{ |
78 |
struct sigaction sigact;
|
79 |
int ret;
|
80 |
|
81 |
memset(&sigact, 0, sizeof(struct sigaction)); |
82 |
sigact.sa_handler = quit_handler; |
83 |
|
84 |
ret = sigaction(SIGINT, &sigact, NULL);
|
85 |
if (ret == -1) { |
86 |
g_error("error configuring signal handler: %s", strerror(errno));
|
87 |
return false; |
88 |
} |
89 |
ret = sigaction(SIGTERM, &sigact, NULL);
|
90 |
if (ret == -1) { |
91 |
g_error("error configuring signal handler: %s", strerror(errno));
|
92 |
return false; |
93 |
} |
94 |
return true; |
95 |
} |
96 |
#endif
|
97 |
|
98 |
static void usage(const char *cmd) |
99 |
{ |
100 |
printf( |
101 |
"Usage: %s -c <channel_opts>\n"
|
102 |
"QEMU Guest Agent %s\n"
|
103 |
"\n"
|
104 |
" -m, --method transport method: one of unix-listen, virtio-serial, or\n"
|
105 |
" isa-serial (virtio-serial is the default)\n"
|
106 |
" -p, --path device/socket path (%s is the default for virtio-serial)\n"
|
107 |
" -l, --logfile set logfile path, logs to stderr by default\n"
|
108 |
" -f, --pidfile specify pidfile (default is %s)\n"
|
109 |
" -v, --verbose log extra debugging information\n"
|
110 |
" -V, --version print version information and exit\n"
|
111 |
" -d, --daemonize become a daemon\n"
|
112 |
#ifdef _WIN32
|
113 |
" -s, --service service commands: install, uninstall\n"
|
114 |
#endif
|
115 |
" -b, --blacklist comma-separated list of RPCs to disable (no spaces, \"?\""
|
116 |
" to list available RPCs)\n"
|
117 |
" -h, --help display this help and exit\n"
|
118 |
"\n"
|
119 |
"Report bugs to <mdroth@linux.vnet.ibm.com>\n"
|
120 |
, cmd, QGA_VERSION, QGA_VIRTIO_PATH_DEFAULT, QGA_PIDFILE_DEFAULT); |
121 |
} |
122 |
|
123 |
static const char *ga_log_level_str(GLogLevelFlags level) |
124 |
{ |
125 |
switch (level & G_LOG_LEVEL_MASK) {
|
126 |
case G_LOG_LEVEL_ERROR:
|
127 |
return "error"; |
128 |
case G_LOG_LEVEL_CRITICAL:
|
129 |
return "critical"; |
130 |
case G_LOG_LEVEL_WARNING:
|
131 |
return "warning"; |
132 |
case G_LOG_LEVEL_MESSAGE:
|
133 |
return "message"; |
134 |
case G_LOG_LEVEL_INFO:
|
135 |
return "info"; |
136 |
case G_LOG_LEVEL_DEBUG:
|
137 |
return "debug"; |
138 |
default:
|
139 |
return "user"; |
140 |
} |
141 |
} |
142 |
|
143 |
bool ga_logging_enabled(GAState *s)
|
144 |
{ |
145 |
return s->logging_enabled;
|
146 |
} |
147 |
|
148 |
void ga_disable_logging(GAState *s)
|
149 |
{ |
150 |
s->logging_enabled = false;
|
151 |
} |
152 |
|
153 |
void ga_enable_logging(GAState *s)
|
154 |
{ |
155 |
s->logging_enabled = true;
|
156 |
} |
157 |
|
158 |
static void ga_log(const gchar *domain, GLogLevelFlags level, |
159 |
const gchar *msg, gpointer opaque)
|
160 |
{ |
161 |
GAState *s = opaque; |
162 |
GTimeVal time; |
163 |
const char *level_str = ga_log_level_str(level); |
164 |
|
165 |
if (!ga_logging_enabled(s)) {
|
166 |
return;
|
167 |
} |
168 |
|
169 |
level &= G_LOG_LEVEL_MASK; |
170 |
#ifndef _WIN32
|
171 |
if (domain && strcmp(domain, "syslog") == 0) { |
172 |
syslog(LOG_INFO, "%s: %s", level_str, msg);
|
173 |
} else if (level & s->log_level) { |
174 |
#else
|
175 |
if (level & s->log_level) {
|
176 |
#endif
|
177 |
g_get_current_time(&time); |
178 |
fprintf(s->log_file, |
179 |
"%lu.%lu: %s: %s\n", time.tv_sec, time.tv_usec, level_str, msg);
|
180 |
fflush(s->log_file); |
181 |
} |
182 |
} |
183 |
|
184 |
#ifndef _WIN32
|
185 |
static void become_daemon(const char *pidfile) |
186 |
{ |
187 |
pid_t pid, sid; |
188 |
int pidfd;
|
189 |
char *pidstr = NULL; |
190 |
|
191 |
pid = fork(); |
192 |
if (pid < 0) { |
193 |
exit(EXIT_FAILURE); |
194 |
} |
195 |
if (pid > 0) { |
196 |
exit(EXIT_SUCCESS); |
197 |
} |
198 |
|
199 |
pidfd = open(pidfile, O_CREAT|O_WRONLY|O_EXCL, S_IRUSR|S_IWUSR); |
200 |
if (pidfd == -1) { |
201 |
g_critical("Cannot create pid file, %s", strerror(errno));
|
202 |
exit(EXIT_FAILURE); |
203 |
} |
204 |
|
205 |
if (asprintf(&pidstr, "%d", getpid()) == -1) { |
206 |
g_critical("Cannot allocate memory");
|
207 |
goto fail;
|
208 |
} |
209 |
if (write(pidfd, pidstr, strlen(pidstr)) != strlen(pidstr)) {
|
210 |
free(pidstr); |
211 |
g_critical("Failed to write pid file");
|
212 |
goto fail;
|
213 |
} |
214 |
|
215 |
umask(0);
|
216 |
sid = setsid(); |
217 |
if (sid < 0) { |
218 |
goto fail;
|
219 |
} |
220 |
if ((chdir("/")) < 0) { |
221 |
goto fail;
|
222 |
} |
223 |
|
224 |
close(STDIN_FILENO); |
225 |
close(STDOUT_FILENO); |
226 |
close(STDERR_FILENO); |
227 |
free(pidstr); |
228 |
return;
|
229 |
|
230 |
fail:
|
231 |
unlink(pidfile); |
232 |
g_critical("failed to daemonize");
|
233 |
exit(EXIT_FAILURE); |
234 |
} |
235 |
#endif
|
236 |
|
237 |
static int send_response(GAState *s, QObject *payload) |
238 |
{ |
239 |
const char *buf; |
240 |
QString *payload_qstr; |
241 |
GIOStatus status; |
242 |
|
243 |
g_assert(payload && s->channel); |
244 |
|
245 |
payload_qstr = qobject_to_json(payload); |
246 |
if (!payload_qstr) {
|
247 |
return -EINVAL;
|
248 |
} |
249 |
|
250 |
qstring_append_chr(payload_qstr, '\n');
|
251 |
buf = qstring_get_str(payload_qstr); |
252 |
status = ga_channel_write_all(s->channel, buf, strlen(buf)); |
253 |
QDECREF(payload_qstr); |
254 |
if (status != G_IO_STATUS_NORMAL) {
|
255 |
return -EIO;
|
256 |
} |
257 |
|
258 |
return 0; |
259 |
} |
260 |
|
261 |
static void process_command(GAState *s, QDict *req) |
262 |
{ |
263 |
QObject *rsp = NULL;
|
264 |
int ret;
|
265 |
|
266 |
g_assert(req); |
267 |
g_debug("processing command");
|
268 |
rsp = qmp_dispatch(QOBJECT(req)); |
269 |
if (rsp) {
|
270 |
ret = send_response(s, rsp); |
271 |
if (ret) {
|
272 |
g_warning("error sending response: %s", strerror(ret));
|
273 |
} |
274 |
qobject_decref(rsp); |
275 |
} else {
|
276 |
g_warning("error getting response");
|
277 |
} |
278 |
} |
279 |
|
280 |
/* handle requests/control events coming in over the channel */
|
281 |
static void process_event(JSONMessageParser *parser, QList *tokens) |
282 |
{ |
283 |
GAState *s = container_of(parser, GAState, parser); |
284 |
QObject *obj; |
285 |
QDict *qdict; |
286 |
Error *err = NULL;
|
287 |
int ret;
|
288 |
|
289 |
g_assert(s && parser); |
290 |
|
291 |
g_debug("process_event: called");
|
292 |
obj = json_parser_parse_err(tokens, NULL, &err);
|
293 |
if (err || !obj || qobject_type(obj) != QTYPE_QDICT) {
|
294 |
qobject_decref(obj); |
295 |
qdict = qdict_new(); |
296 |
if (!err) {
|
297 |
g_warning("failed to parse event: unknown error");
|
298 |
error_set(&err, QERR_JSON_PARSING); |
299 |
} else {
|
300 |
g_warning("failed to parse event: %s", error_get_pretty(err));
|
301 |
} |
302 |
qdict_put_obj(qdict, "error", error_get_qobject(err));
|
303 |
error_free(err); |
304 |
} else {
|
305 |
qdict = qobject_to_qdict(obj); |
306 |
} |
307 |
|
308 |
g_assert(qdict); |
309 |
|
310 |
/* handle host->guest commands */
|
311 |
if (qdict_haskey(qdict, "execute")) { |
312 |
process_command(s, qdict); |
313 |
} else {
|
314 |
if (!qdict_haskey(qdict, "error")) { |
315 |
QDECREF(qdict); |
316 |
qdict = qdict_new(); |
317 |
g_warning("unrecognized payload format");
|
318 |
error_set(&err, QERR_UNSUPPORTED); |
319 |
qdict_put_obj(qdict, "error", error_get_qobject(err));
|
320 |
error_free(err); |
321 |
} |
322 |
ret = send_response(s, QOBJECT(qdict)); |
323 |
if (ret) {
|
324 |
g_warning("error sending error response: %s", strerror(ret));
|
325 |
} |
326 |
} |
327 |
|
328 |
QDECREF(qdict); |
329 |
} |
330 |
|
331 |
/* false return signals GAChannel to close the current client connection */
|
332 |
static gboolean channel_event_cb(GIOCondition condition, gpointer data)
|
333 |
{ |
334 |
GAState *s = data; |
335 |
gchar buf[QGA_READ_COUNT_DEFAULT+1];
|
336 |
gsize count; |
337 |
GError *err = NULL;
|
338 |
GIOStatus status = ga_channel_read(s->channel, buf, QGA_READ_COUNT_DEFAULT, &count); |
339 |
if (err != NULL) { |
340 |
g_warning("error reading channel: %s", err->message);
|
341 |
g_error_free(err); |
342 |
return false; |
343 |
} |
344 |
switch (status) {
|
345 |
case G_IO_STATUS_ERROR:
|
346 |
g_warning("error reading channel");
|
347 |
return false; |
348 |
case G_IO_STATUS_NORMAL:
|
349 |
buf[count] = 0;
|
350 |
g_debug("read data, count: %d, data: %s", (int)count, buf); |
351 |
json_message_parser_feed(&s->parser, (char *)buf, (int)count); |
352 |
break;
|
353 |
case G_IO_STATUS_EOF:
|
354 |
g_debug("received EOF");
|
355 |
if (!s->virtio) {
|
356 |
return false; |
357 |
} |
358 |
case G_IO_STATUS_AGAIN:
|
359 |
/* virtio causes us to spin here when no process is attached to
|
360 |
* host-side chardev. sleep a bit to mitigate this
|
361 |
*/
|
362 |
if (s->virtio) {
|
363 |
usleep(100*1000); |
364 |
} |
365 |
return true; |
366 |
default:
|
367 |
g_warning("unknown channel read status, closing");
|
368 |
return false; |
369 |
} |
370 |
return true; |
371 |
} |
372 |
|
373 |
static gboolean channel_init(GAState *s, const gchar *method, const gchar *path) |
374 |
{ |
375 |
GAChannelMethod channel_method; |
376 |
|
377 |
if (method == NULL) { |
378 |
method = "virtio-serial";
|
379 |
} |
380 |
|
381 |
if (path == NULL) { |
382 |
if (strcmp(method, "virtio-serial") != 0) { |
383 |
g_critical("must specify a path for this channel");
|
384 |
return false; |
385 |
} |
386 |
/* try the default path for the virtio-serial port */
|
387 |
path = QGA_VIRTIO_PATH_DEFAULT; |
388 |
} |
389 |
|
390 |
if (strcmp(method, "virtio-serial") == 0) { |
391 |
s->virtio = true; /* virtio requires special handling in some cases */ |
392 |
channel_method = GA_CHANNEL_VIRTIO_SERIAL; |
393 |
} else if (strcmp(method, "isa-serial") == 0) { |
394 |
channel_method = GA_CHANNEL_ISA_SERIAL; |
395 |
} else if (strcmp(method, "unix-listen") == 0) { |
396 |
channel_method = GA_CHANNEL_UNIX_LISTEN; |
397 |
} else {
|
398 |
g_critical("unsupported channel method/type: %s", method);
|
399 |
return false; |
400 |
} |
401 |
|
402 |
s->channel = ga_channel_new(channel_method, path, channel_event_cb, s); |
403 |
if (!s->channel) {
|
404 |
g_critical("failed to create guest agent channel");
|
405 |
return false; |
406 |
} |
407 |
|
408 |
return true; |
409 |
} |
410 |
|
411 |
#ifdef _WIN32
|
412 |
DWORD WINAPI service_ctrl_handler(DWORD ctrl, DWORD type, LPVOID data, |
413 |
LPVOID ctx) |
414 |
{ |
415 |
DWORD ret = NO_ERROR; |
416 |
GAService *service = &ga_state->service; |
417 |
|
418 |
switch (ctrl)
|
419 |
{ |
420 |
case SERVICE_CONTROL_STOP:
|
421 |
case SERVICE_CONTROL_SHUTDOWN:
|
422 |
quit_handler(SIGTERM); |
423 |
service->status.dwCurrentState = SERVICE_STOP_PENDING; |
424 |
SetServiceStatus(service->status_handle, &service->status); |
425 |
break;
|
426 |
|
427 |
default:
|
428 |
ret = ERROR_CALL_NOT_IMPLEMENTED; |
429 |
} |
430 |
return ret;
|
431 |
} |
432 |
|
433 |
VOID WINAPI service_main(DWORD argc, TCHAR *argv[]) |
434 |
{ |
435 |
GAService *service = &ga_state->service; |
436 |
|
437 |
service->status_handle = RegisterServiceCtrlHandlerEx(QGA_SERVICE_NAME, |
438 |
service_ctrl_handler, NULL);
|
439 |
|
440 |
if (service->status_handle == 0) { |
441 |
g_critical("Failed to register extended requests function!\n");
|
442 |
return;
|
443 |
} |
444 |
|
445 |
service->status.dwServiceType = SERVICE_WIN32; |
446 |
service->status.dwCurrentState = SERVICE_RUNNING; |
447 |
service->status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; |
448 |
service->status.dwWin32ExitCode = NO_ERROR; |
449 |
service->status.dwServiceSpecificExitCode = NO_ERROR; |
450 |
service->status.dwCheckPoint = 0;
|
451 |
service->status.dwWaitHint = 0;
|
452 |
SetServiceStatus(service->status_handle, &service->status); |
453 |
|
454 |
g_main_loop_run(ga_state->main_loop); |
455 |
|
456 |
service->status.dwCurrentState = SERVICE_STOPPED; |
457 |
SetServiceStatus(service->status_handle, &service->status); |
458 |
} |
459 |
#endif
|
460 |
|
461 |
int main(int argc, char **argv) |
462 |
{ |
463 |
const char *sopt = "hVvdm:p:l:f:b:s:"; |
464 |
const char *method = NULL, *path = NULL, *pidfile = QGA_PIDFILE_DEFAULT; |
465 |
const char *log_file_name = NULL; |
466 |
#ifdef _WIN32
|
467 |
const char *service = NULL; |
468 |
#endif
|
469 |
const struct option lopt[] = { |
470 |
{ "help", 0, NULL, 'h' }, |
471 |
{ "version", 0, NULL, 'V' }, |
472 |
{ "logfile", 1, NULL, 'l' }, |
473 |
{ "pidfile", 1, NULL, 'f' }, |
474 |
{ "verbose", 0, NULL, 'v' }, |
475 |
{ "method", 1, NULL, 'm' }, |
476 |
{ "path", 1, NULL, 'p' }, |
477 |
{ "daemonize", 0, NULL, 'd' }, |
478 |
{ "blacklist", 1, NULL, 'b' }, |
479 |
#ifdef _WIN32
|
480 |
{ "service", 1, NULL, 's' }, |
481 |
#endif
|
482 |
{ NULL, 0, NULL, 0 } |
483 |
}; |
484 |
int opt_ind = 0, ch, daemonize = 0, i, j, len; |
485 |
GLogLevelFlags log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL; |
486 |
FILE *log_file = stderr; |
487 |
GAState *s; |
488 |
|
489 |
module_call_init(MODULE_INIT_QAPI); |
490 |
|
491 |
while ((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) { |
492 |
switch (ch) {
|
493 |
case 'm': |
494 |
method = optarg; |
495 |
break;
|
496 |
case 'p': |
497 |
path = optarg; |
498 |
break;
|
499 |
case 'l': |
500 |
log_file_name = optarg; |
501 |
log_file = fopen(log_file_name, "a");
|
502 |
if (!log_file) {
|
503 |
g_critical("unable to open specified log file: %s",
|
504 |
strerror(errno)); |
505 |
return EXIT_FAILURE;
|
506 |
} |
507 |
break;
|
508 |
case 'f': |
509 |
pidfile = optarg; |
510 |
break;
|
511 |
case 'v': |
512 |
/* enable all log levels */
|
513 |
log_level = G_LOG_LEVEL_MASK; |
514 |
break;
|
515 |
case 'V': |
516 |
printf("QEMU Guest Agent %s\n", QGA_VERSION);
|
517 |
return 0; |
518 |
case 'd': |
519 |
daemonize = 1;
|
520 |
break;
|
521 |
case 'b': { |
522 |
char **list_head, **list;
|
523 |
if (*optarg == '?') { |
524 |
list_head = list = qmp_get_command_list(); |
525 |
while (*list != NULL) { |
526 |
printf("%s\n", *list);
|
527 |
g_free(*list); |
528 |
list++; |
529 |
} |
530 |
g_free(list_head); |
531 |
return 0; |
532 |
} |
533 |
for (j = 0, i = 0, len = strlen(optarg); i < len; i++) { |
534 |
if (optarg[i] == ',') { |
535 |
optarg[i] = 0;
|
536 |
qmp_disable_command(&optarg[j]); |
537 |
g_debug("disabling command: %s", &optarg[j]);
|
538 |
j = i + 1;
|
539 |
} |
540 |
} |
541 |
if (j < i) {
|
542 |
qmp_disable_command(&optarg[j]); |
543 |
g_debug("disabling command: %s", &optarg[j]);
|
544 |
} |
545 |
break;
|
546 |
} |
547 |
#ifdef _WIN32
|
548 |
case 's': |
549 |
service = optarg; |
550 |
if (strcmp(service, "install") == 0) { |
551 |
return ga_install_service(path, log_file_name);
|
552 |
} else if (strcmp(service, "uninstall") == 0) { |
553 |
return ga_uninstall_service();
|
554 |
} else {
|
555 |
printf("Unknown service command.\n");
|
556 |
return EXIT_FAILURE;
|
557 |
} |
558 |
break;
|
559 |
#endif
|
560 |
case 'h': |
561 |
usage(argv[0]);
|
562 |
return 0; |
563 |
case '?': |
564 |
g_print("Unknown option, try '%s --help' for more information.\n",
|
565 |
argv[0]);
|
566 |
return EXIT_FAILURE;
|
567 |
} |
568 |
} |
569 |
|
570 |
#ifndef _WIN32
|
571 |
if (daemonize) {
|
572 |
g_debug("starting daemon");
|
573 |
become_daemon(pidfile); |
574 |
} |
575 |
#endif
|
576 |
|
577 |
s = g_malloc0(sizeof(GAState));
|
578 |
s->log_file = log_file; |
579 |
s->log_level = log_level; |
580 |
g_log_set_default_handler(ga_log, s); |
581 |
g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
|
582 |
s->logging_enabled = true;
|
583 |
s->command_state = ga_command_state_new(); |
584 |
ga_command_state_init(s, s->command_state); |
585 |
ga_command_state_init_all(s->command_state); |
586 |
json_message_parser_init(&s->parser, process_event); |
587 |
ga_state = s; |
588 |
#ifndef _WIN32
|
589 |
if (!register_signal_handlers()) {
|
590 |
g_critical("failed to register signal handlers");
|
591 |
goto out_bad;
|
592 |
} |
593 |
#endif
|
594 |
|
595 |
s->main_loop = g_main_loop_new(NULL, false); |
596 |
if (!channel_init(ga_state, method, path)) {
|
597 |
g_critical("failed to initialize guest agent channel");
|
598 |
goto out_bad;
|
599 |
} |
600 |
#ifndef _WIN32
|
601 |
g_main_loop_run(ga_state->main_loop); |
602 |
#else
|
603 |
if (daemonize) {
|
604 |
SERVICE_TABLE_ENTRY service_table[] = { |
605 |
{ (char *)QGA_SERVICE_NAME, service_main }, { NULL, NULL } }; |
606 |
StartServiceCtrlDispatcher(service_table); |
607 |
} else {
|
608 |
g_main_loop_run(ga_state->main_loop); |
609 |
} |
610 |
#endif
|
611 |
|
612 |
ga_command_state_cleanup_all(ga_state->command_state); |
613 |
ga_channel_free(ga_state->channel); |
614 |
|
615 |
if (daemonize) {
|
616 |
unlink(pidfile); |
617 |
} |
618 |
return 0; |
619 |
|
620 |
out_bad:
|
621 |
if (daemonize) {
|
622 |
unlink(pidfile); |
623 |
} |
624 |
return EXIT_FAILURE;
|
625 |
} |