]> git.proxmox.com Git - qemu.git/blobdiff - qga/commands-posix.c
Merge remote-tracking branch 'mst/tags/for_anthony' into staging
[qemu.git] / qga / commands-posix.c
index b0eec7414865351cb44d047423587797f1120a77..3b5c536e41caec51a2805997530c84ea7a94ed3f 100644 (file)
 #include <sys/types.h>
 #include <sys/ioctl.h>
 #include <sys/wait.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
 #include "qga/guest-agent-core.h"
 #include "qga-qmp-commands.h"
-#include "qerror.h"
-#include "qemu-queue.h"
-#include "host-utils.h"
+#include "qapi/qmp/qerror.h"
+#include "qemu/queue.h"
+#include "qemu/host-utils.h"
 
 #ifndef CONFIG_HAS_ENVIRON
 #ifdef __APPLE__
@@ -46,10 +50,29 @@ extern char **environ;
 #endif
 #endif
 
+static void ga_wait_child(pid_t pid, int *status, Error **err)
+{
+    pid_t rpid;
+
+    *status = 0;
+
+    do {
+        rpid = waitpid(pid, status, 0);
+    } while (rpid == -1 && errno == EINTR);
+
+    if (rpid == -1) {
+        error_setg_errno(err, errno, "failed to wait for child (pid: %d)", pid);
+        return;
+    }
+
+    g_assert(rpid == pid);
+}
+
 void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
 {
     const char *shutdown_flag;
-    pid_t rpid, pid;
+    Error *local_err = NULL;
+    pid_t pid;
     int status;
 
     slog("guest-shutdown called, mode: %s", mode);
@@ -60,8 +83,8 @@ void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
     } else if (strcmp(mode, "reboot") == 0) {
         shutdown_flag = "-r";
     } else {
-        error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode",
-                  "halt|powerdown|reboot");
+        error_setg(err,
+                   "mode is invalid (valid values are: halt|powerdown|reboot");
         return;
     }
 
@@ -77,18 +100,98 @@ void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
                "hypervisor initiated shutdown", (char*)NULL, environ);
         _exit(EXIT_FAILURE);
     } else if (pid < 0) {
-        goto exit_err;
+        error_setg_errno(err, errno, "failed to create child process");
+        return;
     }
 
-    do {
-        rpid = waitpid(pid, &status, 0);
-    } while (rpid == -1 && errno == EINTR);
-    if (rpid == pid && WIFEXITED(status) && !WEXITSTATUS(status)) {
+    ga_wait_child(pid, &status, &local_err);
+    if (error_is_set(&local_err)) {
+        error_propagate(err, local_err);
+        return;
+    }
+
+    if (!WIFEXITED(status)) {
+        error_setg(err, "child process has terminated abnormally");
+        return;
+    }
+
+    if (WEXITSTATUS(status)) {
+        error_setg(err, "child process has failed to shutdown");
         return;
     }
 
-exit_err:
-    error_set(err, QERR_UNDEFINED_ERROR);
+    /* succeeded */
+}
+
+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;
+}
+
+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 {
@@ -101,17 +204,25 @@ 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)
+static GuestFileHandle *guest_file_handle_find(int64_t id, Error **err)
 {
     GuestFileHandle *gfh;
 
@@ -122,6 +233,7 @@ static GuestFileHandle *guest_file_handle_find(int64_t id)
         }
     }
 
+    error_setg(err, "handle '%" PRId64 "' has not been found", id);
     return NULL;
 }
 
@@ -129,7 +241,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";
@@ -137,7 +249,8 @@ int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, E
     slog("guest-file-open called, filepath: %s, mode: %s", path, mode);
     fh = fopen(path, mode);
     if (!fh) {
-        error_set(err, QERR_OPEN_FILE_FAILED, path);
+        error_setg_errno(err, errno, "failed to open file '%s' (mode: '%s')",
+                         path, mode);
         return -1;
     }
 
@@ -148,30 +261,35 @@ int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, E
     ret = fcntl(fd, F_GETFL);
     ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK);
     if (ret == -1) {
-        error_set(err, QERR_QGA_COMMAND_FAILED, "fcntl() failed");
+        error_setg_errno(err, errno, "failed to make file '%s' non-blocking",
+                         path);
         fclose(fh);
         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)
 {
-    GuestFileHandle *gfh = guest_file_handle_find(handle);
+    GuestFileHandle *gfh = guest_file_handle_find(handle, err);
     int ret;
 
     slog("guest-file-close called, handle: %ld", handle);
     if (!gfh) {
-        error_set(err, QERR_FD_NOT_FOUND, "handle");
         return;
     }
 
     ret = fclose(gfh->fh);
-    if (ret == -1) {
-        error_set(err, QERR_QGA_COMMAND_FAILED, "fclose() failed");
+    if (ret == EOF) {
+        error_setg_errno(err, errno, "failed to close handle");
         return;
     }
 
@@ -182,21 +300,21 @@ void qmp_guest_file_close(int64_t handle, Error **err)
 struct GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count,
                                           int64_t count, Error **err)
 {
-    GuestFileHandle *gfh = guest_file_handle_find(handle);
+    GuestFileHandle *gfh = guest_file_handle_find(handle, err);
     GuestFileRead *read_data = NULL;
     guchar *buf;
     FILE *fh;
     size_t read_count;
 
     if (!gfh) {
-        error_set(err, QERR_FD_NOT_FOUND, "handle");
         return NULL;
     }
 
     if (!has_count) {
         count = QGA_READ_COUNT_DEFAULT;
     } else if (count < 0) {
-        error_set(err, QERR_INVALID_PARAMETER, "count");
+        error_setg(err, "value '%" PRId64 "' is invalid for argument count",
+                   count);
         return NULL;
     }
 
@@ -204,8 +322,8 @@ struct GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count,
     buf = g_malloc0(count+1);
     read_count = fread(buf, 1, count, fh);
     if (ferror(fh)) {
+        error_setg_errno(err, errno, "failed to read file");
         slog("guest-file-read failed, handle: %ld", handle);
-        error_set(err, QERR_QGA_COMMAND_FAILED, "fread() failed");
     } else {
         buf[read_count] = 0;
         read_data = g_malloc0(sizeof(GuestFileRead));
@@ -228,11 +346,10 @@ GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64,
     guchar *buf;
     gsize buf_len;
     int write_count;
-    GuestFileHandle *gfh = guest_file_handle_find(handle);
+    GuestFileHandle *gfh = guest_file_handle_find(handle, err);
     FILE *fh;
 
     if (!gfh) {
-        error_set(err, QERR_FD_NOT_FOUND, "handle");
         return NULL;
     }
 
@@ -242,15 +359,16 @@ GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64,
     if (!has_count) {
         count = buf_len;
     } else if (count < 0 || count > buf_len) {
+        error_setg(err, "value '%" PRId64 "' is invalid for argument count",
+                   count);
         g_free(buf);
-        error_set(err, QERR_INVALID_PARAMETER, "count");
         return NULL;
     }
 
     write_count = fwrite(buf, 1, count, fh);
     if (ferror(fh)) {
+        error_setg_errno(err, errno, "failed to write to file");
         slog("guest-file-write failed, handle: %ld", handle);
-        error_set(err, QERR_QGA_COMMAND_FAILED, "fwrite() error");
     } else {
         write_data = g_malloc0(sizeof(GuestFileWrite));
         write_data->count = write_count;
@@ -265,20 +383,19 @@ GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64,
 struct GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset,
                                           int64_t whence, Error **err)
 {
-    GuestFileHandle *gfh = guest_file_handle_find(handle);
+    GuestFileHandle *gfh = guest_file_handle_find(handle, err);
     GuestFileSeek *seek_data = NULL;
     FILE *fh;
     int ret;
 
     if (!gfh) {
-        error_set(err, QERR_FD_NOT_FOUND, "handle");
         return NULL;
     }
 
     fh = gfh->fh;
     ret = fseek(fh, offset, whence);
     if (ret == -1) {
-        error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno));
+        error_setg_errno(err, errno, "failed to seek file");
     } else {
         seek_data = g_malloc0(sizeof(GuestFileRead));
         seek_data->position = ftell(fh);
@@ -291,19 +408,18 @@ struct GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset,
 
 void qmp_guest_file_flush(int64_t handle, Error **err)
 {
-    GuestFileHandle *gfh = guest_file_handle_find(handle);
+    GuestFileHandle *gfh = guest_file_handle_find(handle, err);
     FILE *fh;
     int ret;
 
     if (!gfh) {
-        error_set(err, QERR_FD_NOT_FOUND, "handle");
         return;
     }
 
     fh = gfh->fh;
     ret = fflush(fh);
     if (ret == EOF) {
-        error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno));
+        error_setg_errno(err, errno, "failed to flush file");
     }
 }
 
@@ -343,7 +459,7 @@ static void free_fs_mount_list(FsMountList *mounts)
 /*
  * Walk the mount table and build a list of local file systems
  */
-static int build_fs_mount_list(FsMountList *mounts)
+static void build_fs_mount_list(FsMountList *mounts, Error **err)
 {
     struct mntent *ment;
     FsMount *mount;
@@ -352,8 +468,8 @@ static int build_fs_mount_list(FsMountList *mounts)
 
     fp = setmntent(mtab, "r");
     if (!fp) {
-        g_warning("fsfreeze: unable to read mtab");
-        return -1;
+        error_setg(err, "failed to open mtab file: '%s'", mtab);
+        return;
     }
 
     while ((ment = getmntent(fp))) {
@@ -377,13 +493,71 @@ static int build_fs_mount_list(FsMountList *mounts)
     }
 
     endmntent(fp);
-
-    return 0;
 }
 #endif
 
 #if defined(CONFIG_FSFREEZE)
 
+typedef enum {
+    FSFREEZE_HOOK_THAW = 0,
+    FSFREEZE_HOOK_FREEZE,
+} FsfreezeHookArg;
+
+const char *fsfreeze_hook_arg_string[] = {
+    "thaw",
+    "freeze",
+};
+
+static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **err)
+{
+    int status;
+    pid_t pid;
+    const char *hook;
+    const char *arg_str = fsfreeze_hook_arg_string[arg];
+    Error *local_err = NULL;
+
+    hook = ga_fsfreeze_hook(ga_state);
+    if (!hook) {
+        return;
+    }
+    if (access(hook, X_OK) != 0) {
+        error_setg_errno(err, errno, "can't access fsfreeze hook '%s'", hook);
+        return;
+    }
+
+    slog("executing fsfreeze hook with arg '%s'", arg_str);
+    pid = fork();
+    if (pid == 0) {
+        setsid();
+        reopen_fd_to_null(0);
+        reopen_fd_to_null(1);
+        reopen_fd_to_null(2);
+
+        execle(hook, hook, arg_str, NULL, environ);
+        _exit(EXIT_FAILURE);
+    } else if (pid < 0) {
+        error_setg_errno(err, errno, "failed to create child process");
+        return;
+    }
+
+    ga_wait_child(pid, &status, &local_err);
+    if (error_is_set(&local_err)) {
+        error_propagate(err, local_err);
+        return;
+    }
+
+    if (!WIFEXITED(status)) {
+        error_setg(err, "fsfreeze hook has terminated abnormally");
+        return;
+    }
+
+    status = WEXITSTATUS(status);
+    if (status) {
+        error_setg(err, "fsfreeze hook has failed with status %d", status);
+        return;
+    }
+}
+
 /*
  * Return status of freeze/thaw
  */
@@ -405,15 +579,22 @@ int64_t qmp_guest_fsfreeze_freeze(Error **err)
     int ret = 0, i = 0;
     FsMountList mounts;
     struct FsMount *mount;
+    Error *local_err = NULL;
     int fd;
-    char err_msg[512];
 
     slog("guest-fsfreeze called");
 
+    execute_fsfreeze_hook(FSFREEZE_HOOK_FREEZE, &local_err);
+    if (error_is_set(&local_err)) {
+        error_propagate(err, local_err);
+        return -1;
+    }
+
     QTAILQ_INIT(&mounts);
-    ret = build_fs_mount_list(&mounts);
-    if (ret < 0) {
-        return ret;
+    build_fs_mount_list(&mounts, &local_err);
+    if (error_is_set(&local_err)) {
+        error_propagate(err, local_err);
+        return -1;
     }
 
     /* cannot risk guest agent blocking itself on a write in this state */
@@ -422,9 +603,7 @@ int64_t qmp_guest_fsfreeze_freeze(Error **err)
     QTAILQ_FOREACH(mount, &mounts, next) {
         fd = qemu_open(mount->dirname, O_RDONLY);
         if (fd == -1) {
-            sprintf(err_msg, "failed to open %s, %s", mount->dirname,
-                    strerror(errno));
-            error_set(err, QERR_QGA_COMMAND_FAILED, err_msg);
+            error_setg_errno(err, errno, "failed to open %s", mount->dirname);
             goto error;
         }
 
@@ -440,9 +619,8 @@ int64_t qmp_guest_fsfreeze_freeze(Error **err)
         ret = ioctl(fd, FIFREEZE);
         if (ret == -1) {
             if (errno != EOPNOTSUPP) {
-                sprintf(err_msg, "failed to freeze %s, %s",
-                        mount->dirname, strerror(errno));
-                error_set(err, QERR_QGA_COMMAND_FAILED, err_msg);
+                error_setg_errno(err, errno, "failed to freeze %s",
+                                 mount->dirname);
                 close(fd);
                 goto error;
             }
@@ -470,12 +648,12 @@ int64_t qmp_guest_fsfreeze_thaw(Error **err)
     FsMountList mounts;
     FsMount *mount;
     int fd, i = 0, logged;
+    Error *local_err = NULL;
 
     QTAILQ_INIT(&mounts);
-    ret = build_fs_mount_list(&mounts);
-    if (ret) {
-        error_set(err, QERR_QGA_COMMAND_FAILED,
-                  "failed to enumerate filesystems");
+    build_fs_mount_list(&mounts, &local_err);
+    if (error_is_set(&local_err)) {
+        error_propagate(err, local_err);
         return 0;
     }
 
@@ -513,18 +691,22 @@ int64_t qmp_guest_fsfreeze_thaw(Error **err)
 
     ga_unset_frozen(ga_state);
     free_fs_mount_list(&mounts);
+
+    execute_fsfreeze_hook(FSFREEZE_HOOK_THAW, err);
+
     return i;
 }
 
 static void guest_fsfreeze_cleanup(void)
 {
-    int64_t ret;
     Error *err = NULL;
 
     if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
-        ret = qmp_guest_fsfreeze_thaw(&err);
-        if (ret < 0 || err) {
-            slog("failed to clean up frozen filesystems");
+        qmp_guest_fsfreeze_thaw(&err);
+        if (err) {
+            slog("failed to clean up frozen filesystems: %s",
+                 error_get_pretty(err));
+            error_free(err);
         }
     }
 }
@@ -540,7 +722,7 @@ void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err)
     FsMountList mounts;
     struct FsMount *mount;
     int fd;
-    char err_msg[512];
+    Error *local_err = NULL;
     struct fstrim_range r = {
         .start = 0,
         .len = -1,
@@ -550,17 +732,16 @@ void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err)
     slog("guest-fstrim called");
 
     QTAILQ_INIT(&mounts);
-    ret = build_fs_mount_list(&mounts);
-    if (ret < 0) {
+    build_fs_mount_list(&mounts, &local_err);
+    if (error_is_set(&local_err)) {
+        error_propagate(err, local_err);
         return;
     }
 
     QTAILQ_FOREACH(mount, &mounts, next) {
         fd = qemu_open(mount->dirname, O_RDONLY);
         if (fd == -1) {
-            sprintf(err_msg, "failed to open %s, %s", mount->dirname,
-                    strerror(errno));
-            error_set(err, QERR_QGA_COMMAND_FAILED, err_msg);
+            error_setg_errno(err, errno, "failed to open %s", mount->dirname);
             goto error;
         }
 
@@ -573,9 +754,8 @@ void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err)
         ret = ioctl(fd, FITRIM, &r);
         if (ret == -1) {
             if (errno != ENOTTY && errno != EOPNOTSUPP) {
-                sprintf(err_msg, "failed to trim %s, %s",
-                        mount->dirname, strerror(errno));
-                error_set(err, QERR_QGA_COMMAND_FAILED, err_msg);
+                error_setg_errno(err, errno, "failed to trim %s",
+                                 mount->dirname);
                 close(fd);
                 goto error;
             }
@@ -596,8 +776,9 @@ error:
 static void bios_supports_mode(const char *pmutils_bin, const char *pmutils_arg,
                                const char *sysfile_str, Error **err)
 {
+    Error *local_err = NULL;
     char *pmutils_path;
-    pid_t pid, rpid;
+    pid_t pid;
     int status;
 
     pmutils_path = g_find_program_in_path(pmutils_bin);
@@ -642,38 +823,46 @@ static void bios_supports_mode(const char *pmutils_bin, const char *pmutils_arg,
         }
 
         _exit(SUSPEND_NOT_SUPPORTED);
+    } else if (pid < 0) {
+        error_setg_errno(err, errno, "failed to create child process");
+        goto out;
     }
 
-    g_free(pmutils_path);
+    ga_wait_child(pid, &status, &local_err);
+    if (error_is_set(&local_err)) {
+        error_propagate(err, local_err);
+        goto out;
+    }
 
-    if (pid < 0) {
-        goto undef_err;
+    if (!WIFEXITED(status)) {
+        error_setg(err, "child process has terminated abnormally");
+        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;
-        }
+    switch (WEXITSTATUS(status)) {
+    case SUSPEND_SUPPORTED:
+        goto out;
+    case SUSPEND_NOT_SUPPORTED:
+        error_setg(err,
+                   "the requested suspend mode is not supported by the guest");
+        goto out;
+    default:
+        error_setg(err,
+                   "the helper program '%s' returned an unexpected exit status"
+                   " code (%d)", pmutils_path, WEXITSTATUS(status));
+        goto out;
     }
 
-undef_err:
-    error_set(err, QERR_UNDEFINED_ERROR);
+out:
+    g_free(pmutils_path);
 }
 
 static void guest_suspend(const char *pmutils_bin, const char *sysfile_str,
                           Error **err)
 {
+    Error *local_err = NULL;
     char *pmutils_path;
-    pid_t rpid, pid;
+    pid_t pid;
     int status;
 
     pmutils_path = g_find_program_in_path(pmutils_bin);
@@ -711,23 +900,29 @@ static void guest_suspend(const char *pmutils_bin, const char *sysfile_str,
         }
 
         _exit(EXIT_SUCCESS);
+    } else if (pid < 0) {
+        error_setg_errno(err, errno, "failed to create child process");
+        goto out;
     }
 
-    g_free(pmutils_path);
+    ga_wait_child(pid, &status, &local_err);
+    if (error_is_set(&local_err)) {
+        error_propagate(err, local_err);
+        goto out;
+    }
 
-    if (pid < 0) {
-        goto exit_err;
+    if (!WIFEXITED(status)) {
+        error_setg(err, "child process has terminated abnormally");
+        goto out;
     }
 
-    do {
-        rpid = waitpid(pid, &status, 0);
-    } while (rpid == -1 && errno == EINTR);
-    if (rpid == pid && WIFEXITED(status) && !WEXITSTATUS(status)) {
-        return;
+    if (WEXITSTATUS(status)) {
+        error_setg(err, "child process has failed to suspend");
+        goto out;
     }
 
-exit_err:
-    error_set(err, QERR_UNDEFINED_ERROR);
+out:
+    g_free(pmutils_path);
 }
 
 void qmp_guest_suspend_disk(Error **err)
@@ -780,12 +975,9 @@ GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
 {
     GuestNetworkInterfaceList *head = NULL, *cur_item = NULL;
     struct ifaddrs *ifap, *ifa;
-    char err_msg[512];
 
     if (getifaddrs(&ifap) < 0) {
-        snprintf(err_msg, sizeof(err_msg),
-                 "getifaddrs failed: %s", strerror(errno));
-        error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg);
+        error_setg_errno(errp, errno, "getifaddrs failed");
         goto error;
     }
 
@@ -821,53 +1013,43 @@ GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
             /* we haven't obtained HW address yet */
             sock = socket(PF_INET, SOCK_STREAM, 0);
             if (sock == -1) {
-                snprintf(err_msg, sizeof(err_msg),
-                         "failed to create socket: %s", strerror(errno));
-                error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg);
+                error_setg_errno(errp, errno, "failed to create socket");
                 goto error;
             }
 
             memset(&ifr, 0, sizeof(ifr));
-            strncpy(ifr.ifr_name,  info->value->name, IF_NAMESIZE);
+            pstrcpy(ifr.ifr_name, IF_NAMESIZE, info->value->name);
             if (ioctl(sock, SIOCGIFHWADDR, &ifr) == -1) {
-                snprintf(err_msg, sizeof(err_msg),
-                         "failed to get MAC address of %s: %s",
-                         ifa->ifa_name,
-                         strerror(errno));
-                error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg);
+                error_setg_errno(errp, errno,
+                                 "failed to get MAC address of %s",
+                                 ifa->ifa_name);
+                close(sock);
                 goto error;
             }
 
+            close(sock);
             mac_addr = (unsigned char *) &ifr.ifr_hwaddr.sa_data;
 
-            if (asprintf(&info->value->hardware_address,
-                         "%02x:%02x:%02x:%02x:%02x:%02x",
-                         (int) mac_addr[0], (int) mac_addr[1],
-                         (int) mac_addr[2], (int) mac_addr[3],
-                         (int) mac_addr[4], (int) mac_addr[5]) == -1) {
-                snprintf(err_msg, sizeof(err_msg),
-                         "failed to format MAC: %s", strerror(errno));
-                error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg);
-                goto error;
-            }
+            info->value->hardware_address =
+                g_strdup_printf("%02x:%02x:%02x:%02x:%02x:%02x",
+                                (int) mac_addr[0], (int) mac_addr[1],
+                                (int) mac_addr[2], (int) mac_addr[3],
+                                (int) mac_addr[4], (int) mac_addr[5]);
 
             info->value->has_hardware_address = true;
-            close(sock);
         }
 
         if (ifa->ifa_addr &&
             ifa->ifa_addr->sa_family == AF_INET) {
             /* interface with IPv4 address */
-            address_item = g_malloc0(sizeof(*address_item));
-            address_item->value = g_malloc0(sizeof(*address_item->value));
             p = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
             if (!inet_ntop(AF_INET, p, addr4, sizeof(addr4))) {
-                snprintf(err_msg, sizeof(err_msg),
-                         "inet_ntop failed : %s", strerror(errno));
-                error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg);
+                error_setg_errno(errp, errno, "inet_ntop failed");
                 goto error;
             }
 
+            address_item = g_malloc0(sizeof(*address_item));
+            address_item->value = g_malloc0(sizeof(*address_item->value));
             address_item->value->ip_address = g_strdup(addr4);
             address_item->value->ip_address_type = GUEST_IP_ADDRESS_TYPE_IPV4;
 
@@ -880,16 +1062,14 @@ GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
         } else if (ifa->ifa_addr &&
                    ifa->ifa_addr->sa_family == AF_INET6) {
             /* interface with IPv6 address */
-            address_item = g_malloc0(sizeof(*address_item));
-            address_item->value = g_malloc0(sizeof(*address_item->value));
             p = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
             if (!inet_ntop(AF_INET6, p, addr6, sizeof(addr6))) {
-                snprintf(err_msg, sizeof(err_msg),
-                         "inet_ntop failed : %s", strerror(errno));
-                error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg);
+                error_setg_errno(errp, errno, "inet_ntop failed");
                 goto error;
             }
 
+            address_item = g_malloc0(sizeof(*address_item));
+            address_item->value = g_malloc0(sizeof(*address_item->value));
             address_item->value->ip_address = g_strdup(addr6);
             address_item->value->ip_address_type = GUEST_IP_ADDRESS_TYPE_IPV6;
 
@@ -935,6 +1115,162 @@ 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;
+}
+
+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)
@@ -958,6 +1294,18 @@ GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
     return NULL;
 }
 
+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;
+}
+
 #endif
 
 #if !defined(CONFIG_FSFREEZE)