/* * log_daemon.c: log management daemon * * Copyright (C) 2006-2015 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 #include #include #include #include #include #include "log_daemon.h" #include "log_daemon_config.h" #include "admin/admin_server_dispatch.h" #include "virutil.h" #include "virfile.h" #include "virpidfile.h" #include "virprocess.h" #include "virerror.h" #include "virlog.h" #include "viralloc.h" #include "virconf.h" #include "rpc/virnetdaemon.h" #include "virrandom.h" #include "virhash.h" #include "viruuid.h" #include "virstring.h" #include "virgettext.h" #include "virdaemon.h" #include "log_daemon_dispatch.h" #include "log_protocol.h" #include "configmake.h" #define VIR_FROM_THIS VIR_FROM_LOGGING VIR_LOG_INIT("logging.log_daemon"); struct _virLogDaemon { virMutex lock; virNetDaemonPtr dmn; virLogHandlerPtr handler; }; virLogDaemonPtr logDaemon = NULL; static bool execRestart; static void * virLogDaemonClientNew(virNetServerClientPtr client, void *opaque); static void virLogDaemonClientFree(void *opaque); static void * virLogDaemonClientNewPostExecRestart(virNetServerClientPtr client, virJSONValuePtr object, void *opaque); static virJSONValuePtr virLogDaemonClientPreExecRestart(virNetServerClientPtr client, void *opaque); static void virLogDaemonFree(virLogDaemonPtr logd) { if (!logd) return; virObjectUnref(logd->handler); virMutexDestroy(&logd->lock); virObjectUnref(logd->dmn); VIR_FREE(logd); } static void virLogDaemonInhibitor(bool inhibit, void *opaque) { virLogDaemonPtr dmn = opaque; /* virtlogd uses inhibition only to stop session daemon being killed after * the specified timeout, for the system daemon this is taken care of by * libvirtd and the dependencies between the services. */ if (virNetDaemonIsPrivileged(dmn->dmn)) return; if (inhibit) virNetDaemonAddShutdownInhibition(dmn->dmn); else virNetDaemonRemoveShutdownInhibition(dmn->dmn); } static virLogDaemonPtr virLogDaemonNew(virLogDaemonConfigPtr config, bool privileged) { virLogDaemonPtr logd; virNetServerPtr srv = NULL; if (VIR_ALLOC(logd) < 0) return NULL; if (virMutexInit(&logd->lock) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unable to initialize mutex")); VIR_FREE(logd); return NULL; } if (!(logd->dmn = virNetDaemonNew())) goto error; if (!(srv = virNetServerNew("virtlogd", 1, 0, 0, 0, config->max_clients, config->max_clients, -1, 0, virLogDaemonClientNew, virLogDaemonClientPreExecRestart, virLogDaemonClientFree, (void*)(intptr_t)(privileged ? 0x1 : 0x0)))) goto error; if (virNetDaemonAddServer(logd->dmn, srv) < 0) goto error; virObjectUnref(srv); srv = NULL; if (!(srv = virNetServerNew("admin", 1, 0, 0, 0, config->admin_max_clients, config->admin_max_clients, -1, 0, remoteAdmClientNew, remoteAdmClientPreExecRestart, remoteAdmClientFree, logd->dmn))) goto error; if (virNetDaemonAddServer(logd->dmn, srv) < 0) goto error; virObjectUnref(srv); srv = NULL; if (!(logd->handler = virLogHandlerNew(privileged, config->max_size, config->max_backups, virLogDaemonInhibitor, logd))) goto error; return logd; error: virObjectUnref(srv); virLogDaemonFree(logd); return NULL; } virLogHandlerPtr virLogDaemonGetHandler(virLogDaemonPtr dmn) { return dmn->handler; } static virNetServerPtr virLogDaemonNewServerPostExecRestart(virNetDaemonPtr dmn, const char *name, virJSONValuePtr object, void *opaque) { if (STREQ(name, "virtlogd")) { return virNetServerNewPostExecRestart(object, name, virLogDaemonClientNew, virLogDaemonClientNewPostExecRestart, virLogDaemonClientPreExecRestart, virLogDaemonClientFree, opaque); } else if (STREQ(name, "admin")) { return virNetServerNewPostExecRestart(object, name, remoteAdmClientNew, remoteAdmClientNewPostExecRestart, remoteAdmClientPreExecRestart, remoteAdmClientFree, dmn); } else { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unexpected server name '%s' during restart"), name); return NULL; } } static virLogDaemonPtr virLogDaemonNewPostExecRestart(virJSONValuePtr object, bool privileged, virLogDaemonConfigPtr config) { virLogDaemonPtr logd; virJSONValuePtr child; const char *serverNames[] = { "virtlogd" }; if (VIR_ALLOC(logd) < 0) return NULL; if (virMutexInit(&logd->lock) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unable to initialize mutex")); VIR_FREE(logd); return NULL; } if (!(child = virJSONValueObjectGet(object, "daemon"))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Malformed daemon data from JSON file")); goto error; } if (!(logd->dmn = virNetDaemonNewPostExecRestart(child, G_N_ELEMENTS(serverNames), serverNames, virLogDaemonNewServerPostExecRestart, (void*)(intptr_t)(privileged ? 0x1 : 0x0)))) goto error; if (!(child = virJSONValueObjectGet(object, "handler"))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Malformed daemon data from JSON file")); goto error; } if (!(logd->handler = virLogHandlerNewPostExecRestart(child, privileged, config->max_size, config->max_backups, virLogDaemonInhibitor, logd))) goto error; return logd; error: virLogDaemonFree(logd); return NULL; } static void virLogDaemonErrorHandler(void *opaque G_GNUC_UNUSED, virErrorPtr err G_GNUC_UNUSED) { /* Don't do anything, since logging infrastructure already * took care of reporting the error */ } /* Display version information. */ static void virLogDaemonVersion(const char *argv0) { printf("%s (%s) %s\n", argv0, PACKAGE_NAME, PACKAGE_VERSION); } static void virLogDaemonShutdownHandler(virNetDaemonPtr dmn, siginfo_t *sig G_GNUC_UNUSED, void *opaque G_GNUC_UNUSED) { virNetDaemonQuit(dmn); } static void virLogDaemonExecRestartHandler(virNetDaemonPtr dmn, siginfo_t *sig G_GNUC_UNUSED, void *opaque G_GNUC_UNUSED) { execRestart = true; virNetDaemonQuit(dmn); } static int virLogDaemonSetupSignals(virNetDaemonPtr dmn) { if (virNetDaemonAddSignalHandler(dmn, SIGINT, virLogDaemonShutdownHandler, NULL) < 0) return -1; if (virNetDaemonAddSignalHandler(dmn, SIGQUIT, virLogDaemonShutdownHandler, NULL) < 0) return -1; if (virNetDaemonAddSignalHandler(dmn, SIGTERM, virLogDaemonShutdownHandler, NULL) < 0) return -1; if (virNetDaemonAddSignalHandler(dmn, SIGUSR1, virLogDaemonExecRestartHandler, NULL) < 0) return -1; return 0; } static void virLogDaemonClientFree(void *opaque) { virLogDaemonClientPtr priv = opaque; if (!priv) return; VIR_DEBUG("priv=%p client=%lld", priv, (unsigned long long)priv->clientPid); virMutexDestroy(&priv->lock); VIR_FREE(priv); } static void * virLogDaemonClientNew(virNetServerClientPtr client, void *opaque) { virLogDaemonClientPtr priv; uid_t clientuid; gid_t clientgid; unsigned long long timestamp; bool privileged = opaque != NULL; if (VIR_ALLOC(priv) < 0) return NULL; if (virMutexInit(&priv->lock) < 0) { VIR_FREE(priv); virReportSystemError(errno, "%s", _("unable to init mutex")); return NULL; } if (virNetServerClientGetUNIXIdentity(client, &clientuid, &clientgid, &priv->clientPid, ×tamp) < 0) goto error; VIR_DEBUG("New client pid %llu uid %llu", (unsigned long long)priv->clientPid, (unsigned long long)clientuid); if (!privileged) { if (geteuid() != clientuid) { virReportRestrictedError(_("Disallowing client %llu with uid %llu"), (unsigned long long)priv->clientPid, (unsigned long long)clientuid); goto error; } } else { if (clientuid != 0) { virReportRestrictedError(_("Disallowing client %llu with uid %llu"), (unsigned long long)priv->clientPid, (unsigned long long)clientuid); goto error; } } /* there's no closing handshake in the logging protocol */ virNetServerClientSetQuietEOF(client); return priv; error: virLogDaemonClientFree(priv); return NULL; } static void * virLogDaemonClientNewPostExecRestart(virNetServerClientPtr client, virJSONValuePtr object G_GNUC_UNUSED, void *opaque) { virLogDaemonClientPtr priv = virLogDaemonClientNew(client, opaque); if (!priv) return NULL; return priv; } static virJSONValuePtr virLogDaemonClientPreExecRestart(virNetServerClientPtr client G_GNUC_UNUSED, void *opaque G_GNUC_UNUSED) { virJSONValuePtr object = virJSONValueNewObject(); return object; } static int virLogDaemonExecRestartStatePath(bool privileged, char **state_file) { if (privileged) { *state_file = g_strdup(RUNSTATEDIR "/virtlogd-restart-exec.json"); } else { g_autofree char *rundir = NULL; mode_t old_umask; rundir = virGetUserRuntimeDirectory(); old_umask = umask(077); if (virFileMakePath(rundir) < 0) { umask(old_umask); return -1; } umask(old_umask); *state_file = g_strdup_printf("%s/virtlogd-restart-exec.json", rundir); } return 0; } static char * virLogDaemonGetExecRestartMagic(void) { char *ret; ret = g_strdup_printf("%lld", (long long int)getpid()); return ret; } static int virLogDaemonPostExecRestart(const char *state_file, const char *pid_file, int *pid_file_fd, bool privileged, virLogDaemonConfigPtr config) { const char *gotmagic; char *wantmagic = NULL; int ret = -1; char *state = NULL; virJSONValuePtr object = NULL; VIR_DEBUG("Running post-restart exec"); if (!virFileExists(state_file)) { VIR_DEBUG("No restart state file %s present", state_file); ret = 0; goto cleanup; } if (virFileReadAll(state_file, 1024 * 1024 * 10, /* 10 MB */ &state) < 0) goto cleanup; VIR_DEBUG("Loading state %s", state); if (!(object = virJSONValueFromString(state))) goto cleanup; gotmagic = virJSONValueObjectGetString(object, "magic"); if (!gotmagic) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing magic data in JSON document")); goto cleanup; } if (!(wantmagic = virLogDaemonGetExecRestartMagic())) goto cleanup; if (STRNEQ(gotmagic, wantmagic)) { VIR_WARN("Found restart exec file with old magic %s vs wanted %s", gotmagic, wantmagic); ret = 0; goto cleanup; } /* Re-claim PID file now as we will not be daemonizing */ if (pid_file && (*pid_file_fd = virPidFileAcquirePath(pid_file, false, getpid())) < 0) goto cleanup; if (!(logDaemon = virLogDaemonNewPostExecRestart(object, privileged, config))) goto cleanup; ret = 1; cleanup: unlink(state_file); VIR_FREE(wantmagic); VIR_FREE(state); virJSONValueFree(object); return ret; } static int virLogDaemonPreExecRestart(const char *state_file, virNetDaemonPtr dmn, char **argv) { virJSONValuePtr child; char *state = NULL; virJSONValuePtr object = virJSONValueNewObject(); char *magic; VIR_DEBUG("Running pre-restart exec"); if (!(child = virNetDaemonPreExecRestart(dmn))) goto cleanup; if (virJSONValueObjectAppend(object, "daemon", child) < 0) { virJSONValueFree(child); goto cleanup; } if (!(magic = virLogDaemonGetExecRestartMagic())) goto cleanup; if (virJSONValueObjectAppendString(object, "magic", magic) < 0) { VIR_FREE(magic); goto cleanup; } if (!(child = virLogHandlerPreExecRestart(logDaemon->handler))) goto cleanup; if (virJSONValueObjectAppend(object, "handler", child) < 0) { virJSONValueFree(child); goto cleanup; } if (!(state = virJSONValueToString(object, true))) goto cleanup; VIR_DEBUG("Saving state %s", state); if (virFileWriteStr(state_file, state, 0700) < 0) { virReportSystemError(errno, _("Unable to save state file %s"), state_file); goto cleanup; } if (execvp(argv[0], argv) < 0) { virReportSystemError(errno, "%s", _("Unable to restart self")); goto cleanup; } abort(); /* This should be impossible to reach */ cleanup: VIR_FREE(state); virJSONValueFree(object); return -1; } static void virLogDaemonUsage(const char *argv0, bool privileged) { fprintf(stderr, _("\n" "Usage:\n" " %s [options]\n" "\n" "Options:\n" " -h | --help Display program help:\n" " -v | --verbose Verbose messages.\n" " -d | --daemon Run as a daemon & write PID file.\n" " -t | --timeout Exit after timeout period.\n" " -f | --config Configuration file.\n" " -V | --version Display version information.\n" " -p | --pid-file Change name of PID file.\n" "\n" "libvirt log management daemon:\n"), argv0); if (privileged) { fprintf(stderr, _("\n" " Default paths:\n" "\n" " Configuration file (unless overridden by -f):\n" " %s/libvirt/virtlogd.conf\n" "\n" " Sockets:\n" " %s/libvirt/virtlogd-sock\n" "\n" " PID file (unless overridden by -p):\n" " %s/virtlogd.pid\n" "\n"), SYSCONFDIR, RUNSTATEDIR, RUNSTATEDIR); } else { fprintf(stderr, "%s", _("\n" " Default paths:\n" "\n" " Configuration file (unless overridden by -f):\n" " $XDG_CONFIG_HOME/libvirt/virtlogd.conf\n" "\n" " Sockets:\n" " $XDG_RUNTIME_DIR/libvirt/virtlogd-sock\n" "\n" " PID file:\n" " $XDG_RUNTIME_DIR/libvirt/virtlogd.pid\n" "\n")); } } int main(int argc, char **argv) { virNetServerPtr logSrv = NULL; virNetServerPtr adminSrv = NULL; virNetServerProgramPtr logProgram = NULL; virNetServerProgramPtr adminProgram = NULL; char *remote_config_file = NULL; int statuswrite = -1; int ret = 1; int verbose = 0; int godaemon = 0; char *run_dir = NULL; char *pid_file = NULL; int pid_file_fd = -1; char *sock_file = NULL; char *admin_sock_file = NULL; int timeout = 0; /* -t: Shutdown timeout */ char *state_file = NULL; bool implicit_conf = false; mode_t old_umask; bool privileged = false; virLogDaemonConfigPtr config = NULL; int rv; struct option opts[] = { { "verbose", no_argument, &verbose, 'v'}, { "daemon", no_argument, &godaemon, 'd'}, { "config", required_argument, NULL, 'f'}, { "timeout", required_argument, NULL, 't'}, { "pid-file", required_argument, NULL, 'p'}, { "version", no_argument, NULL, 'V' }, { "help", no_argument, NULL, 'h' }, {0, 0, 0, 0} }; privileged = geteuid() == 0; if (virGettextInitialize() < 0 || virErrorInitialize() < 0) { fprintf(stderr, _("%s: initialization failed\n"), argv[0]); exit(EXIT_FAILURE); } while (1) { int optidx = 0; int c; char *tmp; c = getopt_long(argc, argv, "df:p:t:vVh", opts, &optidx); if (c == -1) break; switch (c) { case 0: /* Got one of the flags */ break; case 'v': verbose = 1; break; case 'd': godaemon = 1; break; case 't': if (virStrToLong_i(optarg, &tmp, 10, &timeout) != 0 || timeout < 0 /* Ensure that we can multiply by 1000 without overflowing. */ || timeout > INT_MAX / 1000) { VIR_ERROR(_("Invalid value for timeout")); exit(EXIT_FAILURE); } break; case 'p': VIR_FREE(pid_file); pid_file = g_strdup(optarg); break; case 'f': VIR_FREE(remote_config_file); remote_config_file = g_strdup(optarg); break; case 'V': virLogDaemonVersion(argv[0]); exit(EXIT_SUCCESS); case 'h': virLogDaemonUsage(argv[0], privileged); exit(EXIT_SUCCESS); case '?': default: virLogDaemonUsage(argv[0], privileged); exit(EXIT_FAILURE); } } virFileActivateDirOverrideForProg(argv[0]); if (!(config = virLogDaemonConfigNew(privileged))) { VIR_ERROR(_("Can't create initial configuration")); exit(EXIT_FAILURE); } /* No explicit config, so try and find a default one */ if (remote_config_file == NULL) { implicit_conf = true; if (virLogDaemonConfigFilePath(privileged, &remote_config_file) < 0) { VIR_ERROR(_("Can't determine config path")); exit(EXIT_FAILURE); } } /* Read the config file if it exists */ if (remote_config_file && virLogDaemonConfigLoadFile(config, remote_config_file, implicit_conf) < 0) { VIR_ERROR(_("Can't load config file: %s: %s"), virGetLastErrorMessage(), remote_config_file); exit(EXIT_FAILURE); } virDaemonSetupLogging("virtlogd", config->log_level, config->log_filters, config->log_outputs, privileged, verbose, godaemon); if (!pid_file && virPidFileConstructPath(privileged, RUNSTATEDIR, "virtlogd", &pid_file) < 0) { VIR_ERROR(_("Can't determine pid file path.")); exit(EXIT_FAILURE); } VIR_DEBUG("Decided on pid file path '%s'", NULLSTR(pid_file)); if (virDaemonUnixSocketPaths("virtlogd", privileged, NULL, &sock_file, NULL, &admin_sock_file) < 0) { VIR_ERROR(_("Can't determine socket paths")); exit(EXIT_FAILURE); } VIR_DEBUG("Decided on socket paths '%s' and '%s'", sock_file, admin_sock_file); if (virLogDaemonExecRestartStatePath(privileged, &state_file) < 0) { VIR_ERROR(_("Can't determine restart state file path")); exit(EXIT_FAILURE); } VIR_DEBUG("Decided on restart state file path '%s'", state_file); /* Ensure the rundir exists (on tmpfs on some systems) */ if (privileged) { run_dir = g_strdup(RUNSTATEDIR "/libvirt"); } else { run_dir = virGetUserRuntimeDirectory(); } if (privileged) old_umask = umask(022); else old_umask = umask(077); VIR_DEBUG("Ensuring run dir '%s' exists", run_dir); if (virFileMakePath(run_dir) < 0) { VIR_ERROR(_("unable to create rundir %s: %s"), run_dir, g_strerror(errno)); ret = VIR_DAEMON_ERR_RUNDIR; umask(old_umask); goto cleanup; } umask(old_umask); if ((rv = virLogDaemonPostExecRestart(state_file, pid_file, &pid_file_fd, privileged, config)) < 0) { ret = VIR_DAEMON_ERR_INIT; goto cleanup; } /* rv == 1, means we setup everything from saved state, * so only (possibly) daemonize and setup stuff from * scratch if rv == 0 */ if (rv == 0) { g_autoptr(virSystemdActivation) act = NULL; virSystemdActivationMap actmap[] = { { .name = "virtlogd.socket", .family = AF_UNIX, .path = sock_file }, { .name = "virtlogd-admin.socket", .family = AF_UNIX, .path = admin_sock_file }, }; if (godaemon) { if (chdir("/") < 0) { VIR_ERROR(_("cannot change to root directory: %s"), g_strerror(errno)); goto cleanup; } if ((statuswrite = virDaemonForkIntoBackground(argv[0])) < 0) { VIR_ERROR(_("Failed to fork as daemon: %s"), g_strerror(errno)); goto cleanup; } } /* If we have a pidfile set, claim it now, exiting if already taken */ if ((pid_file_fd = virPidFileAcquirePath(pid_file, false, getpid())) < 0) { ret = VIR_DAEMON_ERR_PIDFILE; goto cleanup; } if (!(logDaemon = virLogDaemonNew(config, privileged))) { ret = VIR_DAEMON_ERR_INIT; goto cleanup; } if (virSystemdGetActivation(actmap, G_N_ELEMENTS(actmap), &act) < 0) { ret = VIR_DAEMON_ERR_NETWORK; goto cleanup; } logSrv = virNetDaemonGetServer(logDaemon->dmn, "virtlogd"); adminSrv = virNetDaemonGetServer(logDaemon->dmn, "admin"); if (virNetServerAddServiceUNIX(logSrv, act, "virtlogd.socket", sock_file, 0700, 0, 0, NULL, false, 0, 1) < 0) { ret = VIR_DAEMON_ERR_NETWORK; goto cleanup; } if (virNetServerAddServiceUNIX(adminSrv, act, "virtlogd-admin.socket", admin_sock_file, 0700, 0, 0, NULL, false, 0, 1) < 0) { ret = VIR_DAEMON_ERR_NETWORK; goto cleanup; } if (act && virSystemdActivationComplete(act) < 0) { ret = VIR_DAEMON_ERR_NETWORK; goto cleanup; } } else { logSrv = virNetDaemonGetServer(logDaemon->dmn, "virtlogd"); /* If exec-restarting from old virtlogd, we won't have an * admin server present */ if (virNetDaemonHasServer(logDaemon->dmn, "admin")) adminSrv = virNetDaemonGetServer(logDaemon->dmn, "admin"); } if (timeout > 0) { VIR_DEBUG("Registering shutdown timeout %d", timeout); virNetDaemonAutoShutdown(logDaemon->dmn, timeout); } if ((virLogDaemonSetupSignals(logDaemon->dmn)) < 0) { ret = VIR_DAEMON_ERR_SIGNAL; goto cleanup; } if (!(logProgram = virNetServerProgramNew(VIR_LOG_MANAGER_PROTOCOL_PROGRAM, VIR_LOG_MANAGER_PROTOCOL_PROGRAM_VERSION, virLogManagerProtocolProcs, virLogManagerProtocolNProcs))) { ret = VIR_DAEMON_ERR_INIT; goto cleanup; } if (virNetServerAddProgram(logSrv, logProgram) < 0) { ret = VIR_DAEMON_ERR_INIT; goto cleanup; } if (adminSrv != NULL) { if (!(adminProgram = virNetServerProgramNew(ADMIN_PROGRAM, ADMIN_PROTOCOL_VERSION, adminProcs, adminNProcs))) { ret = VIR_DAEMON_ERR_INIT; goto cleanup; } if (virNetServerAddProgram(adminSrv, adminProgram) < 0) { ret = VIR_DAEMON_ERR_INIT; goto cleanup; } } /* Disable error func, now logging is setup */ virSetErrorFunc(NULL, virLogDaemonErrorHandler); /* Tell parent of daemon that basic initialization is complete * In particular we're ready to accept net connections & have * written the pidfile */ if (statuswrite != -1) { char status = 0; while (write(statuswrite, &status, 1) == -1 && errno == EINTR) ; VIR_FORCE_CLOSE(statuswrite); } /* Start accepting new clients from network */ virNetDaemonUpdateServices(logDaemon->dmn, true); virNetDaemonRun(logDaemon->dmn); if (execRestart && virLogDaemonPreExecRestart(state_file, logDaemon->dmn, argv) < 0) ret = VIR_DAEMON_ERR_REEXEC; else ret = 0; cleanup: virObjectUnref(logProgram); virObjectUnref(adminProgram); virObjectUnref(logSrv); virObjectUnref(adminSrv); virLogDaemonFree(logDaemon); if (statuswrite != -1) { if (ret != 0) { /* Tell parent of daemon what failed */ char status = ret; while (write(statuswrite, &status, 1) == -1 && errno == EINTR) ; } VIR_FORCE_CLOSE(statuswrite); } if (pid_file_fd != -1) virPidFileReleasePath(pid_file, pid_file_fd); VIR_FREE(pid_file); VIR_FREE(sock_file); VIR_FREE(admin_sock_file); VIR_FREE(state_file); VIR_FREE(run_dir); VIR_FREE(remote_config_file); virLogDaemonConfigFree(config); return ret; }