diff --git a/qapi-schema-guest.json b/qapi-schema-guest.json index 692b570675..d4055d262a 100644 --- a/qapi-schema-guest.json +++ b/qapi-schema-guest.json @@ -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: diff --git a/qapi/qmp-core.h b/qapi/qmp-core.h index 431ddbb337..b0f64ba1ee 100644 --- a/qapi/qmp-core.h +++ b/qapi/qmp-core.h @@ -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); diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c index 43f640a95e..122c1a29ba 100644 --- a/qapi/qmp-dispatch.c +++ b/qapi/qmp-dispatch.c @@ -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; } diff --git a/qapi/qmp-registry.c b/qapi/qmp-registry.c index 43d5cdeb64..5414613377 100644 --- a/qapi/qmp-registry.c +++ b/qapi/qmp-registry.c @@ -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); } diff --git a/qemu-ga.c b/qemu-ga.c index 680997ebd3..8199da789c 100644 --- a/qemu-ga.c +++ b/qemu-ga.c @@ -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 \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; } } diff --git a/qga/commands-posix.c b/qga/commands-posix.c index e448431c66..7664be10a0 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -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) diff --git a/qga/commands.c b/qga/commands.c index 5bcceaae34..46b0b083bc 100644 --- a/qga/commands.c +++ b/qga/commands.c @@ -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) { diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h index bbb8b9b125..49a7abee95 100644 --- a/qga/guest-agent-core.h +++ b/qga/guest-agent-core.h @@ -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 diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py index 0b4f0a0fe1..9eed40e18a 100644 --- a/scripts/qapi-commands.py +++ b/scripts/qapi-commands.py @@ -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)