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:
#
# Initiate guest-activated shutdown. Note: this is an asynchronous
# shutdown request, with no guaruntee of successful shutdown. Errors
# will be logged to guest's syslog.
# shutdown request, with no guarantee of successful shutdown.
#
# @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
##
{ 'command': 'guest-shutdown', 'data': { '*mode': 'str' } }
{ 'command': 'guest-shutdown', 'data': { '*mode': 'str' },
'success-response': 'no' }
##
# @guest-file-open:
@ -359,17 +362,21 @@
# For the best results it's strongly recommended to have the pm-utils
# 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
#
# Notes: o This is an asynchronous request. There's no guarantee a response
# will be sent
# o It's strongly recommended to issue the guest-sync command before
# sending commands when the guest resumes
# Notes: It's strongly recommended to issue the guest-sync command before
# sending commands when the guest resumes
#
# Since: 1.1
##
{ 'command': 'guest-suspend-disk' }
{ 'command': 'guest-suspend-disk', 'success-response': 'no' }
##
# @guest-suspend-ram
@ -387,17 +394,21 @@
# command. Thus, it's *required* to query QEMU for the presence of the
# '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
#
# Notes: o This is an asynchronous request. There's no guarantee a response
# will be sent
# o It's strongly recommended to issue the guest-sync command before
# sending commands when the guest resumes
# Notes: It's strongly recommended to issue the guest-sync command before
# sending commands when the guest resumes
#
# Since: 1.1
##
{ 'command': 'guest-suspend-ram' }
{ 'command': 'guest-suspend-ram', 'success-response': 'no' }
##
# @guest-suspend-hybrid
@ -410,17 +421,21 @@
# command. Thus, it's *required* to query QEMU for the presence of the
# '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
#
# Notes: o This is an asynchronous request. There's no guarantee a response
# will be sent
# o It's strongly recommended to issue the guest-sync command before
# sending commands when the guest resumes
# Notes: It's strongly recommended to issue the guest-sync command before
# sending commands when the guest resumes
#
# Since: 1.1
##
{ 'command': 'guest-suspend-hybrid' }
{ 'command': 'guest-suspend-hybrid', 'success-response': 'no' }
##
# @GuestIpAddressType:

View File

@ -25,16 +25,24 @@ typedef enum QmpCommandType
QCT_NORMAL,
} QmpCommandType;
typedef enum QmpCommandOptions
{
QCO_NO_OPTIONS = 0x0,
QCO_NO_SUCCESS_RESP = 0x1,
} QmpCommandOptions;
typedef struct QmpCommand
{
const char *name;
QmpCommandType type;
QmpCommandFunc *fn;
QmpCommandOptions options;
QTAILQ_ENTRY(QmpCommand) node;
bool enabled;
} 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);
QObject *qmp_dispatch(QObject *request);
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) {
case QCT_NORMAL:
cmd->fn(args, &ret, errp);
if (!error_is_set(errp) && ret == NULL) {
ret = QOBJECT(qdict_new());
if (!error_is_set(errp)) {
if (cmd->options & QCO_NO_SUCCESS_RESP) {
g_assert(!ret);
} else if (!ret) {
ret = QOBJECT(qdict_new());
}
}
break;
}

View File

@ -17,7 +17,8 @@
static QTAILQ_HEAD(QmpCommandList, QmpCommand) 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));
@ -25,6 +26,7 @@ void qmp_register_command(const char *name, QmpCommandFunc *fn)
cmd->type = QCT_NORMAL;
cmd->fn = fn;
cmd->enabled = true;
cmd->options = options;
QTAILQ_INSERT_TAIL(&qmp_commands, cmd, node);
}

View File

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

View File

@ -34,29 +34,11 @@
#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)
{
int ret;
const char *shutdown_flag;
pid_t rpid, pid;
int status;
slog("guest-shutdown called, mode: %s", mode);
if (!has_mode || strcmp(mode, "powerdown") == 0) {
@ -71,23 +53,30 @@ void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
return;
}
ret = fork();
if (ret == 0) {
pid = fork();
if (pid == 0) {
/* child, start the shutdown */
setsid();
fclose(stdin);
fclose(stdout);
fclose(stderr);
reopen_fd_to_null(0);
reopen_fd_to_null(1);
reopen_fd_to_null(2);
ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
"hypervisor initiated shutdown", (char*)NULL);
if (ret) {
slog("guest-shutdown failed: %s", strerror(errno));
}
exit(!!ret);
} else if (ret < 0) {
error_set(err, QERR_UNDEFINED_ERROR);
execle("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
"hypervisor initiated shutdown", (char*)NULL, environ);
_exit(EXIT_FAILURE);
} else if (pid < 0) {
goto exit_err;
}
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 {
@ -531,117 +520,88 @@ static void guest_fsfreeze_cleanup(void)
#define SUSPEND_SUPPORTED 0
#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,
const char *sysfile_str, Error **err)
{
pid_t pid;
ssize_t ret;
char *pmutils_path;
int status, pipefds[2];
if (pipe(pipefds) < 0) {
error_set(err, QERR_UNDEFINED_ERROR);
return;
}
pid_t pid, rpid;
int status;
pmutils_path = g_find_program_in_path(pmutils_bin);
pid = fork();
if (!pid) {
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = SIG_DFL;
sigaction(SIGCHLD, &act, NULL);
char buf[32]; /* hopefully big enough */
ssize_t ret;
int fd;
setsid();
close(pipefds[0]);
reopen_fd_to_null(0);
reopen_fd_to_null(1);
reopen_fd_to_null(2);
pid = fork();
if (!pid) {
int fd;
char buf[32]; /* hopefully big enough */
if (pmutils_path) {
execle(pmutils_path, pmutils_bin, pmutils_arg, NULL, environ);
}
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 (!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 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);
}
if (pid > 0) {
wait(&status);
} else {
status = SUSPEND_NOT_SUPPORTED;
fd = open(LINUX_SYS_STATE_FILE, O_RDONLY);
if (fd < 0) {
_exit(SUSPEND_NOT_SUPPORTED);
}
ret = write(pipefds[1], &status, sizeof(status));
if (ret != sizeof(status)) {
_exit(EXIT_FAILURE);
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);
}
_exit(EXIT_SUCCESS);
_exit(SUSPEND_NOT_SUPPORTED);
}
close(pipefds[1]);
g_free(pmutils_path);
if (pid < 0) {
error_set(err, QERR_UNDEFINED_ERROR);
goto out;
goto undef_err;
}
ret = read(pipefds[0], &status, sizeof(status));
if (ret == sizeof(status) && WIFEXITED(status) &&
WEXITSTATUS(status) == SUSPEND_SUPPORTED) {
goto out;
do {
rpid = waitpid(pid, &status, 0);
} while (rpid == -1 && errno == EINTR);
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);
out:
close(pipefds[0]);
undef_err:
error_set(err, QERR_UNDEFINED_ERROR);
}
static void guest_suspend(const char *pmutils_bin, const char *sysfile_str,
Error **err)
{
pid_t pid;
char *pmutils_path;
pid_t rpid, pid;
int status;
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);
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;
}
exit_err:
error_set(err, QERR_UNDEFINED_ERROR);
}
void qmp_guest_suspend_disk(Error **err)

View File

@ -52,7 +52,7 @@ struct GuestAgentInfo *qmp_guest_info(Error **err)
GuestAgentCommandInfoList *cmd_info_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();
if (*cmd_list_head == NULL) {

View File

@ -13,7 +13,6 @@
#include "qapi/qmp-core.h"
#include "qemu-common.h"
#define QGA_VERSION "1.0"
#define QGA_READ_COUNT_DEFAULT 4096
typedef struct GAState GAState;
@ -35,3 +34,7 @@ void ga_set_response_delimited(GAState *s);
bool ga_is_frozen(GAState *s);
void ga_set_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
def option_value_matches(opt, val, cmd):
if opt in cmd and cmd[opt] == val:
return True
return False
def gen_registry(commands):
registry=""
push_indent()
for cmd in commands:
options = 'QCO_NO_OPTIONS'
if option_value_matches('success-response', 'no', cmd):
options = 'QCO_NO_SUCCESS_RESP'
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()
ret = mcgen('''
static void qmp_init_marshal(void)