From 39097daf15c42243742667607d2cad2c9dc4f764 Mon Sep 17 00:00:00 2001 From: Michael Roth Date: Fri, 1 Mar 2013 11:40:27 -0600 Subject: qemu-ga: use key-value store to avoid recycling fd handles after restart Hosts hold on to handles provided by guest-file-open for periods that can span beyond the life of the qemu-ga process that issued them. Since these are issued starting from 0 on every restart, we run the risk of issuing duplicate handles after restarts/reboots. As a result, users with a stale copy of these handles may end up reading/writing corrupted data due to their existing handles effectively being re-assigned to an unexpected file or offset. We unfortunately do not issue handles as strings, but as integers, so a solution such as using UUIDs can't be implemented without introducing a new interface. As a workaround, we fix this by implementing a persistent key-value store that will be used to track the value of the last handle that was issued across restarts/reboots to avoid issuing duplicates. The store is automatically written to the same directory we currently set via --statedir to track fsfreeze state, and so should be applicable for stable releases where this flag is supported. A follow-up can use this same store for handling fsfreeze state, but that change is cosmetic and left out for now. Signed-off-by: Michael Roth Cc: qemu-stable@nongnu.org * fixed guest_file_handle_add() return value from uint64_t to int64_t --- qga/commands-posix.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) (limited to 'qga/commands-posix.c') diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 7a0202eb2a..1c2aff356a 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -129,14 +129,22 @@ static struct { QTAILQ_HEAD(, GuestFileHandle) filehandles; } guest_file_state; -static void guest_file_handle_add(FILE *fh) +static int64_t guest_file_handle_add(FILE *fh, Error **errp) { GuestFileHandle *gfh; + int64_t handle; + + handle = ga_get_fd_handle(ga_state, errp); + if (error_is_set(errp)) { + return 0; + } gfh = g_malloc0(sizeof(GuestFileHandle)); - gfh->id = fileno(fh); + gfh->id = handle; gfh->fh = fh; QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next); + + return handle; } static GuestFileHandle *guest_file_handle_find(int64_t id, Error **err) @@ -158,7 +166,7 @@ int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, E { FILE *fh; int fd; - int64_t ret = -1; + int64_t ret = -1, handle; if (!has_mode) { mode = "r"; @@ -184,9 +192,14 @@ int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, E return -1; } - guest_file_handle_add(fh); - slog("guest-file-open, handle: %d", fd); - return fd; + handle = guest_file_handle_add(fh, err); + if (error_is_set(err)) { + fclose(fh); + return -1; + } + + slog("guest-file-open, handle: %d", handle); + return handle; } void qmp_guest_file_close(int64_t handle, Error **err) -- cgit 1.4.1 From 6912e6a94cb0a1d650271103efbc3ac2299e4fd0 Mon Sep 17 00:00:00 2001 From: Lei Li Date: Tue, 5 Mar 2013 17:39:11 +0800 Subject: qga: add guest-get-time command Signed-off-by: Lei Li Reviewed-by: Eric Blake Reviewed-by: Michael Roth *added stub for w32 Signed-off-by: Michael Roth --- qga/commands-posix.c | 16 ++++++++++++++++ qga/commands-win32.c | 6 ++++++ qga/qapi-schema.json | 13 +++++++++++++ 3 files changed, 35 insertions(+) (limited to 'qga/commands-posix.c') diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 1c2aff356a..c83d26d0a5 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -119,6 +119,22 @@ void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) /* succeded */ } +int64_t qmp_guest_get_time(Error **errp) +{ + int ret; + qemu_timeval tq; + int64_t time_ns; + + ret = qemu_gettimeofday(&tq); + if (ret < 0) { + error_setg_errno(errp, errno, "Failed to get time"); + return -1; + } + + time_ns = tq.tv_sec * 1000000000LL + tq.tv_usec * 1000; + return time_ns; +} + typedef struct GuestFileHandle { uint64_t id; FILE *fh; diff --git a/qga/commands-win32.c b/qga/commands-win32.c index 7e8ecb3b40..3ebb856bea 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -278,6 +278,12 @@ GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **err) return NULL; } +int64_t qmp_guest_get_time(Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return -1; +} + /* register init/cleanup routines for stateful command groups */ void ga_command_state_init(GAState *s, GACommandState *cs) { diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index d91d903256..bb0f75ee5d 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -82,6 +82,19 @@ ## { 'command': 'guest-ping' } +## +# @guest-get-time: +# +# Get the information about guest time relative to the Epoch +# of 1970-01-01 in UTC. +# +# Returns: Time in nanoseconds. +# +# Since 1.5 +## +{ 'command': 'guest-get-time', + 'returns': 'int' } + ## # @GuestAgentCommandInfo: # -- cgit 1.4.1 From a1bca57f758a1ebe2ee808aa6c94f7687f9cfdd0 Mon Sep 17 00:00:00 2001 From: Lei Li Date: Tue, 5 Mar 2013 17:39:12 +0800 Subject: qga: add guest-set-time command Signed-off-by: Lei Li Reviewed-by: Eric Blake Reviewed-by: Michael Roth *added stub for w32 Signed-off-by: Michael Roth --- qga/commands-posix.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ qga/commands-win32.c | 5 +++++ qga/qapi-schema.json | 26 +++++++++++++++++++++++++ 3 files changed, 86 insertions(+) (limited to 'qga/commands-posix.c') diff --git a/qga/commands-posix.c b/qga/commands-posix.c index c83d26d0a5..c253f97fa7 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -135,6 +135,61 @@ int64_t qmp_guest_get_time(Error **errp) return time_ns; } +void qmp_guest_set_time(int64_t time_ns, Error **errp) +{ + int ret; + int status; + pid_t pid; + Error *local_err = NULL; + struct timeval tv; + + /* year-2038 will overflow in case time_t is 32bit */ + if (time_ns / 1000000000 != (time_t)(time_ns / 1000000000)) { + error_setg(errp, "Time %" PRId64 " is too large", time_ns); + return; + } + + tv.tv_sec = time_ns / 1000000000; + tv.tv_usec = (time_ns % 1000000000) / 1000; + + ret = settimeofday(&tv, NULL); + if (ret < 0) { + error_setg_errno(errp, errno, "Failed to set time to guest"); + return; + } + + /* Set the Hardware Clock to the current System Time. */ + pid = fork(); + if (pid == 0) { + setsid(); + reopen_fd_to_null(0); + reopen_fd_to_null(1); + reopen_fd_to_null(2); + + execle("/sbin/hwclock", "hwclock", "-w", NULL, environ); + _exit(EXIT_FAILURE); + } else if (pid < 0) { + error_setg_errno(errp, errno, "failed to create child process"); + return; + } + + ga_wait_child(pid, &status, &local_err); + if (error_is_set(&local_err)) { + error_propagate(errp, local_err); + return; + } + + if (!WIFEXITED(status)) { + error_setg(errp, "child process has terminated abnormally"); + return; + } + + if (WEXITSTATUS(status)) { + error_setg(errp, "hwclock failed to set hardware clock to system time"); + return; + } +} + typedef struct GuestFileHandle { uint64_t id; FILE *fh; diff --git a/qga/commands-win32.c b/qga/commands-win32.c index 3ebb856bea..622c74dd88 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -284,6 +284,11 @@ int64_t qmp_guest_get_time(Error **errp) return -1; } +void qmp_guest_set_time(int64_t time_ns, Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); +} + /* register init/cleanup routines for stateful command groups */ void ga_command_state_init(GAState *s, GACommandState *cs) { diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index bb0f75ee5d..437d750e34 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -95,6 +95,32 @@ { 'command': 'guest-get-time', 'returns': 'int' } +## +# @guest-set-time: +# +# Set guest time. +# +# When a guest is paused or migrated to a file then loaded +# from that file, the guest OS has no idea that there +# was a big gap in the time. Depending on how long the +# gap was, NTP might not be able to resynchronize the +# guest. +# +# This command tries to set guest time to the given value, +# then sets the Hardware Clock to the current System Time. +# This will make it easier for a guest to resynchronize +# without waiting for NTP. +# +# @time: time of nanoseconds, relative to the Epoch of +# 1970-01-01 in UTC. +# +# Returns: Nothing on success. +# +# Since: 1.5 +## +{ 'command': 'guest-set-time', + 'data': { 'time': 'int' } } + ## # @GuestAgentCommandInfo: # -- cgit 1.4.1 From 70e133a7080116340b1a8898893c6d455bd47299 Mon Sep 17 00:00:00 2001 From: Laszlo Ersek Date: Wed, 6 Mar 2013 22:59:29 +0100 Subject: qga: introduce guest-get-vcpus / guest-set-vcpus with stubs Signed-off-by: Laszlo Ersek Reviewed-by: Eric Blake Signed-off-by: Michael Roth --- qga/commands-posix.c | 12 +++++++++ qga/commands-win32.c | 12 +++++++++ qga/qapi-schema.json | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) (limited to 'qga/commands-posix.c') diff --git a/qga/commands-posix.c b/qga/commands-posix.c index c253f97fa7..03a41a23cf 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -1167,6 +1167,18 @@ void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err) } #endif +GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return NULL; +} + +int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return -1; +} + /* register init/cleanup routines for stateful command groups */ void ga_command_state_init(GAState *s, GACommandState *cs) { diff --git a/qga/commands-win32.c b/qga/commands-win32.c index 622c74dd88..b19be9db48 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -289,6 +289,18 @@ void qmp_guest_set_time(int64_t time_ns, Error **errp) error_set(errp, QERR_UNSUPPORTED); } +GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return NULL; +} + +int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return -1; +} + /* register init/cleanup routines for stateful command groups */ void ga_command_state_init(GAState *s, GACommandState *cs) { diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index 437d750e34..dac4e6f95f 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -554,3 +554,75 @@ ## { 'command': 'guest-network-get-interfaces', 'returns': ['GuestNetworkInterface'] } + +## +# @GuestLogicalProcessor: +# +# @logical-id: Arbitrary guest-specific unique identifier of the VCPU. +# +# @online: Whether the VCPU is enabled. +# +# @can-offline: Whether offlining the VCPU is possible. This member is always +# filled in by the guest agent when the structure is returned, +# and always ignored on input (hence it can be omitted then). +# +# Since: 1.5 +## +{ 'type': 'GuestLogicalProcessor', + 'data': {'logical-id': 'int', + 'online': 'bool', + '*can-offline': 'bool'} } + +## +# @guest-get-vcpus: +# +# Retrieve the list of the guest's logical processors. +# +# This is a read-only operation. +# +# Returns: The list of all VCPUs the guest knows about. Each VCPU is put on the +# list exactly once, but their order is unspecified. +# +# Since: 1.5 +## +{ 'command': 'guest-get-vcpus', + 'returns': ['GuestLogicalProcessor'] } + +## +# @guest-set-vcpus: +# +# Attempt to reconfigure (currently: enable/disable) logical processors inside +# the guest. +# +# The input list is processed node by node in order. In each node @logical-id +# is used to look up the guest VCPU, for which @online specifies the requested +# state. The set of distinct @logical-id's is only required to be a subset of +# the guest-supported identifiers. There's no restriction on list length or on +# repeating the same @logical-id (with possibly different @online field). +# Preferably the input list should describe a modified subset of +# @guest-get-vcpus' return value. +# +# Returns: The length of the initial sublist that has been successfully +# processed. The guest agent maximizes this value. Possible cases: +# +# 0: if the @vcpus list was empty on input. Guest state +# has not been changed. Otherwise, +# +# Error: processing the first node of @vcpus failed for the +# reason returned. Guest state has not been changed. +# Otherwise, +# +# < length(@vcpus): more than zero initial nodes have been processed, +# but not the entire @vcpus list. Guest state has +# changed accordingly. To retrieve the error +# (assuming it persists), repeat the call with the +# successfully processed initial sublist removed. +# Otherwise, +# +# length(@vcpus): call successful. +# +# Since: 1.5 +## +{ 'command': 'guest-set-vcpus', + 'data': {'vcpus': ['GuestLogicalProcessor'] }, + 'returns': 'int' } -- cgit 1.4.1 From d2baff62538d6c638c1c0d2b3fc900060a90dd78 Mon Sep 17 00:00:00 2001 From: Laszlo Ersek Date: Wed, 6 Mar 2013 22:59:30 +0100 Subject: qga: implement qmp_guest_get_vcpus() for Linux with sysfs Signed-off-by: Laszlo Ersek Reviewed-by: Eric Blake Signed-off-by: Michael Roth --- qga/commands-posix.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 140 insertions(+), 6 deletions(-) (limited to 'qga/commands-posix.c') diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 03a41a23cf..17cedab645 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -15,6 +15,10 @@ #include #include #include +#include +#include +#include +#include #include "qga/guest-agent-core.h" #include "qga-qmp-commands.h" #include "qapi/qmp/qerror.h" @@ -1111,6 +1115,136 @@ error: return NULL; } +#define SYSCONF_EXACT(name, err) sysconf_exact((name), #name, (err)) + +static long sysconf_exact(int name, const char *name_str, Error **err) +{ + long ret; + + errno = 0; + ret = sysconf(name); + if (ret == -1) { + if (errno == 0) { + error_setg(err, "sysconf(%s): value indefinite", name_str); + } else { + error_setg_errno(err, errno, "sysconf(%s)", name_str); + } + } + return ret; +} + +/* Transfer online/offline status between @vcpu and the guest system. + * + * On input either @errp or *@errp must be NULL. + * + * In system-to-@vcpu direction, the following @vcpu fields are accessed: + * - R: vcpu->logical_id + * - W: vcpu->online + * - W: vcpu->can_offline + * + * In @vcpu-to-system direction, the following @vcpu fields are accessed: + * - R: vcpu->logical_id + * - R: vcpu->online + * + * Written members remain unmodified on error. + */ +static void transfer_vcpu(GuestLogicalProcessor *vcpu, bool sys2vcpu, + Error **errp) +{ + char *dirpath; + int dirfd; + + dirpath = g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/", + vcpu->logical_id); + dirfd = open(dirpath, O_RDONLY | O_DIRECTORY); + if (dirfd == -1) { + error_setg_errno(errp, errno, "open(\"%s\")", dirpath); + } else { + static const char fn[] = "online"; + int fd; + int res; + + fd = openat(dirfd, fn, sys2vcpu ? O_RDONLY : O_RDWR); + if (fd == -1) { + if (errno != ENOENT) { + error_setg_errno(errp, errno, "open(\"%s/%s\")", dirpath, fn); + } else if (sys2vcpu) { + vcpu->online = true; + vcpu->can_offline = false; + } else if (!vcpu->online) { + error_setg(errp, "logical processor #%" PRId64 " can't be " + "offlined", vcpu->logical_id); + } /* otherwise pretend successful re-onlining */ + } else { + unsigned char status; + + res = pread(fd, &status, 1, 0); + if (res == -1) { + error_setg_errno(errp, errno, "pread(\"%s/%s\")", dirpath, fn); + } else if (res == 0) { + error_setg(errp, "pread(\"%s/%s\"): unexpected EOF", dirpath, + fn); + } else if (sys2vcpu) { + vcpu->online = (status != '0'); + vcpu->can_offline = true; + } else if (vcpu->online != (status != '0')) { + status = '0' + vcpu->online; + if (pwrite(fd, &status, 1, 0) == -1) { + error_setg_errno(errp, errno, "pwrite(\"%s/%s\")", dirpath, + fn); + } + } /* otherwise pretend successful re-(on|off)-lining */ + + res = close(fd); + g_assert(res == 0); + } + + res = close(dirfd); + g_assert(res == 0); + } + + g_free(dirpath); +} + +GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) +{ + int64_t current; + GuestLogicalProcessorList *head, **link; + long sc_max; + Error *local_err = NULL; + + current = 0; + head = NULL; + link = &head; + sc_max = SYSCONF_EXACT(_SC_NPROCESSORS_CONF, &local_err); + + while (local_err == NULL && current < sc_max) { + GuestLogicalProcessor *vcpu; + GuestLogicalProcessorList *entry; + + vcpu = g_malloc0(sizeof *vcpu); + vcpu->logical_id = current++; + vcpu->has_can_offline = true; /* lolspeak ftw */ + transfer_vcpu(vcpu, true, &local_err); + + entry = g_malloc0(sizeof *entry); + entry->value = vcpu; + + *link = entry; + link = &entry->next; + } + + if (local_err == NULL) { + /* there's no guest with zero VCPUs */ + g_assert(head != NULL); + return head; + } + + qapi_free_GuestLogicalProcessorList(head); + error_propagate(errp, local_err); + return NULL; +} + #else /* defined(__linux__) */ void qmp_guest_suspend_disk(Error **err) @@ -1134,6 +1268,12 @@ GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp) return NULL; } +GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return NULL; +} + #endif #if !defined(CONFIG_FSFREEZE) @@ -1167,12 +1307,6 @@ void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err) } #endif -GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) -{ - error_set(errp, QERR_UNSUPPORTED); - return NULL; -} - int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) { error_set(errp, QERR_UNSUPPORTED); -- cgit 1.4.1 From cbb65fc27fd97a3b020df7fce9db4ce09e3956ad Mon Sep 17 00:00:00 2001 From: Laszlo Ersek Date: Wed, 6 Mar 2013 22:59:31 +0100 Subject: qga: implement qmp_guest_set_vcpus() for Linux with sysfs Signed-off-by: Laszlo Ersek Reviewed-by: Eric Blake Signed-off-by: Michael Roth --- qga/commands-posix.c | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) (limited to 'qga/commands-posix.c') diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 17cedab645..d7da850615 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -1245,6 +1245,32 @@ GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) return NULL; } +int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) +{ + int64_t processed; + Error *local_err = NULL; + + processed = 0; + while (vcpus != NULL) { + transfer_vcpu(vcpus->value, false, &local_err); + if (local_err != NULL) { + break; + } + ++processed; + vcpus = vcpus->next; + } + + if (local_err != NULL) { + if (processed == 0) { + error_propagate(errp, local_err); + } else { + error_free(local_err); + } + } + + return processed; +} + #else /* defined(__linux__) */ void qmp_guest_suspend_disk(Error **err) @@ -1274,6 +1300,12 @@ GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) return NULL; } +int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return -1; +} + #endif #if !defined(CONFIG_FSFREEZE) @@ -1307,12 +1339,6 @@ void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err) } #endif -int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) -{ - error_set(errp, QERR_UNSUPPORTED); - return -1; -} - /* register init/cleanup routines for stateful command groups */ void ga_command_state_init(GAState *s, GACommandState *cs) { -- cgit 1.4.1