#include <syslog.h>
#include <sys/wait.h>
#endif
-#include "qemu-common.h"
+#include "qemu/help-texts.h"
#include "qapi/qmp/json-parser.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qjson.h"
#include "guest-agent-core.h"
#include "qga-qapi-init-commands.h"
-#include "qapi/qmp/qerror.h"
#include "qapi/error.h"
#include "channel.h"
-#include "qemu/bswap.h"
#include "qemu/cutils.h"
#include "qemu/help_option.h"
#include "qemu/sockets.h"
#include "qga/service-win32.h"
#include "qga/vss-win32.h"
#endif
-#ifdef __linux__
-#include <linux/fs.h>
-#ifdef FIFREEZE
-#define CONFIG_FSFREEZE
-#endif
-#endif
+#include "commands-common.h"
#ifndef _WIN32
+#ifdef CONFIG_BSD
+#define QGA_VIRTIO_PATH_DEFAULT "/dev/vtcon/org.qemu.guest_agent.0"
+#else /* CONFIG_BSD */
#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent.0"
-#define QGA_STATE_RELATIVE_DIR "run"
+#endif /* CONFIG_BSD */
#define QGA_SERIAL_PATH_DEFAULT "/dev/ttyS0"
+#define QGA_STATE_RELATIVE_DIR "run"
#else
#define QGA_VIRTIO_PATH_DEFAULT "\\\\.\\Global\\org.qemu.guest_agent.0"
#define QGA_STATE_RELATIVE_DIR "qemu-ga"
#ifdef _WIN32
GAService service;
HANDLE wakeup_event;
+ HANDLE event_log;
#endif
bool delimit_response;
bool frozen;
- GList *blacklist;
+ GList *blockedrpcs;
+ GList *allowedrpcs;
char *state_filepath_isfrozen;
struct {
const char *log_filepath;
QmpCommandList ga_commands;
/* commands that are safe to issue while filesystems are frozen */
-static const char *ga_freeze_whitelist[] = {
+static const char *ga_freeze_allowlist[] = {
"guest-ping",
"guest-info",
"guest-sync",
static void
init_dfl_pathnames(void)
{
+ g_autofree char *state = qemu_get_local_state_dir();
+
g_assert(dfl_pathnames.state_dir == NULL);
g_assert(dfl_pathnames.pidfile == NULL);
- dfl_pathnames.state_dir = qemu_get_local_state_pathname(
- QGA_STATE_RELATIVE_DIR);
- dfl_pathnames.pidfile = qemu_get_local_state_pathname(
- QGA_STATE_RELATIVE_DIR G_DIR_SEPARATOR_S "qemu-ga.pid");
+ dfl_pathnames.state_dir = g_build_filename(state, QGA_STATE_RELATIVE_DIR, NULL);
+ dfl_pathnames.pidfile = g_build_filename(state, QGA_STATE_RELATIVE_DIR, "qemu-ga.pid", NULL);
}
static void quit_handler(int sig)
static void usage(const char *cmd)
{
+#ifdef CONFIG_FSFREEZE
+ g_autofree char *fsfreeze_hook = get_relocated_path(QGA_FSFREEZE_HOOK_DEFAULT);
+#endif
+
printf(
"Usage: %s [-m <method> -p <path>] [<options>]\n"
"QEMU Guest Agent " QEMU_FULL_VERSION "\n"
#ifdef _WIN32
" -s, --service service commands: install, uninstall, vss-install, vss-uninstall\n"
#endif
-" -b, --blacklist comma-separated list of RPCs to disable (no spaces, \"?\"\n"
-" to list available RPCs)\n"
+" -b, --block-rpcs comma-separated list of RPCs to disable (no spaces,\n"
+" use \"help\" to list available RPCs)\n"
+" -a, --allow-rpcs comma-separated list of RPCs to enable (no spaces,\n"
+" use \"help\" to list available RPCs)\n"
" -D, --dump-conf dump a qemu-ga config file based on current config\n"
" options / command-line parameters to stdout\n"
" -r, --retry-path attempt re-opening path if it's unavailable or closed\n"
, cmd, QGA_VIRTIO_PATH_DEFAULT, QGA_SERIAL_PATH_DEFAULT,
dfl_pathnames.pidfile,
#ifdef CONFIG_FSFREEZE
- QGA_FSFREEZE_HOOK_DEFAULT,
+ fsfreeze_hook,
#endif
dfl_pathnames.state_dir);
}
s->logging_enabled = true;
}
+static int glib_log_level_to_system(int level)
+{
+ switch (level) {
+#ifndef _WIN32
+ case G_LOG_LEVEL_ERROR:
+ return LOG_ERR;
+ case G_LOG_LEVEL_CRITICAL:
+ return LOG_CRIT;
+ case G_LOG_LEVEL_WARNING:
+ return LOG_WARNING;
+ case G_LOG_LEVEL_MESSAGE:
+ return LOG_NOTICE;
+ case G_LOG_LEVEL_DEBUG:
+ return LOG_DEBUG;
+ case G_LOG_LEVEL_INFO:
+ default:
+ return LOG_INFO;
+#else
+ case G_LOG_LEVEL_ERROR:
+ case G_LOG_LEVEL_CRITICAL:
+ return EVENTLOG_ERROR_TYPE;
+ case G_LOG_LEVEL_WARNING:
+ return EVENTLOG_WARNING_TYPE;
+ case G_LOG_LEVEL_MESSAGE:
+ case G_LOG_LEVEL_INFO:
+ case G_LOG_LEVEL_DEBUG:
+ default:
+ return EVENTLOG_INFORMATION_TYPE;
+#endif
+ }
+}
+
static void ga_log(const gchar *domain, GLogLevelFlags level,
const gchar *msg, gpointer opaque)
{
GAState *s = opaque;
- GTimeVal time;
const char *level_str = ga_log_level_str(level);
if (!ga_logging_enabled(s)) {
}
level &= G_LOG_LEVEL_MASK;
-#ifndef _WIN32
if (g_strcmp0(domain, "syslog") == 0) {
- syslog(LOG_INFO, "%s: %s", level_str, msg);
- } else if (level & s->log_level) {
+#ifndef _WIN32
+ syslog(glib_log_level_to_system(level), "%s: %s", level_str, msg);
#else
- if (level & s->log_level) {
+ ReportEvent(s->event_log, glib_log_level_to_system(level),
+ 0, 1, NULL, 1, 0, &msg, NULL);
#endif
- g_get_current_time(&time);
- fprintf(s->log_file,
- "%lu.%lu: %s: %s\n", time.tv_sec, time.tv_usec, level_str, msg);
+ } else if (level & s->log_level) {
+ g_autoptr(GDateTime) now = g_date_time_new_now_utc();
+ g_autofree char *nowstr = g_date_time_format(now, "%s.%f");
+ fprintf(s->log_file, "%s: %s: %s\n", nowstr, level_str, msg);
fflush(s->log_file);
}
}
}
/* disable commands that aren't safe for fsfreeze */
-static void ga_disable_non_whitelisted(const QmpCommand *cmd, void *opaque)
+static void ga_disable_not_allowed_freeze(const QmpCommand *cmd, void *opaque)
{
- bool whitelisted = false;
+ bool allowed = false;
int i = 0;
const char *name = qmp_command_name(cmd);
- while (ga_freeze_whitelist[i] != NULL) {
- if (strcmp(name, ga_freeze_whitelist[i]) == 0) {
- whitelisted = true;
+ while (ga_freeze_allowlist[i] != NULL) {
+ if (strcmp(name, ga_freeze_allowlist[i]) == 0) {
+ allowed = true;
}
i++;
}
- if (!whitelisted) {
+ if (!allowed) {
g_debug("disabling command: %s", name);
qmp_disable_command(&ga_commands, name, "the agent is in frozen state");
}
}
-/* [re-]enable all commands, except those explicitly blacklisted by user */
-static void ga_enable_non_blacklisted(const QmpCommand *cmd, void *opaque)
+/* [re-]enable all commands, except those explicitly blocked by user */
+static void ga_enable_non_blocked(const QmpCommand *cmd, void *opaque)
{
- GList *blacklist = opaque;
+ GAState *s = opaque;
+ GList *blockedrpcs = s->blockedrpcs;
+ GList *allowedrpcs = s->allowedrpcs;
const char *name = qmp_command_name(cmd);
- if (g_list_find_custom(blacklist, name, ga_strcmp) == NULL &&
- !qmp_command_is_enabled(cmd)) {
+ if (g_list_find_custom(blockedrpcs, name, ga_strcmp) == NULL) {
+ if (qmp_command_is_enabled(cmd)) {
+ return;
+ }
+
+ if (allowedrpcs &&
+ g_list_find_custom(allowedrpcs, name, ga_strcmp) == NULL) {
+ return;
+ }
+
g_debug("enabling command: %s", name);
qmp_enable_command(&ga_commands, name);
}
}
+/* disable commands that aren't allowed */
+static void ga_disable_not_allowed(const QmpCommand *cmd, void *opaque)
+{
+ GList *allowedrpcs = opaque;
+ const char *name = qmp_command_name(cmd);
+
+ if (g_list_find_custom(allowedrpcs, name, ga_strcmp) == NULL) {
+ g_debug("disabling command: %s", name);
+ qmp_disable_command(&ga_commands, name, "the command is not allowed");
+ }
+}
+
static bool ga_create_file(const char *path)
{
int fd = open(path, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR);
if (ga_is_frozen(s)) {
return;
}
- /* disable all non-whitelisted (for frozen state) commands */
- qmp_for_each_command(&ga_commands, ga_disable_non_whitelisted, NULL);
+ /* disable all forbidden (for frozen state) commands */
+ qmp_for_each_command(&ga_commands, ga_disable_not_allowed_freeze, NULL);
g_warning("disabling logging due to filesystem freeze");
ga_disable_logging(s);
s->frozen = true;
s->deferred_options.pid_filepath = NULL;
}
- /* enable all disabled, non-blacklisted commands */
- qmp_for_each_command(&ga_commands, ga_enable_non_blacklisted, s->blacklist);
+ /* enable all disabled, non-blocked and allowed commands */
+ qmp_for_each_command(&ga_commands, ga_enable_non_blocked, s);
s->frozen = false;
if (!ga_delete_file(s->state_filepath_isfrozen)) {
g_warning("unable to delete %s, fsfreeze may not function properly",
* host-side chardev. sleep a bit to mitigate this
*/
if (s->virtio) {
- usleep(100 * 1000);
+ g_usleep(G_USEC_PER_SEC / 10);
}
return true;
default:
int64_t handle;
g_assert(s->pstate_filepath);
- /* we blacklist commands and avoid operations that potentially require
+ /*
+ * We block commands and avoid operations that potentially require
* writing to disk when we're in a frozen state. this includes opening
* new files, so we should never get here in that situation
*/
#ifdef _WIN32
const char *service;
#endif
- gchar *bliststr; /* blacklist may point to this string */
- GList *blacklist;
+ gchar *bliststr; /* blockedrpcs may point to this string */
+ gchar *aliststr; /* allowedrpcs may point to this string */
+ GList *blockedrpcs;
+ GList *allowedrpcs;
int daemonize;
GLogLevelFlags log_level;
int dumpconf;
GError *gerr = NULL;
GKeyFile *keyfile;
g_autofree char *conf = g_strdup(g_getenv("QGA_CONF")) ?: get_relocated_path(QGA_CONF_DEFAULT);
+ const gchar *blockrpcs_key = "block-rpcs";
/* read system config */
keyfile = g_key_file_new();
config->retry_path =
g_key_file_get_boolean(keyfile, "general", "retry-path", &gerr);
}
+
if (g_key_file_has_key(keyfile, "general", "blacklist", NULL)) {
+ g_warning("config using deprecated 'blacklist' key, should be replaced"
+ " with the 'block-rpcs' key.");
+ blockrpcs_key = "blacklist";
+ }
+ if (g_key_file_has_key(keyfile, "general", blockrpcs_key, NULL)) {
config->bliststr =
- g_key_file_get_string(keyfile, "general", "blacklist", &gerr);
- config->blacklist = g_list_concat(config->blacklist,
+ g_key_file_get_string(keyfile, "general", blockrpcs_key, &gerr);
+ config->blockedrpcs = g_list_concat(config->blockedrpcs,
split_list(config->bliststr, ","));
}
+ if (g_key_file_has_key(keyfile, "general", "allow-rpcs", NULL)) {
+ config->aliststr =
+ g_key_file_get_string(keyfile, "general", "allow-rpcs", &gerr);
+ config->allowedrpcs = g_list_concat(config->allowedrpcs,
+ split_list(config->aliststr, ","));
+ }
+
+ if (g_key_file_has_key(keyfile, "general", blockrpcs_key, NULL) &&
+ g_key_file_has_key(keyfile, "general", "allow-rpcs", NULL)) {
+ g_critical("wrong config, using 'block-rpcs' and 'allow-rpcs' keys at"
+ " the same time is not allowed");
+ exit(EXIT_FAILURE);
+ }
end:
g_key_file_free(keyfile);
config->log_level == G_LOG_LEVEL_MASK);
g_key_file_set_boolean(keyfile, "general", "retry-path",
config->retry_path);
- tmp = list_join(config->blacklist, ',');
- g_key_file_set_string(keyfile, "general", "blacklist", tmp);
+ tmp = list_join(config->blockedrpcs, ',');
+ g_key_file_set_string(keyfile, "general", "block-rpcs", tmp);
+ g_free(tmp);
+ tmp = list_join(config->allowedrpcs, ',');
+ g_key_file_set_string(keyfile, "general", "allow-rpcs", tmp);
g_free(tmp);
tmp = g_key_file_to_data(keyfile, NULL, &error);
static void config_parse(GAConfig *config, int argc, char **argv)
{
- const char *sopt = "hVvdm:p:l:f:F::b:s:t:Dr";
+ const char *sopt = "hVvdm:p:l:f:F::b:a:s:t:Dr";
int opt_ind = 0, ch;
+ bool block_rpcs = false, allow_rpcs = false;
const struct option lopt[] = {
{ "help", 0, NULL, 'h' },
{ "version", 0, NULL, 'V' },
{ "method", 1, NULL, 'm' },
{ "path", 1, NULL, 'p' },
{ "daemonize", 0, NULL, 'd' },
- { "blacklist", 1, NULL, 'b' },
+ { "block-rpcs", 1, NULL, 'b' },
+ { "blacklist", 1, NULL, 'b' }, /* deprecated alias for 'block-rpcs' */
+ { "allow-rpcs", 1, NULL, 'a' },
#ifdef _WIN32
{ "service", 1, NULL, 's' },
#endif
qmp_for_each_command(&ga_commands, ga_print_cmd, NULL);
exit(EXIT_SUCCESS);
}
- config->blacklist = g_list_concat(config->blacklist,
- split_list(optarg, ","));
+ config->blockedrpcs = g_list_concat(config->blockedrpcs,
+ split_list(optarg, ","));
+ block_rpcs = true;
+ break;
+ }
+ case 'a': {
+ if (is_help_option(optarg)) {
+ qmp_for_each_command(&ga_commands, ga_print_cmd, NULL);
+ exit(EXIT_SUCCESS);
+ }
+ config->allowedrpcs = g_list_concat(config->allowedrpcs,
+ split_list(optarg, ","));
+ allow_rpcs = true;
break;
}
#ifdef _WIN32
exit(EXIT_FAILURE);
}
}
+
+ if (block_rpcs && allow_rpcs) {
+ g_critical("wrong commandline, using --block-rpcs and --allow-rpcs at the"
+ " same time is not allowed");
+ exit(EXIT_FAILURE);
+ }
}
static void config_free(GAConfig *config)
g_free(config->state_dir);
g_free(config->channel_path);
g_free(config->bliststr);
+ g_free(config->aliststr);
#ifdef CONFIG_FSFREEZE
g_free(config->fsfreeze_hook);
#endif
- g_list_free_full(config->blacklist, g_free);
+ g_list_free_full(config->blockedrpcs, g_free);
+ g_list_free_full(config->allowedrpcs, g_free);
g_free(config);
}
/* check if a previous instance of qemu-ga exited with filesystems' state
* marked as frozen. this could be a stale value (a non-qemu-ga process
* or reboot may have since unfrozen them), but better to require an
- * uneeded unfreeze than to risk hanging on start-up
+ * unneeded unfreeze than to risk hanging on start-up
*/
struct stat st;
if (stat(s->state_filepath_isfrozen, &st) == -1) {
g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
ga_enable_logging(s);
+ g_debug("Guest agent version %s started", QEMU_FULL_VERSION);
+
#ifdef _WIN32
+ s->event_log = RegisterEventSource(NULL, "qemu-ga");
+ if (!s->event_log) {
+ g_autofree gchar *errmsg = g_win32_error_message(GetLastError());
+ g_critical("unable to register event source: %s", errmsg);
+ return NULL;
+ }
+
/* On win32 the state directory is application specific (be it the default
* or a user override). We got past the command line parsing; let's create
* the directory (with any intermediate directories). If we run into an
s->deferred_options.log_filepath = config->log_filepath;
}
ga_disable_logging(s);
- qmp_for_each_command(&ga_commands, ga_disable_non_whitelisted, NULL);
+ qmp_for_each_command(&ga_commands, ga_disable_not_allowed_freeze, NULL);
} else {
if (config->daemonize) {
become_daemon(config->pid_filepath);
return NULL;
}
- config->blacklist = ga_command_blacklist_init(config->blacklist);
- if (config->blacklist) {
- GList *l = config->blacklist;
- s->blacklist = config->blacklist;
+ if (config->allowedrpcs) {
+ qmp_for_each_command(&ga_commands, ga_disable_not_allowed, config->allowedrpcs);
+ s->allowedrpcs = config->allowedrpcs;
+ }
+
+ /*
+ * Some commands can be blocked due to system limitation.
+ * Initialize blockedrpcs list even if allowedrpcs specified.
+ */
+ config->blockedrpcs = ga_command_init_blockedrpcs(config->blockedrpcs);
+ if (config->blockedrpcs) {
+ GList *l = config->blockedrpcs;
+ s->blockedrpcs = config->blockedrpcs;
do {
g_debug("disabling command: %s", (char *)l->data);
qmp_disable_command(&ga_commands, l->data, NULL);
{
#ifdef _WIN32
CloseHandle(s->wakeup_event);
+ CloseHandle(s->event_log);
#endif
if (s->command_state) {
ga_command_state_cleanup_all(s->command_state);