Merge remote-tracking branch 'mdroth/qga-pull-5-15-12' into staging

* mdroth/qga-pull-5-15-12:
  qemu-ga: align versioning with QEMU_VERSION
  qemu-ga: fix segv after failure to open log file
  qemu-ga: guest-shutdown: use only async-signal-safe functions
  qemu-ga: guest-shutdown: become synchronous
  qemu-ga: guest-suspend: make the API synchronous
  qemu-ga: become_daemon(): reopen standard fds to /dev/null
  qemu-ga: make reopen_fd_to_null() public
  qemu-ga: guest-suspend-hybrid: don't emit a success response
  qemu-ga: guest-suspend-ram: don't emit a success response
  qemu-ga: guest-suspend-disk: don't emit a success response
  qemu-ga: guest-shutdown: don't emit a success response
  qemu-ga: don't warn on no command return
  qapi: add support for command options
This commit is contained in:
Anthony Liguori 2012-05-21 15:31:31 -05:00
commit b4f1a7ca72
9 changed files with 172 additions and 160 deletions

View File

@ -126,16 +126,19 @@
# @guest-shutdown: # @guest-shutdown:
# #
# Initiate guest-activated shutdown. Note: this is an asynchronous # Initiate guest-activated shutdown. Note: this is an asynchronous
# shutdown request, with no guaruntee of successful shutdown. Errors # shutdown request, with no guarantee of successful shutdown.
# will be logged to guest's syslog.
# #
# @mode: #optional "halt", "powerdown" (default), or "reboot" # @mode: #optional "halt", "powerdown" (default), or "reboot"
# #
# Returns: Nothing on success # This command does NOT return a response on success. Success condition
# is indicated by the VM exiting with a zero exit status or, when
# running with --no-shutdown, by issuing the query-status QMP command
# to confirm the VM status is "shutdown".
# #
# Since: 0.15.0 # Since: 0.15.0
## ##
{ 'command': 'guest-shutdown', 'data': { '*mode': 'str' } } { 'command': 'guest-shutdown', 'data': { '*mode': 'str' },
'success-response': 'no' }
## ##
# @guest-file-open: # @guest-file-open:
@ -359,17 +362,21 @@
# For the best results it's strongly recommended to have the pm-utils # For the best results it's strongly recommended to have the pm-utils
# package installed in the guest. # package installed in the guest.
# #
# Returns: nothing on success # This command does NOT return a response on success. There is a high chance
# the command succeeded if the VM exits with a zero exit status or, when
# running with --no-shutdown, by issuing the query-status QMP command to
# to confirm the VM status is "shutdown". However, the VM could also exit
# (or set its status to "shutdown") due to other reasons.
#
# The following errors may be returned:
# If suspend to disk is not supported, Unsupported # If suspend to disk is not supported, Unsupported
# #
# Notes: o This is an asynchronous request. There's no guarantee a response # Notes: It's strongly recommended to issue the guest-sync command before
# will be sent # sending commands when the guest resumes
# o It's strongly recommended to issue the guest-sync command before
# sending commands when the guest resumes
# #
# Since: 1.1 # Since: 1.1
## ##
{ 'command': 'guest-suspend-disk' } { 'command': 'guest-suspend-disk', 'success-response': 'no' }
## ##
# @guest-suspend-ram # @guest-suspend-ram
@ -387,17 +394,21 @@
# command. Thus, it's *required* to query QEMU for the presence of the # command. Thus, it's *required* to query QEMU for the presence of the
# 'system_wakeup' command before issuing guest-suspend-ram. # 'system_wakeup' command before issuing guest-suspend-ram.
# #
# Returns: nothing on success # This command does NOT return a response on success. There are two options
# to check for success:
# 1. Wait for the SUSPEND QMP event from QEMU
# 2. Issue the query-status QMP command to confirm the VM status is
# "suspended"
#
# The following errors may be returned:
# If suspend to ram is not supported, Unsupported # If suspend to ram is not supported, Unsupported
# #
# Notes: o This is an asynchronous request. There's no guarantee a response # Notes: It's strongly recommended to issue the guest-sync command before
# will be sent # sending commands when the guest resumes
# o It's strongly recommended to issue the guest-sync command before
# sending commands when the guest resumes
# #
# Since: 1.1 # Since: 1.1
## ##
{ 'command': 'guest-suspend-ram' } { 'command': 'guest-suspend-ram', 'success-response': 'no' }
## ##
# @guest-suspend-hybrid # @guest-suspend-hybrid
@ -410,17 +421,21 @@
# command. Thus, it's *required* to query QEMU for the presence of the # command. Thus, it's *required* to query QEMU for the presence of the
# 'system_wakeup' command before issuing guest-suspend-hybrid. # 'system_wakeup' command before issuing guest-suspend-hybrid.
# #
# Returns: nothing on success # This command does NOT return a response on success. There are two options
# to check for success:
# 1. Wait for the SUSPEND QMP event from QEMU
# 2. Issue the query-status QMP command to confirm the VM status is
# "suspended"
#
# The following errors may be returned:
# If hybrid suspend is not supported, Unsupported # If hybrid suspend is not supported, Unsupported
# #
# Notes: o This is an asynchronous request. There's no guarantee a response # Notes: It's strongly recommended to issue the guest-sync command before
# will be sent # sending commands when the guest resumes
# o It's strongly recommended to issue the guest-sync command before
# sending commands when the guest resumes
# #
# Since: 1.1 # Since: 1.1
## ##
{ 'command': 'guest-suspend-hybrid' } { 'command': 'guest-suspend-hybrid', 'success-response': 'no' }
## ##
# @GuestIpAddressType: # @GuestIpAddressType:

View File

@ -25,16 +25,24 @@ typedef enum QmpCommandType
QCT_NORMAL, QCT_NORMAL,
} QmpCommandType; } QmpCommandType;
typedef enum QmpCommandOptions
{
QCO_NO_OPTIONS = 0x0,
QCO_NO_SUCCESS_RESP = 0x1,
} QmpCommandOptions;
typedef struct QmpCommand typedef struct QmpCommand
{ {
const char *name; const char *name;
QmpCommandType type; QmpCommandType type;
QmpCommandFunc *fn; QmpCommandFunc *fn;
QmpCommandOptions options;
QTAILQ_ENTRY(QmpCommand) node; QTAILQ_ENTRY(QmpCommand) node;
bool enabled; bool enabled;
} QmpCommand; } QmpCommand;
void qmp_register_command(const char *name, QmpCommandFunc *fn); void qmp_register_command(const char *name, QmpCommandFunc *fn,
QmpCommandOptions options);
QmpCommand *qmp_find_command(const char *name); QmpCommand *qmp_find_command(const char *name);
QObject *qmp_dispatch(QObject *request); QObject *qmp_dispatch(QObject *request);
void qmp_disable_command(const char *name); void qmp_disable_command(const char *name);

View File

@ -94,8 +94,12 @@ static QObject *do_qmp_dispatch(QObject *request, Error **errp)
switch (cmd->type) { switch (cmd->type) {
case QCT_NORMAL: case QCT_NORMAL:
cmd->fn(args, &ret, errp); cmd->fn(args, &ret, errp);
if (!error_is_set(errp) && ret == NULL) { if (!error_is_set(errp)) {
ret = QOBJECT(qdict_new()); if (cmd->options & QCO_NO_SUCCESS_RESP) {
g_assert(!ret);
} else if (!ret) {
ret = QOBJECT(qdict_new());
}
} }
break; break;
} }

View File

@ -17,7 +17,8 @@
static QTAILQ_HEAD(QmpCommandList, QmpCommand) qmp_commands = static QTAILQ_HEAD(QmpCommandList, QmpCommand) qmp_commands =
QTAILQ_HEAD_INITIALIZER(qmp_commands); QTAILQ_HEAD_INITIALIZER(qmp_commands);
void qmp_register_command(const char *name, QmpCommandFunc *fn) void qmp_register_command(const char *name, QmpCommandFunc *fn,
QmpCommandOptions options)
{ {
QmpCommand *cmd = g_malloc0(sizeof(*cmd)); QmpCommand *cmd = g_malloc0(sizeof(*cmd));
@ -25,6 +26,7 @@ void qmp_register_command(const char *name, QmpCommandFunc *fn)
cmd->type = QCT_NORMAL; cmd->type = QCT_NORMAL;
cmd->fn = fn; cmd->fn = fn;
cmd->enabled = true; cmd->enabled = true;
cmd->options = options;
QTAILQ_INSERT_TAIL(&qmp_commands, cmd, node); QTAILQ_INSERT_TAIL(&qmp_commands, cmd, node);
} }

View File

@ -104,16 +104,9 @@ static void quit_handler(int sig)
} }
#ifndef _WIN32 #ifndef _WIN32
/* reap _all_ terminated children */
static void child_handler(int sig)
{
int status;
while (waitpid(-1, &status, WNOHANG) > 0) /* NOTHING */;
}
static gboolean register_signal_handlers(void) static gboolean register_signal_handlers(void)
{ {
struct sigaction sigact, sigact_chld; struct sigaction sigact;
int ret; int ret;
memset(&sigact, 0, sizeof(struct sigaction)); memset(&sigact, 0, sizeof(struct sigaction));
@ -130,15 +123,24 @@ static gboolean register_signal_handlers(void)
return false; return false;
} }
memset(&sigact_chld, 0, sizeof(struct sigaction)); return true;
sigact_chld.sa_handler = child_handler; }
sigact_chld.sa_flags = SA_NOCLDSTOP;
ret = sigaction(SIGCHLD, &sigact_chld, NULL); /* TODO: use this in place of all post-fork() fclose(std*) callers */
if (ret == -1) { void reopen_fd_to_null(int fd)
g_error("error configuring signal handler: %s", strerror(errno)); {
int nullfd;
nullfd = open("/dev/null", O_RDWR);
if (nullfd < 0) {
return;
} }
return true; dup2(nullfd, fd);
if (nullfd != fd) {
close(nullfd);
}
} }
#endif #endif
@ -167,7 +169,7 @@ static void usage(const char *cmd)
" -h, --help display this help and exit\n" " -h, --help display this help and exit\n"
"\n" "\n"
"Report bugs to <mdroth@linux.vnet.ibm.com>\n" "Report bugs to <mdroth@linux.vnet.ibm.com>\n"
, cmd, QGA_VERSION, QGA_VIRTIO_PATH_DEFAULT, QGA_PIDFILE_DEFAULT, , cmd, QEMU_VERSION, QGA_VIRTIO_PATH_DEFAULT, QGA_PIDFILE_DEFAULT,
QGA_STATEDIR_DEFAULT); QGA_STATEDIR_DEFAULT);
} }
@ -428,9 +430,9 @@ static void become_daemon(const char *pidfile)
goto fail; goto fail;
} }
close(STDIN_FILENO); reopen_fd_to_null(STDIN_FILENO);
close(STDOUT_FILENO); reopen_fd_to_null(STDOUT_FILENO);
close(STDERR_FILENO); reopen_fd_to_null(STDERR_FILENO);
return; return;
fail: fail:
@ -488,8 +490,6 @@ static void process_command(GAState *s, QDict *req)
g_warning("error sending response: %s", strerror(ret)); g_warning("error sending response: %s", strerror(ret));
} }
qobject_decref(rsp); qobject_decref(rsp);
} else {
g_warning("error getting response");
} }
} }
@ -729,7 +729,7 @@ int main(int argc, char **argv)
log_level = G_LOG_LEVEL_MASK; log_level = G_LOG_LEVEL_MASK;
break; break;
case 'V': case 'V':
printf("QEMU Guest Agent %s\n", QGA_VERSION); printf("QEMU Guest Agent %s\n", QEMU_VERSION);
return 0; return 0;
case 'd': case 'd':
daemonize = 1; daemonize = 1;
@ -836,12 +836,13 @@ int main(int argc, char **argv)
become_daemon(pid_filepath); become_daemon(pid_filepath);
} }
if (log_filepath) { if (log_filepath) {
s->log_file = fopen(log_filepath, "a"); FILE *log_file = fopen(log_filepath, "a");
if (!s->log_file) { if (!log_file) {
g_critical("unable to open specified log file: %s", g_critical("unable to open specified log file: %s",
strerror(errno)); strerror(errno));
goto out_bad; goto out_bad;
} }
s->log_file = log_file;
} }
} }

View File

@ -34,29 +34,11 @@
#endif #endif
#endif #endif
#if defined(__linux__)
/* TODO: use this in place of all post-fork() fclose(std*) callers */
static void reopen_fd_to_null(int fd)
{
int nullfd;
nullfd = open("/dev/null", O_RDWR);
if (nullfd < 0) {
return;
}
dup2(nullfd, fd);
if (nullfd != fd) {
close(nullfd);
}
}
#endif /* defined(__linux__) */
void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
{ {
int ret;
const char *shutdown_flag; const char *shutdown_flag;
pid_t rpid, pid;
int status;
slog("guest-shutdown called, mode: %s", mode); slog("guest-shutdown called, mode: %s", mode);
if (!has_mode || strcmp(mode, "powerdown") == 0) { if (!has_mode || strcmp(mode, "powerdown") == 0) {
@ -71,23 +53,30 @@ void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
return; return;
} }
ret = fork(); pid = fork();
if (ret == 0) { if (pid == 0) {
/* child, start the shutdown */ /* child, start the shutdown */
setsid(); setsid();
fclose(stdin); reopen_fd_to_null(0);
fclose(stdout); reopen_fd_to_null(1);
fclose(stderr); reopen_fd_to_null(2);
ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0", execle("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
"hypervisor initiated shutdown", (char*)NULL); "hypervisor initiated shutdown", (char*)NULL, environ);
if (ret) { _exit(EXIT_FAILURE);
slog("guest-shutdown failed: %s", strerror(errno)); } else if (pid < 0) {
} goto exit_err;
exit(!!ret);
} else if (ret < 0) {
error_set(err, QERR_UNDEFINED_ERROR);
} }
do {
rpid = waitpid(pid, &status, 0);
} while (rpid == -1 && errno == EINTR);
if (rpid == pid && WIFEXITED(status) && !WEXITSTATUS(status)) {
return;
}
exit_err:
error_set(err, QERR_UNDEFINED_ERROR);
} }
typedef struct GuestFileHandle { typedef struct GuestFileHandle {
@ -531,117 +520,88 @@ static void guest_fsfreeze_cleanup(void)
#define SUSPEND_SUPPORTED 0 #define SUSPEND_SUPPORTED 0
#define SUSPEND_NOT_SUPPORTED 1 #define SUSPEND_NOT_SUPPORTED 1
/**
* This function forks twice and the information about the mode support
* status is passed to the qemu-ga process via a pipe.
*
* This approach allows us to keep the way we reap terminated children
* in qemu-ga quite simple.
*/
static void bios_supports_mode(const char *pmutils_bin, const char *pmutils_arg, static void bios_supports_mode(const char *pmutils_bin, const char *pmutils_arg,
const char *sysfile_str, Error **err) const char *sysfile_str, Error **err)
{ {
pid_t pid;
ssize_t ret;
char *pmutils_path; char *pmutils_path;
int status, pipefds[2]; pid_t pid, rpid;
int status;
if (pipe(pipefds) < 0) {
error_set(err, QERR_UNDEFINED_ERROR);
return;
}
pmutils_path = g_find_program_in_path(pmutils_bin); pmutils_path = g_find_program_in_path(pmutils_bin);
pid = fork(); pid = fork();
if (!pid) { if (!pid) {
struct sigaction act; char buf[32]; /* hopefully big enough */
ssize_t ret;
memset(&act, 0, sizeof(act)); int fd;
act.sa_handler = SIG_DFL;
sigaction(SIGCHLD, &act, NULL);
setsid(); setsid();
close(pipefds[0]);
reopen_fd_to_null(0); reopen_fd_to_null(0);
reopen_fd_to_null(1); reopen_fd_to_null(1);
reopen_fd_to_null(2); reopen_fd_to_null(2);
pid = fork(); if (pmutils_path) {
if (!pid) { execle(pmutils_path, pmutils_bin, pmutils_arg, NULL, environ);
int fd; }
char buf[32]; /* hopefully big enough */
if (pmutils_path) { /*
execle(pmutils_path, pmutils_bin, pmutils_arg, NULL, environ); * If we get here either pm-utils is not installed or execle() has
} * failed. Let's try the manual method if the caller wants it.
*/
/*
* If we get here either pm-utils is not installed or execle() has
* failed. Let's try the manual method if the caller wants it.
*/
if (!sysfile_str) {
_exit(SUSPEND_NOT_SUPPORTED);
}
fd = open(LINUX_SYS_STATE_FILE, O_RDONLY);
if (fd < 0) {
_exit(SUSPEND_NOT_SUPPORTED);
}
ret = read(fd, buf, sizeof(buf)-1);
if (ret <= 0) {
_exit(SUSPEND_NOT_SUPPORTED);
}
buf[ret] = '\0';
if (strstr(buf, sysfile_str)) {
_exit(SUSPEND_SUPPORTED);
}
if (!sysfile_str) {
_exit(SUSPEND_NOT_SUPPORTED); _exit(SUSPEND_NOT_SUPPORTED);
} }
if (pid > 0) { fd = open(LINUX_SYS_STATE_FILE, O_RDONLY);
wait(&status); if (fd < 0) {
} else { _exit(SUSPEND_NOT_SUPPORTED);
status = SUSPEND_NOT_SUPPORTED;
} }
ret = write(pipefds[1], &status, sizeof(status)); ret = read(fd, buf, sizeof(buf)-1);
if (ret != sizeof(status)) { if (ret <= 0) {
_exit(EXIT_FAILURE); _exit(SUSPEND_NOT_SUPPORTED);
}
buf[ret] = '\0';
if (strstr(buf, sysfile_str)) {
_exit(SUSPEND_SUPPORTED);
} }
_exit(EXIT_SUCCESS); _exit(SUSPEND_NOT_SUPPORTED);
} }
close(pipefds[1]);
g_free(pmutils_path); g_free(pmutils_path);
if (pid < 0) { if (pid < 0) {
error_set(err, QERR_UNDEFINED_ERROR); goto undef_err;
goto out;
} }
ret = read(pipefds[0], &status, sizeof(status)); do {
if (ret == sizeof(status) && WIFEXITED(status) && rpid = waitpid(pid, &status, 0);
WEXITSTATUS(status) == SUSPEND_SUPPORTED) { } while (rpid == -1 && errno == EINTR);
goto out; if (rpid == pid && WIFEXITED(status)) {
switch (WEXITSTATUS(status)) {
case SUSPEND_SUPPORTED:
return;
case SUSPEND_NOT_SUPPORTED:
error_set(err, QERR_UNSUPPORTED);
return;
default:
goto undef_err;
}
} }
error_set(err, QERR_UNSUPPORTED); undef_err:
error_set(err, QERR_UNDEFINED_ERROR);
out:
close(pipefds[0]);
} }
static void guest_suspend(const char *pmutils_bin, const char *sysfile_str, static void guest_suspend(const char *pmutils_bin, const char *sysfile_str,
Error **err) Error **err)
{ {
pid_t pid;
char *pmutils_path; char *pmutils_path;
pid_t rpid, pid;
int status;
pmutils_path = g_find_program_in_path(pmutils_bin); pmutils_path = g_find_program_in_path(pmutils_bin);
@ -683,9 +643,18 @@ static void guest_suspend(const char *pmutils_bin, const char *sysfile_str,
g_free(pmutils_path); g_free(pmutils_path);
if (pid < 0) { if (pid < 0) {
error_set(err, QERR_UNDEFINED_ERROR); goto exit_err;
}
do {
rpid = waitpid(pid, &status, 0);
} while (rpid == -1 && errno == EINTR);
if (rpid == pid && WIFEXITED(status) && !WEXITSTATUS(status)) {
return; return;
} }
exit_err:
error_set(err, QERR_UNDEFINED_ERROR);
} }
void qmp_guest_suspend_disk(Error **err) void qmp_guest_suspend_disk(Error **err)

View File

@ -52,7 +52,7 @@ struct GuestAgentInfo *qmp_guest_info(Error **err)
GuestAgentCommandInfoList *cmd_info_list; GuestAgentCommandInfoList *cmd_info_list;
char **cmd_list_head, **cmd_list; char **cmd_list_head, **cmd_list;
info->version = g_strdup(QGA_VERSION); info->version = g_strdup(QEMU_VERSION);
cmd_list_head = cmd_list = qmp_get_command_list(); cmd_list_head = cmd_list = qmp_get_command_list();
if (*cmd_list_head == NULL) { if (*cmd_list_head == NULL) {

View File

@ -13,7 +13,6 @@
#include "qapi/qmp-core.h" #include "qapi/qmp-core.h"
#include "qemu-common.h" #include "qemu-common.h"
#define QGA_VERSION "1.0"
#define QGA_READ_COUNT_DEFAULT 4096 #define QGA_READ_COUNT_DEFAULT 4096
typedef struct GAState GAState; typedef struct GAState GAState;
@ -35,3 +34,7 @@ void ga_set_response_delimited(GAState *s);
bool ga_is_frozen(GAState *s); bool ga_is_frozen(GAState *s);
void ga_set_frozen(GAState *s); void ga_set_frozen(GAState *s);
void ga_unset_frozen(GAState *s); void ga_unset_frozen(GAState *s);
#ifndef _WIN32
void reopen_fd_to_null(int fd);
#endif

View File

@ -291,14 +291,24 @@ out:
return ret return ret
def option_value_matches(opt, val, cmd):
if opt in cmd and cmd[opt] == val:
return True
return False
def gen_registry(commands): def gen_registry(commands):
registry="" registry=""
push_indent() push_indent()
for cmd in commands: for cmd in commands:
options = 'QCO_NO_OPTIONS'
if option_value_matches('success-response', 'no', cmd):
options = 'QCO_NO_SUCCESS_RESP'
registry += mcgen(''' registry += mcgen('''
qmp_register_command("%(name)s", qmp_marshal_input_%(c_name)s); qmp_register_command("%(name)s", qmp_marshal_input_%(c_name)s, %(opts)s);
''', ''',
name=cmd['command'], c_name=c_fun(cmd['command'])) name=cmd['command'], c_name=c_fun(cmd['command']),
opts=options)
pop_indent() pop_indent()
ret = mcgen(''' ret = mcgen('''
static void qmp_init_marshal(void) static void qmp_init_marshal(void)