This adds the initial set of QMP/QAPI commands provided by the guest agent:
guest-ping guest-file-open guest-file-read guest-file-write guest-file-seek guest-file-close The input/output specification for these commands are documented in the schema. Signed-off-by: Michael Roth <mdr...@linux.vnet.ibm.com> --- qapi-schema.json | 133 ++++++++++++++++++++- qga/guest-agent-commands.c | 284 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 415 insertions(+), 2 deletions(-) create mode 100644 qga/guest-agent-commands.c diff --git a/qapi-schema.json b/qapi-schema.json index 5292938..e2f209d 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -1387,10 +1387,139 @@ { 'enum': 'VirtioNetTxStrategy', 'data': ['bh', 'timer'] } { 'type': 'GuestInfo', 'data': {'*name': 'str', '*pid': 'int'} } +## +# @guest-ping: +# +# Ping the guest agent, a non-error return implies success +# +# Since: 0.15.0 +## { 'command': 'guest-ping' } -{ 'command': 'guest-view-file', 'data': { 'filename': 'str' }, - 'returns': 'str' } +## +# @guest-info: +# +# Get some information about the guest agent. +# +# Since: 0.15.0 +## +{ 'type': 'GuestAgentInfo', 'data': {'version': 'str', 'timeout_ms': 'int'} } +{ 'command': 'guest-info', + 'returns': 'GuestAgentInfo' } + +## +# @guest-shutdown: +# +# Initiate guest-activated shutdown +# +# @shutdown_mode: "halt", "powerdown", or "reboot" +# +# Returns: Nothing on success +# +# Since: 0.15.0 +## +{ 'command': 'guest-shutdown', 'data': { 'shutdown_mode': 'str' } } + +## +# @guest-file-open: +# +# Open a file in the guest and retrieve a file handle for it +# +# @filename: Full path to the file in the guest to open. +# +# @mode: #optional open mode, as per fopen(), "r" is the default. +# +# Returns: Guest file handle on success. +# If @filename cannot be opened, OpenFileFailed +# +# Since: 0.15.0 +## +{ 'command': 'guest-file-open', + 'data': { 'filename': 'str', 'mode': 'str' }, + 'returns': 'int' } + +## +# @guest-file-read: +# +# Read from an open file in the guest +# +# @filehandle: filehandle returned by guest-file-open +# +# @count: maximum number of bytes to read +# +# Returns: GuestFileRead on success. +# If @filehandle cannot be found, OpenFileFailed +# +# Since: 0.15.0 +## +{ 'type': 'GuestFileRead', + 'data': { 'count': 'int', 'buf': 'str', 'eof': 'bool' } } + +{ 'command': 'guest-file-read', + 'data': { 'filehandle': 'int', 'count': 'int' }, + 'returns': 'GuestFileRead' } + +## +# @guest-file-write: +# +# Write to an open file in the guest +# +# @filehandle: filehandle returned by guest-file-open +# +# @data_b64: base64-encoded string representing data to be written +# +# @count: bytes to write (actual bytes, after b64-decode) +# +# Returns: GuestFileWrite on success. +# If @filehandle cannot be found, OpenFileFailed +# +# Since: 0.15.0 +## +{ 'type': 'GuestFileWrite', + 'data': { 'count': 'int', 'eof': 'bool' } } +{ 'command': 'guest-file-write', + 'data': { 'filehandle': 'int', 'data_b64': 'str', 'count': 'int' }, + 'returns': 'GuestFileWrite' } + +## +# @guest-file-seek: +# +# Seek to a position in the file, as with fseek(), and return the +# current file position afterward. Also encapsulates ftell()'s +# functionality, just Set offset=0, whence=SEEK_CUR. +# +# @filehandle: filehandle returned by guest-file-open +# +# @offset: bytes to skip over in the file stream +# +# @whence: SEEK_SET, SEEK_CUR, or SEEK_END, as with fseek() +# +# Returns: GuestFileSeek on success. +# If @filename cannot be opened, OpenFileFailed +# +# Since: 0.15.0 +## +{ 'type': 'GuestFileSeek', + 'data': { 'position': 'int', 'eof': 'bool' } } + +{ 'command': 'guest-file-seek', + 'data': { 'filehandle': 'int', 'offset': 'int', 'whence': 'int' }, + 'returns': 'GuestFileSeek' } + +## +# @guest-file-close: +# +# Close an open file in the guest +# +# @filehandle: filehandle returned by guest-file-open +# +# Returns: Nothing on success. +# If @filename cannot be opened, OpenFileFailed +# +# Since: 0.15.0 +## +{ 'command': 'guest-file-close', + 'data': { 'filehandle': 'int' } } { 'type': 'ProbeProtocol', 'data': { 'unsafe': 'bool', 'filename': 'str' } } diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c new file mode 100644 index 0000000..843ef36 --- /dev/null +++ b/qga/guest-agent-commands.c @@ -0,0 +1,284 @@ +/* + * QEMU Guest Agent commands + * + * Copyright IBM Corp. 2011 + * + * Authors: + * Michael Roth <mdr...@linux.vnet.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include <glib.h> +#include "guest-agent.h" + +static bool enable_syslog = true; +static GAState *ga_state; + +#define SLOG(msg, ...) do { \ + if (enable_syslog) { \ + g_log("syslog", G_LOG_LEVEL_INFO, msg, ## __VA_ARGS__); \ + } \ +} while(0) + +void qga_guest_ping(Error **err) +{ + SLOG("guest-ping called"); +} + +struct GuestAgentInfo *qga_guest_info(Error **err) +{ + GuestAgentInfo *info = g_malloc0(sizeof(GuestInfo)); + + info->version = g_strdup(QGA_VERSION); + info->timeout_ms = ga_get_timeout(ga_state); + + return info; +} + +void qga_guest_shutdown(const char *shutdown_mode, Error **err) +{ + int ret; + const char *shutdown_flag; + + SLOG("guest-shutdown called, shutdown_mode: %s", shutdown_mode); + + if (strcmp(shutdown_mode, "halt") == 0) { + shutdown_flag = "-H"; + } else if (strcmp(shutdown_mode, "powerdown") == 0) { + shutdown_flag = "-P"; + } else if (strcmp(shutdown_mode, "reboot") == 0) { + shutdown_flag = "-r"; + } else { + ret = -EINVAL; + error_set(err, QERR_INVALID_PARAMETER_VALUE, "shutdown_mode", + "halt|powerdown|reboot"); + return; + } + + ret = fork(); + if (ret == 0) { + /* child, start the shutdown */ + setsid(); + fclose(stdin); + fclose(stdout); + fclose(stderr); + + sleep(5); + ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0", + "hypervisor initiated shutdown", (char*)NULL); + exit(!!ret); + } else if (ret < 0) { + error_set(err, QERR_UNDEFINED_ERROR); + } +} + +typedef struct GuestFileHandle { + uint64_t id; + FILE *fh; +} GuestFileHandle; + +static struct { + GSList *filehandles; + uint64_t last_id; +} guest_file_state; + +static void guest_file_init(void) +{ + guest_file_state.filehandles = NULL; + guest_file_state.last_id = 0; +} + +static void guest_file_cleanup(void) +{ + /* TODO: cleanup old array, close out any open filehandles */ + guest_file_state.filehandles = NULL; + guest_file_state.last_id = 0; +} + +static int64_t guest_file_handle_add(FILE *fh) +{ + GuestFileHandle *gfh; + + gfh = g_malloc(sizeof(GuestFileHandle)); + gfh->id = guest_file_state.last_id++; + gfh->fh = fh; + guest_file_state.filehandles = g_slist_append(guest_file_state.filehandles, + gfh); + return gfh->id; +} + +static gint guest_file_handle_match(gconstpointer elem, gconstpointer id_p) +{ + const uint64_t *id = id_p; + const GuestFileHandle *gfh = elem; + + g_assert(gfh); + return (gfh->id != *id); +} + +static FILE *guest_file_handle_find(int64_t id) +{ + GSList *elem = g_slist_find_custom(guest_file_state.filehandles, &id, + guest_file_handle_match); + GuestFileHandle *gfh; + + if (elem) { + g_assert(elem->data); + gfh = elem->data; + return gfh->fh; + } + + return NULL; +} + +static void guest_file_handle_remove(int64_t id) +{ + GSList *elem = g_slist_find_custom(guest_file_state.filehandles, &id, + guest_file_handle_match); + gpointer data = elem->data; + + if (!data) { + return; + } + guest_file_state.filehandles = g_slist_remove(guest_file_state.filehandles, + data); + g_free(data); +} + +int64_t qga_guest_file_open(const char *filename, const char *mode, Error **err) +{ + FILE *fh; + int fd, ret; + int64_t id = -1; + + SLOG("guest-file-open called, filename: %s, mode: %s", filename, mode); + fh = fopen(filename, mode); + if (!fh) { + error_set(err, QERR_OPEN_FILE_FAILED, filename); + goto out; + } + + /* set fd non-blocking to avoid common use cases (like reading from a + * named pipe) from hanging the agent + */ + fd = fileno(fh); + ret = fcntl(fd, F_GETFL); + ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK); + if (ret == -1) { + error_set(err, QERR_OPEN_FILE_FAILED, filename); + fclose(fh); + goto out; + } + + id = guest_file_handle_add(fh); + SLOG("guest-file-open, filehandle: %ld", id); +out: + return id; +} + +struct GuestFileRead *qga_guest_file_read(int64_t filehandle, int64_t count, + Error **err) +{ + GuestFileRead *read_data; + guchar *buf; + FILE *fh = guest_file_handle_find(filehandle); + size_t read_count; + + if (!fh) { + error_set(err, QERR_FD_NOT_FOUND, "filehandle"); + return NULL; + } + + read_data = g_malloc0(sizeof(GuestFileRead)); + buf = g_malloc(count); + + read_count = fread(buf, 1, count, fh); + buf[read_count] = 0; + read_data->count = read_count; + read_data->eof = feof(fh); + if (read_count) { + read_data->buf = g_base64_encode(buf, read_count); + } + g_free(buf); + /* clear error and eof. error is generally due to EAGAIN from non-blocking + * mode, and no real way to differenitate from a real error since we only + * get boolean error flag from ferror() + */ + clearerr(fh); + + return read_data; +} + +GuestFileWrite *qga_guest_file_write(int64_t filehandle, const char *data_b64, + int64_t count, Error **err) +{ + GuestFileWrite *write_data; + guchar *data; + gsize data_len; + int write_count; + FILE *fh = guest_file_handle_find(filehandle); + + if (!fh) { + error_set(err, QERR_FD_NOT_FOUND, "filehandle"); + return NULL; + } + + write_data = g_malloc0(sizeof(GuestFileWrite)); + data = g_base64_decode(data_b64, &data_len); + write_count = fwrite(data, 1, MIN(count, data_len), fh); + write_data->count = write_count; + write_data->eof = feof(fh); + g_free(data); + clearerr(fh); + + return write_data; +} + +struct GuestFileSeek *qga_guest_file_seek(int64_t filehandle, int64_t offset, + int64_t whence, Error **err) +{ + GuestFileSeek *seek_data; + FILE *fh = guest_file_handle_find(filehandle); + int ret; + + if (!fh) { + error_set(err, QERR_FD_NOT_FOUND, "filehandle"); + return NULL; + } + + seek_data = g_malloc0(sizeof(GuestFileRead)); + ret = fseek(fh, offset, whence); + if (ret == -1) { + error_set(err, QERR_UNDEFINED_ERROR); + g_free(seek_data); + return NULL; + } + seek_data->position = ftell(fh); + seek_data->eof = feof(fh); + clearerr(fh); + + return seek_data; +} + +void qga_guest_file_close(int64_t filehandle, Error **err) +{ + FILE *fh = guest_file_handle_find(filehandle); + + SLOG("guest-file-close called, filehandle: %ld", filehandle); + if (!fh) { + error_set(err, QERR_FD_NOT_FOUND, "filehandle"); + return; + } + + fclose(fh); + guest_file_handle_remove(filehandle); +} + +/* register init/cleanup routines for stateful command groups */ +void ga_command_state_init(GAState *s, GACommandState *cs) +{ + ga_state = s; + ga_command_state_add(cs, &guest_file_init, &guest_file_cleanup); +} -- 1.7.0.4