* [PATCH 0/5] qga: Add FreeBSD support
@ 2022-09-22 13:19 Alexander Ivanov
2022-09-22 13:19 ` [PATCH 1/5] qga: Move Linux-specific commands code to separate file Alexander Ivanov
` (4 more replies)
0 siblings, 5 replies; 14+ messages in thread
From: Alexander Ivanov @ 2022-09-22 13:19 UTC (permalink / raw)
To: qemu-devel; +Cc: den, michael.roth, kkostiuk
Add freeze/thaw, shutdown/halt/reboot and password setting support for
FreeBSD.
Alexander Ivanov (5):
qga: Move Linux-specific commands code to separate file
qga: Add initial FreeBSD support
qga: Add UFS freeze/thaw support for FreeBSD
qga: Add shutdown/halt/reboot support for FreeBSD
qga: Add support for user password settinng in FreeeBSD
meson.build | 2 +-
qga/commands-bsd.c | 210 ++++
qga/commands-common.h | 45 +
qga/commands-linux.c | 2138 +++++++++++++++++++++++++++++++++++
qga/commands-posix.c | 2476 ++++-------------------------------------
qga/main.c | 17 +-
qga/meson.build | 6 +
7 files changed, 2606 insertions(+), 2288 deletions(-)
create mode 100644 qga/commands-bsd.c
create mode 100644 qga/commands-linux.c
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 1/5] qga: Move Linux-specific commands code to separate file
2022-09-22 13:19 [PATCH 0/5] qga: Add FreeBSD support Alexander Ivanov
@ 2022-09-22 13:19 ` Alexander Ivanov
2022-09-23 7:59 ` Marc-André Lureau
2022-09-22 13:19 ` [PATCH 2/5] qga: Add initial FreeBSD support Alexander Ivanov
` (3 subsequent siblings)
4 siblings, 1 reply; 14+ messages in thread
From: Alexander Ivanov @ 2022-09-22 13:19 UTC (permalink / raw)
To: qemu-devel; +Cc: den, michael.roth, kkostiuk
In the next patches we are going to add FreeBSD support for QEMU Guest
Agent. In the result, code in commands-posix.c will be too cumbersome.
Move Linux-specific code to a separate file keeping common POSIX code in
commands-posix.c.
Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
---
qga/commands-common.h | 34 +
qga/commands-linux.c | 2242 ++++++++++++++++++++++++++++++++++++
qga/commands-posix.c | 2558 +++--------------------------------------
qga/meson.build | 3 +
4 files changed, 2447 insertions(+), 2390 deletions(-)
create mode 100644 qga/commands-linux.c
diff --git a/qga/commands-common.h b/qga/commands-common.h
index d0e4a9696f..aa0472ea4c 100644
--- a/qga/commands-common.h
+++ b/qga/commands-common.h
@@ -29,4 +29,38 @@ GuestFileRead *guest_file_read_unsafe(GuestFileHandle *gfh,
*/
char *qga_get_host_name(Error **errp);
+void ga_wait_child(pid_t pid, int *status, Error **errp);
+
+#if defined(__linux__)
+#include <linux/fs.h>
+#ifdef FIFREEZE
+#define CONFIG_FSFREEZE
#endif
+#ifdef FITRIM
+#define CONFIG_FSTRIM
+#endif
+#endif /* __linux__*/
+
+#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
+typedef struct FsMount {
+ char *dirname;
+ char *devtype;
+ unsigned int devmajor, devminor;
+ QTAILQ_ENTRY(FsMount) next;
+} FsMount;
+
+typedef QTAILQ_HEAD(FsMountList, FsMount) FsMountList;
+
+bool build_fs_mount_list(FsMountList *mounts, Error **errp);
+void free_fs_mount_list(FsMountList *mounts);
+#endif
+
+#if defined(CONFIG_FSFREEZE)
+int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints,
+ strList *mountpoints,
+ FsMountList mounts,
+ Error **errp);
+int qmp_guest_fsfreeze_do_thaw(Error **errp);
+#endif
+
+#endif /* QGA_COMMANDS_COMMON_H */
diff --git a/qga/commands-linux.c b/qga/commands-linux.c
new file mode 100644
index 0000000000..615e9a0027
--- /dev/null
+++ b/qga/commands-linux.c
@@ -0,0 +1,2242 @@
+/*
+ * QEMU Guest Agent Linux-specific command implementations
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ * Michal Privoznik <mprivozn@redhat.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 "qemu/osdep.h"
+#include <sys/ioctl.h>
+#include "guest-agent-core.h"
+#include "qga-qapi-commands.h"
+#include "qapi/qmp/qerror.h"
+#include "qapi/error.h"
+#include "qemu/queue.h"
+#include "qemu/base64.h"
+#include "commands-common.h"
+#include "block/nvme.h"
+#include "cutils.h"
+
+#include <mntent.h>
+#include <sys/statvfs.h>
+#include <linux/nvme_ioctl.h>
+
+#ifdef CONFIG_LIBUDEV
+#include <libudev.h>
+#endif
+
+#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
+static int dev_major_minor(const char *devpath,
+ unsigned int *devmajor, unsigned int *devminor)
+{
+ struct stat st;
+
+ *devmajor = 0;
+ *devminor = 0;
+
+ if (stat(devpath, &st) < 0) {
+ slog("failed to stat device file '%s': %s", devpath, strerror(errno));
+ return -1;
+ }
+ if (S_ISDIR(st.st_mode)) {
+ /* It is bind mount */
+ return -2;
+ }
+ if (S_ISBLK(st.st_mode)) {
+ *devmajor = major(st.st_rdev);
+ *devminor = minor(st.st_rdev);
+ return 0;
+ }
+ return -1;
+}
+
+/*
+ * Walk the mount table and build a list of local file systems
+ */
+static bool build_fs_mount_list_from_mtab(FsMountList *mounts, Error **errp)
+{
+ struct mntent *ment;
+ FsMount *mount;
+ char const *mtab = "/proc/self/mounts";
+ FILE *fp;
+ unsigned int devmajor, devminor;
+
+ fp = setmntent(mtab, "r");
+ if (!fp) {
+ error_setg(errp, "failed to open mtab file: '%s'", mtab);
+ return false;
+ }
+
+ while ((ment = getmntent(fp))) {
+ /*
+ * An entry which device name doesn't start with a '/' is
+ * either a dummy file system or a network file system.
+ * Add special handling for smbfs and cifs as is done by
+ * coreutils as well.
+ */
+ if ((ment->mnt_fsname[0] != '/') ||
+ (strcmp(ment->mnt_type, "smbfs") == 0) ||
+ (strcmp(ment->mnt_type, "cifs") == 0)) {
+ continue;
+ }
+ if (dev_major_minor(ment->mnt_fsname, &devmajor, &devminor) == -2) {
+ /* Skip bind mounts */
+ continue;
+ }
+
+ mount = g_new0(FsMount, 1);
+ mount->dirname = g_strdup(ment->mnt_dir);
+ mount->devtype = g_strdup(ment->mnt_type);
+ mount->devmajor = devmajor;
+ mount->devminor = devminor;
+
+ QTAILQ_INSERT_TAIL(mounts, mount, next);
+ }
+
+ endmntent(fp);
+ return true;
+}
+
+static void decode_mntname(char *name, int len)
+{
+ int i, j = 0;
+ for (i = 0; i <= len; i++) {
+ if (name[i] != '\\') {
+ name[j++] = name[i];
+ } else if (name[i + 1] == '\\') {
+ name[j++] = '\\';
+ i++;
+ } else if (name[i + 1] >= '0' && name[i + 1] <= '3' &&
+ name[i + 2] >= '0' && name[i + 2] <= '7' &&
+ name[i + 3] >= '0' && name[i + 3] <= '7') {
+ name[j++] = (name[i + 1] - '0') * 64 +
+ (name[i + 2] - '0') * 8 +
+ (name[i + 3] - '0');
+ i += 3;
+ } else {
+ name[j++] = name[i];
+ }
+ }
+}
+
+bool build_fs_mount_list(FsMountList *mounts, Error **errp)
+{
+ FsMount *mount;
+ char const *mountinfo = "/proc/self/mountinfo";
+ FILE *fp;
+ char *line = NULL, *dash;
+ size_t n;
+ char check;
+ unsigned int devmajor, devminor;
+ int ret, dir_s, dir_e, type_s, type_e, dev_s, dev_e;
+
+ fp = fopen(mountinfo, "r");
+ if (!fp) {
+ return build_fs_mount_list_from_mtab(mounts, errp);
+ }
+
+ while (getline(&line, &n, fp) != -1) {
+ ret = sscanf(line, "%*u %*u %u:%u %*s %n%*s%n%c",
+ &devmajor, &devminor, &dir_s, &dir_e, &check);
+ if (ret < 3) {
+ continue;
+ }
+ dash = strstr(line + dir_e, " - ");
+ if (!dash) {
+ continue;
+ }
+ ret = sscanf(dash, " - %n%*s%n %n%*s%n%c",
+ &type_s, &type_e, &dev_s, &dev_e, &check);
+ if (ret < 1) {
+ continue;
+ }
+ line[dir_e] = 0;
+ dash[type_e] = 0;
+ dash[dev_e] = 0;
+ decode_mntname(line + dir_s, dir_e - dir_s);
+ decode_mntname(dash + dev_s, dev_e - dev_s);
+ if (devmajor == 0) {
+ /* btrfs reports major number = 0 */
+ if (strcmp("btrfs", dash + type_s) != 0 ||
+ dev_major_minor(dash + dev_s, &devmajor, &devminor) < 0) {
+ continue;
+ }
+ }
+
+ mount = g_new0(FsMount, 1);
+ mount->dirname = g_strdup(line + dir_s);
+ mount->devtype = g_strdup(dash + type_s);
+ mount->devmajor = devmajor;
+ mount->devminor = devminor;
+
+ QTAILQ_INSERT_TAIL(mounts, mount, next);
+ }
+ free(line);
+
+ fclose(fp);
+ return true;
+}
+#endif
+
+#if defined(CONFIG_FSFREEZE)
+
+static char *get_pci_driver(char const *syspath, int pathlen, Error **errp)
+{
+ char *path;
+ char *dpath;
+ char *driver = NULL;
+ char buf[PATH_MAX];
+ ssize_t len;
+
+ path = g_strndup(syspath, pathlen);
+ dpath = g_strdup_printf("%s/driver", path);
+ len = readlink(dpath, buf, sizeof(buf) - 1);
+ if (len != -1) {
+ buf[len] = 0;
+ driver = g_path_get_basename(buf);
+ }
+ g_free(dpath);
+ g_free(path);
+ return driver;
+}
+
+static int compare_uint(const void *_a, const void *_b)
+{
+ unsigned int a = *(unsigned int *)_a;
+ unsigned int b = *(unsigned int *)_b;
+
+ return a < b ? -1 : a > b ? 1 : 0;
+}
+
+/* Walk the specified sysfs and build a sorted list of host or ata numbers */
+static int build_hosts(char const *syspath, char const *host, bool ata,
+ unsigned int *hosts, int hosts_max, Error **errp)
+{
+ char *path;
+ DIR *dir;
+ struct dirent *entry;
+ int i = 0;
+
+ path = g_strndup(syspath, host - syspath);
+ dir = opendir(path);
+ if (!dir) {
+ error_setg_errno(errp, errno, "opendir(\"%s\")", path);
+ g_free(path);
+ return -1;
+ }
+
+ while (i < hosts_max) {
+ entry = readdir(dir);
+ if (!entry) {
+ break;
+ }
+ if (ata && sscanf(entry->d_name, "ata%d", hosts + i) == 1) {
+ ++i;
+ } else if (!ata && sscanf(entry->d_name, "host%d", hosts + i) == 1) {
+ ++i;
+ }
+ }
+
+ qsort(hosts, i, sizeof(hosts[0]), compare_uint);
+
+ g_free(path);
+ closedir(dir);
+ return i;
+}
+
+/*
+ * Store disk device info for devices on the PCI bus.
+ * Returns true if information has been stored, or false for failure.
+ */
+static bool build_guest_fsinfo_for_pci_dev(char const *syspath,
+ GuestDiskAddress *disk,
+ Error **errp)
+{
+ unsigned int pci[4], host, hosts[8], tgt[3];
+ int i, nhosts = 0, pcilen;
+ GuestPCIAddress *pciaddr = disk->pci_controller;
+ bool has_ata = false, has_host = false, has_tgt = false;
+ char *p, *q, *driver = NULL;
+ bool ret = false;
+
+ p = strstr(syspath, "/devices/pci");
+ if (!p || sscanf(p + 12, "%*x:%*x/%x:%x:%x.%x%n",
+ pci, pci + 1, pci + 2, pci + 3, &pcilen) < 4) {
+ g_debug("only pci device is supported: sysfs path '%s'", syspath);
+ return false;
+ }
+
+ p += 12 + pcilen;
+ while (true) {
+ driver = get_pci_driver(syspath, p - syspath, errp);
+ if (driver && (g_str_equal(driver, "ata_piix") ||
+ g_str_equal(driver, "sym53c8xx") ||
+ g_str_equal(driver, "virtio-pci") ||
+ g_str_equal(driver, "ahci") ||
+ g_str_equal(driver, "nvme"))) {
+ break;
+ }
+
+ g_free(driver);
+ if (sscanf(p, "/%x:%x:%x.%x%n",
+ pci, pci + 1, pci + 2, pci + 3, &pcilen) == 4) {
+ p += pcilen;
+ continue;
+ }
+
+ g_debug("unsupported driver or sysfs path '%s'", syspath);
+ return false;
+ }
+
+ p = strstr(syspath, "/target");
+ if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u",
+ tgt, tgt + 1, tgt + 2) == 3) {
+ has_tgt = true;
+ }
+
+ p = strstr(syspath, "/ata");
+ if (p) {
+ q = p + 4;
+ has_ata = true;
+ } else {
+ p = strstr(syspath, "/host");
+ q = p + 5;
+ }
+ if (p && sscanf(q, "%u", &host) == 1) {
+ has_host = true;
+ nhosts = build_hosts(syspath, p, has_ata, hosts,
+ ARRAY_SIZE(hosts), errp);
+ if (nhosts < 0) {
+ goto cleanup;
+ }
+ }
+
+ pciaddr->domain = pci[0];
+ pciaddr->bus = pci[1];
+ pciaddr->slot = pci[2];
+ pciaddr->function = pci[3];
+
+ if (strcmp(driver, "ata_piix") == 0) {
+ /* a host per ide bus, target*:0:<unit>:0 */
+ if (!has_host || !has_tgt) {
+ g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver);
+ goto cleanup;
+ }
+ for (i = 0; i < nhosts; i++) {
+ if (host == hosts[i]) {
+ disk->bus_type = GUEST_DISK_BUS_TYPE_IDE;
+ disk->bus = i;
+ disk->unit = tgt[1];
+ break;
+ }
+ }
+ if (i >= nhosts) {
+ g_debug("no host for '%s' (driver '%s')", syspath, driver);
+ goto cleanup;
+ }
+ } else if (strcmp(driver, "sym53c8xx") == 0) {
+ /* scsi(LSI Logic): target*:0:<unit>:0 */
+ if (!has_tgt) {
+ g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver);
+ goto cleanup;
+ }
+ disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
+ disk->unit = tgt[1];
+ } else if (strcmp(driver, "virtio-pci") == 0) {
+ if (has_tgt) {
+ /* virtio-scsi: target*:0:0:<unit> */
+ disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
+ disk->unit = tgt[2];
+ } else {
+ /* virtio-blk: 1 disk per 1 device */
+ disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
+ }
+ } else if (strcmp(driver, "ahci") == 0) {
+ /* ahci: 1 host per 1 unit */
+ if (!has_host || !has_tgt) {
+ g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver);
+ goto cleanup;
+ }
+ for (i = 0; i < nhosts; i++) {
+ if (host == hosts[i]) {
+ disk->unit = i;
+ disk->bus_type = GUEST_DISK_BUS_TYPE_SATA;
+ break;
+ }
+ }
+ if (i >= nhosts) {
+ g_debug("no host for '%s' (driver '%s')", syspath, driver);
+ goto cleanup;
+ }
+ } else if (strcmp(driver, "nvme") == 0) {
+ disk->bus_type = GUEST_DISK_BUS_TYPE_NVME;
+ } else {
+ g_debug("unknown driver '%s' (sysfs path '%s')", driver, syspath);
+ goto cleanup;
+ }
+
+ ret = true;
+
+cleanup:
+ g_free(driver);
+ return ret;
+}
+
+/*
+ * Store disk device info for non-PCI virtio devices (for example s390x
+ * channel I/O devices). Returns true if information has been stored, or
+ * false for failure.
+ */
+static bool build_guest_fsinfo_for_nonpci_virtio(char const *syspath,
+ GuestDiskAddress *disk,
+ Error **errp)
+{
+ unsigned int tgt[3];
+ char *p;
+
+ if (!strstr(syspath, "/virtio") || !strstr(syspath, "/block")) {
+ g_debug("Unsupported virtio device '%s'", syspath);
+ return false;
+ }
+
+ p = strstr(syspath, "/target");
+ if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u",
+ &tgt[0], &tgt[1], &tgt[2]) == 3) {
+ /* virtio-scsi: target*:0:<target>:<unit> */
+ disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
+ disk->bus = tgt[0];
+ disk->target = tgt[1];
+ disk->unit = tgt[2];
+ } else {
+ /* virtio-blk: 1 disk per 1 device */
+ disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
+ }
+
+ return true;
+}
+
+/*
+ * Store disk device info for CCW devices (s390x channel I/O devices).
+ * Returns true if information has been stored, or false for failure.
+ */
+static bool build_guest_fsinfo_for_ccw_dev(char const *syspath,
+ GuestDiskAddress *disk,
+ Error **errp)
+{
+ unsigned int cssid, ssid, subchno, devno;
+ char *p;
+
+ p = strstr(syspath, "/devices/css");
+ if (!p || sscanf(p + 12, "%*x/%x.%x.%x/%*x.%*x.%x/",
+ &cssid, &ssid, &subchno, &devno) < 4) {
+ g_debug("could not parse ccw device sysfs path: %s", syspath);
+ return false;
+ }
+
+ disk->has_ccw_address = true;
+ disk->ccw_address = g_new0(GuestCCWAddress, 1);
+ disk->ccw_address->cssid = cssid;
+ disk->ccw_address->ssid = ssid;
+ disk->ccw_address->subchno = subchno;
+ disk->ccw_address->devno = devno;
+
+ if (strstr(p, "/virtio")) {
+ build_guest_fsinfo_for_nonpci_virtio(syspath, disk, errp);
+ }
+
+ return true;
+}
+
+/* Store disk device info specified by @sysfs into @fs */
+static void build_guest_fsinfo_for_real_device(char const *syspath,
+ GuestFilesystemInfo *fs,
+ Error **errp)
+{
+ GuestDiskAddress *disk;
+ GuestPCIAddress *pciaddr;
+ bool has_hwinf;
+#ifdef CONFIG_LIBUDEV
+ struct udev *udev = NULL;
+ struct udev_device *udevice = NULL;
+#endif
+
+ pciaddr = g_new0(GuestPCIAddress, 1);
+ pciaddr->domain = -1; /* -1 means field is invalid */
+ pciaddr->bus = -1;
+ pciaddr->slot = -1;
+ pciaddr->function = -1;
+
+ disk = g_new0(GuestDiskAddress, 1);
+ disk->pci_controller = pciaddr;
+ disk->bus_type = GUEST_DISK_BUS_TYPE_UNKNOWN;
+
+#ifdef CONFIG_LIBUDEV
+ udev = udev_new();
+ udevice = udev_device_new_from_syspath(udev, syspath);
+ if (udev == NULL || udevice == NULL) {
+ g_debug("failed to query udev");
+ } else {
+ const char *devnode, *serial;
+ devnode = udev_device_get_devnode(udevice);
+ if (devnode != NULL) {
+ disk->dev = g_strdup(devnode);
+ disk->has_dev = true;
+ }
+ serial = udev_device_get_property_value(udevice, "ID_SERIAL");
+ if (serial != NULL && *serial != 0) {
+ disk->serial = g_strdup(serial);
+ disk->has_serial = true;
+ }
+ }
+
+ udev_unref(udev);
+ udev_device_unref(udevice);
+#endif
+
+ if (strstr(syspath, "/devices/pci")) {
+ has_hwinf = build_guest_fsinfo_for_pci_dev(syspath, disk, errp);
+ } else if (strstr(syspath, "/devices/css")) {
+ has_hwinf = build_guest_fsinfo_for_ccw_dev(syspath, disk, errp);
+ } else if (strstr(syspath, "/virtio")) {
+ has_hwinf = build_guest_fsinfo_for_nonpci_virtio(syspath, disk, errp);
+ } else {
+ g_debug("Unsupported device type for '%s'", syspath);
+ has_hwinf = false;
+ }
+
+ if (has_hwinf || disk->has_dev || disk->has_serial) {
+ QAPI_LIST_PREPEND(fs->disk, disk);
+ } else {
+ qapi_free_GuestDiskAddress(disk);
+ }
+}
+
+static void build_guest_fsinfo_for_device(char const *devpath,
+ GuestFilesystemInfo *fs,
+ Error **errp);
+
+/* Store a list of slave devices of virtual volume specified by @syspath into
+ * @fs */
+static void build_guest_fsinfo_for_virtual_device(char const *syspath,
+ GuestFilesystemInfo *fs,
+ Error **errp)
+{
+ Error *err = NULL;
+ DIR *dir;
+ char *dirpath;
+ struct dirent *entry;
+
+ dirpath = g_strdup_printf("%s/slaves", syspath);
+ dir = opendir(dirpath);
+ if (!dir) {
+ if (errno != ENOENT) {
+ error_setg_errno(errp, errno, "opendir(\"%s\")", dirpath);
+ }
+ g_free(dirpath);
+ return;
+ }
+
+ for (;;) {
+ errno = 0;
+ entry = readdir(dir);
+ if (entry == NULL) {
+ if (errno) {
+ error_setg_errno(errp, errno, "readdir(\"%s\")", dirpath);
+ }
+ break;
+ }
+
+ if (entry->d_type == DT_LNK) {
+ char *path;
+
+ g_debug(" slave device '%s'", entry->d_name);
+ path = g_strdup_printf("%s/slaves/%s", syspath, entry->d_name);
+ build_guest_fsinfo_for_device(path, fs, &err);
+ g_free(path);
+
+ if (err) {
+ error_propagate(errp, err);
+ break;
+ }
+ }
+ }
+
+ g_free(dirpath);
+ closedir(dir);
+}
+
+static bool is_disk_virtual(const char *devpath, Error **errp)
+{
+ g_autofree char *syspath = realpath(devpath, NULL);
+
+ if (!syspath) {
+ error_setg_errno(errp, errno, "realpath(\"%s\")", devpath);
+ return false;
+ }
+ return strstr(syspath, "/devices/virtual/block/") != NULL;
+}
+
+/* Dispatch to functions for virtual/real device */
+static void build_guest_fsinfo_for_device(char const *devpath,
+ GuestFilesystemInfo *fs,
+ Error **errp)
+{
+ ERRP_GUARD();
+ g_autofree char *syspath = NULL;
+ bool is_virtual = false;
+
+ syspath = realpath(devpath, NULL);
+ if (!syspath) {
+ if (errno != ENOENT) {
+ error_setg_errno(errp, errno, "realpath(\"%s\")", devpath);
+ return;
+ }
+
+ /* ENOENT: This devpath may not exist because of container config */
+ if (!fs->name) {
+ fs->name = g_path_get_basename(devpath);
+ }
+ return;
+ }
+
+ if (!fs->name) {
+ fs->name = g_path_get_basename(syspath);
+ }
+
+ g_debug(" parse sysfs path '%s'", syspath);
+ is_virtual = is_disk_virtual(syspath, errp);
+ if (*errp != NULL) {
+ return;
+ }
+ if (is_virtual) {
+ build_guest_fsinfo_for_virtual_device(syspath, fs, errp);
+ } else {
+ build_guest_fsinfo_for_real_device(syspath, fs, errp);
+ }
+}
+
+#ifdef CONFIG_LIBUDEV
+
+/*
+ * Wrapper around build_guest_fsinfo_for_device() for getting just
+ * the disk address.
+ */
+static GuestDiskAddress *get_disk_address(const char *syspath, Error **errp)
+{
+ g_autoptr(GuestFilesystemInfo) fs = NULL;
+
+ fs = g_new0(GuestFilesystemInfo, 1);
+ build_guest_fsinfo_for_device(syspath, fs, errp);
+ if (fs->disk != NULL) {
+ return g_steal_pointer(&fs->disk->value);
+ }
+ return NULL;
+}
+
+static char *get_alias_for_syspath(const char *syspath)
+{
+ struct udev *udev = NULL;
+ struct udev_device *udevice = NULL;
+ char *ret = NULL;
+
+ udev = udev_new();
+ if (udev == NULL) {
+ g_debug("failed to query udev");
+ goto out;
+ }
+ udevice = udev_device_new_from_syspath(udev, syspath);
+ if (udevice == NULL) {
+ g_debug("failed to query udev for path: %s", syspath);
+ goto out;
+ } else {
+ const char *alias = udev_device_get_property_value(
+ udevice, "DM_NAME");
+ /*
+ * NULL means there was an error and empty string means there is no
+ * alias. In case of no alias we return NULL instead of empty string.
+ */
+ if (alias == NULL) {
+ g_debug("failed to query udev for device alias for: %s",
+ syspath);
+ } else if (*alias != 0) {
+ ret = g_strdup(alias);
+ }
+ }
+
+out:
+ udev_unref(udev);
+ udev_device_unref(udevice);
+ return ret;
+}
+
+static char *get_device_for_syspath(const char *syspath)
+{
+ struct udev *udev = NULL;
+ struct udev_device *udevice = NULL;
+ char *ret = NULL;
+
+ udev = udev_new();
+ if (udev == NULL) {
+ g_debug("failed to query udev");
+ goto out;
+ }
+ udevice = udev_device_new_from_syspath(udev, syspath);
+ if (udevice == NULL) {
+ g_debug("failed to query udev for path: %s", syspath);
+ goto out;
+ } else {
+ ret = g_strdup(udev_device_get_devnode(udevice));
+ }
+
+out:
+ udev_unref(udev);
+ udev_device_unref(udevice);
+ return ret;
+}
+
+static void get_disk_deps(const char *disk_dir, GuestDiskInfo *disk)
+{
+ g_autofree char *deps_dir = NULL;
+ const gchar *dep;
+ GDir *dp_deps = NULL;
+
+ /* List dependent disks */
+ deps_dir = g_strdup_printf("%s/slaves", disk_dir);
+ g_debug(" listing entries in: %s", deps_dir);
+ dp_deps = g_dir_open(deps_dir, 0, NULL);
+ if (dp_deps == NULL) {
+ g_debug("failed to list entries in %s", deps_dir);
+ return;
+ }
+ disk->has_dependencies = true;
+ while ((dep = g_dir_read_name(dp_deps)) != NULL) {
+ g_autofree char *dep_dir = NULL;
+ char *dev_name;
+
+ /* Add dependent disks */
+ dep_dir = g_strdup_printf("%s/%s", deps_dir, dep);
+ dev_name = get_device_for_syspath(dep_dir);
+ if (dev_name != NULL) {
+ g_debug(" adding dependent device: %s", dev_name);
+ QAPI_LIST_PREPEND(disk->dependencies, dev_name);
+ }
+ }
+ g_dir_close(dp_deps);
+}
+
+/*
+ * Detect partitions subdirectory, name is "<disk_name><number>" or
+ * "<disk_name>p<number>"
+ *
+ * @disk_name -- last component of /sys path (e.g. sda)
+ * @disk_dir -- sys path of the disk (e.g. /sys/block/sda)
+ * @disk_dev -- device node of the disk (e.g. /dev/sda)
+ */
+static GuestDiskInfoList *get_disk_partitions(
+ GuestDiskInfoList *list,
+ const char *disk_name, const char *disk_dir,
+ const char *disk_dev)
+{
+ GuestDiskInfoList *ret = list;
+ struct dirent *de_disk;
+ DIR *dp_disk = NULL;
+ size_t len = strlen(disk_name);
+
+ dp_disk = opendir(disk_dir);
+ while ((de_disk = readdir(dp_disk)) != NULL) {
+ g_autofree char *partition_dir = NULL;
+ char *dev_name;
+ GuestDiskInfo *partition;
+
+ if (!(de_disk->d_type & DT_DIR)) {
+ continue;
+ }
+
+ if (!(strncmp(disk_name, de_disk->d_name, len) == 0 &&
+ ((*(de_disk->d_name + len) == 'p' &&
+ isdigit(*(de_disk->d_name + len + 1))) ||
+ isdigit(*(de_disk->d_name + len))))) {
+ continue;
+ }
+
+ partition_dir = g_strdup_printf("%s/%s",
+ disk_dir, de_disk->d_name);
+ dev_name = get_device_for_syspath(partition_dir);
+ if (dev_name == NULL) {
+ g_debug("Failed to get device name for syspath: %s",
+ disk_dir);
+ continue;
+ }
+ partition = g_new0(GuestDiskInfo, 1);
+ partition->name = dev_name;
+ partition->partition = true;
+ partition->has_dependencies = true;
+ /* Add parent disk as dependent for easier tracking of hierarchy */
+ QAPI_LIST_PREPEND(partition->dependencies, g_strdup(disk_dev));
+
+ QAPI_LIST_PREPEND(ret, partition);
+ }
+ closedir(dp_disk);
+
+ return ret;
+}
+
+static void get_nvme_smart(GuestDiskInfo *disk)
+{
+ int fd;
+ GuestNVMeSmart *smart;
+ NvmeSmartLog log = {0};
+ struct nvme_admin_cmd cmd = {
+ .opcode = NVME_ADM_CMD_GET_LOG_PAGE,
+ .nsid = NVME_NSID_BROADCAST,
+ .addr = (uintptr_t)&log,
+ .data_len = sizeof(log),
+ .cdw10 = NVME_LOG_SMART_INFO | (1 << 15) /* RAE bit */
+ | (((sizeof(log) >> 2) - 1) << 16)
+ };
+
+ fd = qga_open_cloexec(disk->name, O_RDONLY, 0);
+ if (fd == -1) {
+ g_debug("Failed to open device: %s: %s", disk->name, g_strerror(errno));
+ return;
+ }
+
+ if (ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd)) {
+ g_debug("Failed to get smart: %s: %s", disk->name, g_strerror(errno));
+ close(fd);
+ return;
+ }
+
+ disk->has_smart = true;
+ disk->smart = g_new0(GuestDiskSmart, 1);
+ disk->smart->type = GUEST_DISK_BUS_TYPE_NVME;
+
+ smart = &disk->smart->u.nvme;
+ smart->critical_warning = log.critical_warning;
+ smart->temperature = lduw_le_p(&log.temperature); /* unaligned field */
+ smart->available_spare = log.available_spare;
+ smart->available_spare_threshold = log.available_spare_threshold;
+ smart->percentage_used = log.percentage_used;
+ smart->data_units_read_lo = le64_to_cpu(log.data_units_read[0]);
+ smart->data_units_read_hi = le64_to_cpu(log.data_units_read[1]);
+ smart->data_units_written_lo = le64_to_cpu(log.data_units_written[0]);
+ smart->data_units_written_hi = le64_to_cpu(log.data_units_written[1]);
+ smart->host_read_commands_lo = le64_to_cpu(log.host_read_commands[0]);
+ smart->host_read_commands_hi = le64_to_cpu(log.host_read_commands[1]);
+ smart->host_write_commands_lo = le64_to_cpu(log.host_write_commands[0]);
+ smart->host_write_commands_hi = le64_to_cpu(log.host_write_commands[1]);
+ smart->controller_busy_time_lo = le64_to_cpu(log.controller_busy_time[0]);
+ smart->controller_busy_time_hi = le64_to_cpu(log.controller_busy_time[1]);
+ smart->power_cycles_lo = le64_to_cpu(log.power_cycles[0]);
+ smart->power_cycles_hi = le64_to_cpu(log.power_cycles[1]);
+ smart->power_on_hours_lo = le64_to_cpu(log.power_on_hours[0]);
+ smart->power_on_hours_hi = le64_to_cpu(log.power_on_hours[1]);
+ smart->unsafe_shutdowns_lo = le64_to_cpu(log.unsafe_shutdowns[0]);
+ smart->unsafe_shutdowns_hi = le64_to_cpu(log.unsafe_shutdowns[1]);
+ smart->media_errors_lo = le64_to_cpu(log.media_errors[0]);
+ smart->media_errors_hi = le64_to_cpu(log.media_errors[1]);
+ smart->number_of_error_log_entries_lo =
+ le64_to_cpu(log.number_of_error_log_entries[0]);
+ smart->number_of_error_log_entries_hi =
+ le64_to_cpu(log.number_of_error_log_entries[1]);
+
+ close(fd);
+}
+
+static void get_disk_smart(GuestDiskInfo *disk)
+{
+ if (disk->has_address
+ && (disk->address->bus_type == GUEST_DISK_BUS_TYPE_NVME)) {
+ get_nvme_smart(disk);
+ }
+}
+
+GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
+{
+ GuestDiskInfoList *ret = NULL;
+ GuestDiskInfo *disk;
+ DIR *dp = NULL;
+ struct dirent *de = NULL;
+
+ g_debug("listing /sys/block directory");
+ dp = opendir("/sys/block");
+ if (dp == NULL) {
+ error_setg_errno(errp, errno, "Can't open directory \"/sys/block\"");
+ return NULL;
+ }
+ while ((de = readdir(dp)) != NULL) {
+ g_autofree char *disk_dir = NULL, *line = NULL,
+ *size_path = NULL;
+ char *dev_name;
+ Error *local_err = NULL;
+ if (de->d_type != DT_LNK) {
+ g_debug(" skipping entry: %s", de->d_name);
+ continue;
+ }
+
+ /* Check size and skip zero-sized disks */
+ g_debug(" checking disk size");
+ size_path = g_strdup_printf("/sys/block/%s/size", de->d_name);
+ if (!g_file_get_contents(size_path, &line, NULL, NULL)) {
+ g_debug(" failed to read disk size");
+ continue;
+ }
+ if (g_strcmp0(line, "0\n") == 0) {
+ g_debug(" skipping zero-sized disk");
+ continue;
+ }
+
+ g_debug(" adding %s", de->d_name);
+ disk_dir = g_strdup_printf("/sys/block/%s", de->d_name);
+ dev_name = get_device_for_syspath(disk_dir);
+ if (dev_name == NULL) {
+ g_debug("Failed to get device name for syspath: %s",
+ disk_dir);
+ continue;
+ }
+ disk = g_new0(GuestDiskInfo, 1);
+ disk->name = dev_name;
+ disk->partition = false;
+ disk->alias = get_alias_for_syspath(disk_dir);
+ disk->has_alias = (disk->alias != NULL);
+ QAPI_LIST_PREPEND(ret, disk);
+
+ /* Get address for non-virtual devices */
+ bool is_virtual = is_disk_virtual(disk_dir, &local_err);
+ if (local_err != NULL) {
+ g_debug(" failed to check disk path, ignoring error: %s",
+ error_get_pretty(local_err));
+ error_free(local_err);
+ local_err = NULL;
+ /* Don't try to get the address */
+ is_virtual = true;
+ }
+ if (!is_virtual) {
+ disk->address = get_disk_address(disk_dir, &local_err);
+ if (local_err != NULL) {
+ g_debug(" failed to get device info, ignoring error: %s",
+ error_get_pretty(local_err));
+ error_free(local_err);
+ local_err = NULL;
+ } else if (disk->address != NULL) {
+ disk->has_address = true;
+ }
+ }
+
+ get_disk_deps(disk_dir, disk);
+ get_disk_smart(disk);
+ ret = get_disk_partitions(ret, de->d_name, disk_dir, dev_name);
+ }
+
+ closedir(dp);
+
+ return ret;
+}
+
+#else
+
+GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+#endif
+
+/* Return a list of the disk device(s)' info which @mount lies on */
+static GuestFilesystemInfo *build_guest_fsinfo(struct FsMount *mount,
+ Error **errp)
+{
+ GuestFilesystemInfo *fs = g_malloc0(sizeof(*fs));
+ struct statvfs buf;
+ unsigned long used, nonroot_total, fr_size;
+ char *devpath = g_strdup_printf("/sys/dev/block/%u:%u",
+ mount->devmajor, mount->devminor);
+
+ fs->mountpoint = g_strdup(mount->dirname);
+ fs->type = g_strdup(mount->devtype);
+ build_guest_fsinfo_for_device(devpath, fs, errp);
+
+ if (statvfs(fs->mountpoint, &buf) == 0) {
+ fr_size = buf.f_frsize;
+ used = buf.f_blocks - buf.f_bfree;
+ nonroot_total = used + buf.f_bavail;
+ fs->used_bytes = used * fr_size;
+ fs->total_bytes = nonroot_total * fr_size;
+
+ fs->has_total_bytes = true;
+ fs->has_used_bytes = true;
+ }
+
+ g_free(devpath);
+
+ return fs;
+}
+
+GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
+{
+ FsMountList mounts;
+ struct FsMount *mount;
+ GuestFilesystemInfoList *ret = NULL;
+ Error *local_err = NULL;
+
+ QTAILQ_INIT(&mounts);
+ if (!build_fs_mount_list(&mounts, &local_err)) {
+ error_propagate(errp, local_err);
+ return NULL;
+ }
+
+ QTAILQ_FOREACH(mount, &mounts, next) {
+ g_debug("Building guest fsinfo for '%s'", mount->dirname);
+
+ QAPI_LIST_PREPEND(ret, build_guest_fsinfo(mount, &local_err));
+ if (local_err) {
+ error_propagate(errp, local_err);
+ qapi_free_GuestFilesystemInfoList(ret);
+ ret = NULL;
+ break;
+ }
+ }
+
+ free_fs_mount_list(&mounts);
+ return ret;
+}
+
+/*
+ * Walk list of mounted file systems in the guest, and freeze the ones which
+ * are real local file systems.
+ */
+int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints,
+ strList *mountpoints,
+ FsMountList mounts,
+ Error **errp)
+{
+ struct FsMount *mount;
+ strList *list;
+ int fd, ret, i = 0;
+
+ QTAILQ_FOREACH_REVERSE(mount, &mounts, next) {
+ /* To issue fsfreeze in the reverse order of mounts, check if the
+ * mount is listed in the list here */
+ if (has_mountpoints) {
+ for (list = mountpoints; list; list = list->next) {
+ if (strcmp(list->value, mount->dirname) == 0) {
+ break;
+ }
+ }
+ if (!list) {
+ continue;
+ }
+ }
+
+ fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
+ if (fd == -1) {
+ error_setg_errno(errp, errno, "failed to open %s", mount->dirname);
+ return -1;
+ }
+
+ /* we try to cull filesystems we know won't work in advance, but other
+ * filesystems may not implement fsfreeze for less obvious reasons.
+ * these will report EOPNOTSUPP. we simply ignore these when tallying
+ * the number of frozen filesystems.
+ * if a filesystem is mounted more than once (aka bind mount) a
+ * consecutive attempt to freeze an already frozen filesystem will
+ * return EBUSY.
+ *
+ * any other error means a failure to freeze a filesystem we
+ * expect to be freezable, so return an error in those cases
+ * and return system to thawed state.
+ */
+ ret = ioctl(fd, FIFREEZE);
+ if (ret == -1) {
+ if (errno != EOPNOTSUPP && errno != EBUSY) {
+ error_setg_errno(errp, errno, "failed to freeze %s",
+ mount->dirname);
+ close(fd);
+ return -1;
+ }
+ } else {
+ i++;
+ }
+ close(fd);
+ }
+ return i;
+}
+
+int qmp_guest_fsfreeze_do_thaw(Error **errp)
+{
+ int ret;
+ FsMountList mounts;
+ FsMount *mount;
+ int fd, i = 0, logged;
+ Error *local_err = NULL;
+
+ QTAILQ_INIT(&mounts);
+ if (!build_fs_mount_list(&mounts, &local_err)) {
+ error_propagate(errp, local_err);
+ return -1;
+ }
+
+ QTAILQ_FOREACH(mount, &mounts, next) {
+ logged = false;
+ fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
+ if (fd == -1) {
+ continue;
+ }
+ /* we have no way of knowing whether a filesystem was actually unfrozen
+ * as a result of a successful call to FITHAW, only that if an error
+ * was returned the filesystem was *not* unfrozen by that particular
+ * call.
+ *
+ * since multiple preceding FIFREEZEs require multiple calls to FITHAW
+ * to unfreeze, continuing issuing FITHAW until an error is returned,
+ * in which case either the filesystem is in an unfreezable state, or,
+ * more likely, it was thawed previously (and remains so afterward).
+ *
+ * also, since the most recent successful call is the one that did
+ * the actual unfreeze, we can use this to provide an accurate count
+ * of the number of filesystems unfrozen by guest-fsfreeze-thaw, which
+ * may * be useful for determining whether a filesystem was unfrozen
+ * during the freeze/thaw phase by a process other than qemu-ga.
+ */
+ do {
+ ret = ioctl(fd, FITHAW);
+ if (ret == 0 && !logged) {
+ i++;
+ logged = true;
+ }
+ } while (ret == 0);
+ close(fd);
+ }
+
+ free_fs_mount_list(&mounts);
+
+ return i;
+}
+#endif /* CONFIG_FSFREEZE */
+
+#if defined(CONFIG_FSTRIM)
+/*
+ * Walk list of mounted file systems in the guest, and trim them.
+ */
+GuestFilesystemTrimResponse *
+qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
+{
+ GuestFilesystemTrimResponse *response;
+ GuestFilesystemTrimResult *result;
+ int ret = 0;
+ FsMountList mounts;
+ struct FsMount *mount;
+ int fd;
+ struct fstrim_range r;
+
+ slog("guest-fstrim called");
+
+ QTAILQ_INIT(&mounts);
+ if (!build_fs_mount_list(&mounts, errp)) {
+ return NULL;
+ }
+
+ response = g_malloc0(sizeof(*response));
+
+ QTAILQ_FOREACH(mount, &mounts, next) {
+ result = g_malloc0(sizeof(*result));
+ result->path = g_strdup(mount->dirname);
+
+ QAPI_LIST_PREPEND(response->paths, result);
+
+ fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
+ if (fd == -1) {
+ result->error = g_strdup_printf("failed to open: %s",
+ strerror(errno));
+ result->has_error = true;
+ continue;
+ }
+
+ /* We try to cull filesystems we know won't work in advance, but other
+ * filesystems may not implement fstrim for less obvious reasons.
+ * These will report EOPNOTSUPP; while in some other cases ENOTTY
+ * will be reported (e.g. CD-ROMs).
+ * Any other error means an unexpected error.
+ */
+ r.start = 0;
+ r.len = -1;
+ r.minlen = has_minimum ? minimum : 0;
+ ret = ioctl(fd, FITRIM, &r);
+ if (ret == -1) {
+ result->has_error = true;
+ if (errno == ENOTTY || errno == EOPNOTSUPP) {
+ result->error = g_strdup("trim not supported");
+ } else {
+ result->error = g_strdup_printf("failed to trim: %s",
+ strerror(errno));
+ }
+ close(fd);
+ continue;
+ }
+
+ result->has_minimum = true;
+ result->minimum = r.minlen;
+ result->has_trimmed = true;
+ result->trimmed = r.len;
+ close(fd);
+ }
+
+ free_fs_mount_list(&mounts);
+ return response;
+}
+#endif /* CONFIG_FSTRIM */
+
+
+#define LINUX_SYS_STATE_FILE "/sys/power/state"
+#define SUSPEND_SUPPORTED 0
+#define SUSPEND_NOT_SUPPORTED 1
+
+typedef enum {
+ SUSPEND_MODE_DISK = 0,
+ SUSPEND_MODE_RAM = 1,
+ SUSPEND_MODE_HYBRID = 2,
+} SuspendMode;
+
+/*
+ * Executes a command in a child process using g_spawn_sync,
+ * returning an int >= 0 representing the exit status of the
+ * process.
+ *
+ * If the program wasn't found in path, returns -1.
+ *
+ * If a problem happened when creating the child process,
+ * returns -1 and errp is set.
+ */
+static int run_process_child(const char *command[], Error **errp)
+{
+ int exit_status, spawn_flag;
+ GError *g_err = NULL;
+ bool success;
+
+ spawn_flag = G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL |
+ G_SPAWN_STDERR_TO_DEV_NULL;
+
+ success = g_spawn_sync(NULL, (char **)command, NULL, spawn_flag,
+ NULL, NULL, NULL, NULL,
+ &exit_status, &g_err);
+
+ if (success) {
+ return WEXITSTATUS(exit_status);
+ }
+
+ if (g_err && (g_err->code != G_SPAWN_ERROR_NOENT)) {
+ error_setg(errp, "failed to create child process, error '%s'",
+ g_err->message);
+ }
+
+ g_error_free(g_err);
+ return -1;
+}
+
+static bool systemd_supports_mode(SuspendMode mode, Error **errp)
+{
+ const char *systemctl_args[3] = {"systemd-hibernate", "systemd-suspend",
+ "systemd-hybrid-sleep"};
+ const char *cmd[4] = {"systemctl", "status", systemctl_args[mode], NULL};
+ int status;
+
+ status = run_process_child(cmd, errp);
+
+ /*
+ * systemctl status uses LSB return codes so we can expect
+ * status > 0 and be ok. To assert if the guest has support
+ * for the selected suspend mode, status should be < 4. 4 is
+ * the code for unknown service status, the return value when
+ * the service does not exist. A common value is status = 3
+ * (program is not running).
+ */
+ if (status > 0 && status < 4) {
+ return true;
+ }
+
+ return false;
+}
+
+static void systemd_suspend(SuspendMode mode, Error **errp)
+{
+ Error *local_err = NULL;
+ const char *systemctl_args[3] = {"hibernate", "suspend", "hybrid-sleep"};
+ const char *cmd[3] = {"systemctl", systemctl_args[mode], NULL};
+ int status;
+
+ status = run_process_child(cmd, &local_err);
+
+ if (status == 0) {
+ return;
+ }
+
+ if ((status == -1) && !local_err) {
+ error_setg(errp, "the helper program 'systemctl %s' was not found",
+ systemctl_args[mode]);
+ return;
+ }
+
+ if (local_err) {
+ error_propagate(errp, local_err);
+ } else {
+ error_setg(errp, "the helper program 'systemctl %s' returned an "
+ "unexpected exit status code (%d)",
+ systemctl_args[mode], status);
+ }
+}
+
+static bool pmutils_supports_mode(SuspendMode mode, Error **errp)
+{
+ Error *local_err = NULL;
+ const char *pmutils_args[3] = {"--hibernate", "--suspend",
+ "--suspend-hybrid"};
+ const char *cmd[3] = {"pm-is-supported", pmutils_args[mode], NULL};
+ int status;
+
+ status = run_process_child(cmd, &local_err);
+
+ if (status == SUSPEND_SUPPORTED) {
+ return true;
+ }
+
+ if ((status == -1) && !local_err) {
+ return false;
+ }
+
+ if (local_err) {
+ error_propagate(errp, local_err);
+ } else {
+ error_setg(errp,
+ "the helper program '%s' returned an unexpected exit"
+ " status code (%d)", "pm-is-supported", status);
+ }
+
+ return false;
+}
+
+static void pmutils_suspend(SuspendMode mode, Error **errp)
+{
+ Error *local_err = NULL;
+ const char *pmutils_binaries[3] = {"pm-hibernate", "pm-suspend",
+ "pm-suspend-hybrid"};
+ const char *cmd[2] = {pmutils_binaries[mode], NULL};
+ int status;
+
+ status = run_process_child(cmd, &local_err);
+
+ if (status == 0) {
+ return;
+ }
+
+ if ((status == -1) && !local_err) {
+ error_setg(errp, "the helper program '%s' was not found",
+ pmutils_binaries[mode]);
+ return;
+ }
+
+ if (local_err) {
+ error_propagate(errp, local_err);
+ } else {
+ error_setg(errp,
+ "the helper program '%s' returned an unexpected exit"
+ " status code (%d)", pmutils_binaries[mode], status);
+ }
+}
+
+static bool linux_sys_state_supports_mode(SuspendMode mode, Error **errp)
+{
+ const char *sysfile_strs[3] = {"disk", "mem", NULL};
+ const char *sysfile_str = sysfile_strs[mode];
+ char buf[32]; /* hopefully big enough */
+ int fd;
+ ssize_t ret;
+
+ if (!sysfile_str) {
+ error_setg(errp, "unknown guest suspend mode");
+ return false;
+ }
+
+ fd = open(LINUX_SYS_STATE_FILE, O_RDONLY);
+ if (fd < 0) {
+ return false;
+ }
+
+ ret = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+ if (ret <= 0) {
+ return false;
+ }
+ buf[ret] = '\0';
+
+ if (strstr(buf, sysfile_str)) {
+ return true;
+ }
+ return false;
+}
+
+static void linux_sys_state_suspend(SuspendMode mode, Error **errp)
+{
+ Error *local_err = NULL;
+ const char *sysfile_strs[3] = {"disk", "mem", NULL};
+ const char *sysfile_str = sysfile_strs[mode];
+ pid_t pid;
+ int status;
+
+ if (!sysfile_str) {
+ error_setg(errp, "unknown guest suspend mode");
+ return;
+ }
+
+ pid = fork();
+ if (!pid) {
+ /* child */
+ int fd;
+
+ setsid();
+ reopen_fd_to_null(0);
+ reopen_fd_to_null(1);
+ reopen_fd_to_null(2);
+
+ fd = open(LINUX_SYS_STATE_FILE, O_WRONLY);
+ if (fd < 0) {
+ _exit(EXIT_FAILURE);
+ }
+
+ if (write(fd, sysfile_str, strlen(sysfile_str)) < 0) {
+ _exit(EXIT_FAILURE);
+ }
+
+ _exit(EXIT_SUCCESS);
+ } else if (pid < 0) {
+ error_setg_errno(errp, errno, "failed to create child process");
+ return;
+ }
+
+ ga_wait_child(pid, &status, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ if (WEXITSTATUS(status)) {
+ error_setg(errp, "child process has failed to suspend");
+ }
+
+}
+
+static void guest_suspend(SuspendMode mode, Error **errp)
+{
+ Error *local_err = NULL;
+ bool mode_supported = false;
+
+ if (systemd_supports_mode(mode, &local_err)) {
+ mode_supported = true;
+ systemd_suspend(mode, &local_err);
+ }
+
+ if (!local_err) {
+ return;
+ }
+
+ error_free(local_err);
+ local_err = NULL;
+
+ if (pmutils_supports_mode(mode, &local_err)) {
+ mode_supported = true;
+ pmutils_suspend(mode, &local_err);
+ }
+
+ if (!local_err) {
+ return;
+ }
+
+ error_free(local_err);
+ local_err = NULL;
+
+ if (linux_sys_state_supports_mode(mode, &local_err)) {
+ mode_supported = true;
+ linux_sys_state_suspend(mode, &local_err);
+ }
+
+ if (!mode_supported) {
+ error_free(local_err);
+ error_setg(errp,
+ "the requested suspend mode is not supported by the guest");
+ } else {
+ error_propagate(errp, local_err);
+ }
+}
+
+void qmp_guest_suspend_disk(Error **errp)
+{
+ guest_suspend(SUSPEND_MODE_DISK, errp);
+}
+
+void qmp_guest_suspend_ram(Error **errp)
+{
+ guest_suspend(SUSPEND_MODE_RAM, errp);
+}
+
+void qmp_guest_suspend_hybrid(Error **errp)
+{
+ guest_suspend(SUSPEND_MODE_HYBRID, errp);
+}
+
+/* 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,
+ char *dirpath, Error **errp)
+{
+ int fd;
+ int res;
+ int dirfd;
+ static const char fn[] = "online";
+
+ dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
+ if (dirfd == -1) {
+ error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
+ return;
+ }
+
+ 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);
+}
+
+GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
+{
+ GuestLogicalProcessorList *head, **tail;
+ const char *cpu_dir = "/sys/devices/system/cpu";
+ const gchar *line;
+ g_autoptr(GDir) cpu_gdir = NULL;
+ Error *local_err = NULL;
+
+ head = NULL;
+ tail = &head;
+ cpu_gdir = g_dir_open(cpu_dir, 0, NULL);
+
+ if (cpu_gdir == NULL) {
+ error_setg_errno(errp, errno, "failed to list entries: %s", cpu_dir);
+ return NULL;
+ }
+
+ while (local_err == NULL && (line = g_dir_read_name(cpu_gdir)) != NULL) {
+ GuestLogicalProcessor *vcpu;
+ int64_t id;
+ if (sscanf(line, "cpu%" PRId64, &id)) {
+ g_autofree char *path = g_strdup_printf("/sys/devices/system/cpu/"
+ "cpu%" PRId64 "/", id);
+ vcpu = g_malloc0(sizeof *vcpu);
+ vcpu->logical_id = id;
+ vcpu->has_can_offline = true; /* lolspeak ftw */
+ transfer_vcpu(vcpu, true, path, &local_err);
+ QAPI_LIST_APPEND(tail, vcpu);
+ }
+ }
+
+ 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) {
+ char *path = g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/",
+ vcpus->value->logical_id);
+
+ transfer_vcpu(vcpus->value, false, path, &local_err);
+ g_free(path);
+ 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;
+}
+
+void qmp_guest_set_user_password(const char *username,
+ const char *password,
+ bool crypted,
+ Error **errp)
+{
+ Error *local_err = NULL;
+ char *passwd_path = NULL;
+ pid_t pid;
+ int status;
+ int datafd[2] = { -1, -1 };
+ char *rawpasswddata = NULL;
+ size_t rawpasswdlen;
+ char *chpasswddata = NULL;
+ size_t chpasswdlen;
+
+ rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp);
+ if (!rawpasswddata) {
+ return;
+ }
+ rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
+ rawpasswddata[rawpasswdlen] = '\0';
+
+ if (strchr(rawpasswddata, '\n')) {
+ error_setg(errp, "forbidden characters in raw password");
+ goto out;
+ }
+
+ if (strchr(username, '\n') ||
+ strchr(username, ':')) {
+ error_setg(errp, "forbidden characters in username");
+ goto out;
+ }
+
+ chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata);
+ chpasswdlen = strlen(chpasswddata);
+
+ passwd_path = g_find_program_in_path("chpasswd");
+
+ if (!passwd_path) {
+ error_setg(errp, "cannot find 'passwd' program in PATH");
+ goto out;
+ }
+
+ if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) {
+ error_setg(errp, "cannot create pipe FDs");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid == 0) {
+ close(datafd[1]);
+ /* child */
+ setsid();
+ dup2(datafd[0], 0);
+ reopen_fd_to_null(1);
+ reopen_fd_to_null(2);
+
+ if (crypted) {
+ execl(passwd_path, "chpasswd", "-e", NULL);
+ } else {
+ execl(passwd_path, "chpasswd", NULL);
+ }
+ _exit(EXIT_FAILURE);
+ } else if (pid < 0) {
+ error_setg_errno(errp, errno, "failed to create child process");
+ goto out;
+ }
+ close(datafd[0]);
+ datafd[0] = -1;
+
+ if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) != chpasswdlen) {
+ error_setg_errno(errp, errno, "cannot write new account password");
+ goto out;
+ }
+ close(datafd[1]);
+ datafd[1] = -1;
+
+ ga_wait_child(pid, &status, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ goto out;
+ }
+
+ if (!WIFEXITED(status)) {
+ error_setg(errp, "child process has terminated abnormally");
+ goto out;
+ }
+
+ if (WEXITSTATUS(status)) {
+ error_setg(errp, "child process has failed to set user password");
+ goto out;
+ }
+
+out:
+ g_free(chpasswddata);
+ g_free(rawpasswddata);
+ g_free(passwd_path);
+ if (datafd[0] != -1) {
+ close(datafd[0]);
+ }
+ if (datafd[1] != -1) {
+ close(datafd[1]);
+ }
+}
+
+static void ga_read_sysfs_file(int dirfd, const char *pathname, char *buf,
+ int size, Error **errp)
+{
+ int fd;
+ int res;
+
+ errno = 0;
+ fd = openat(dirfd, pathname, O_RDONLY);
+ if (fd == -1) {
+ error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname);
+ return;
+ }
+
+ res = pread(fd, buf, size, 0);
+ if (res == -1) {
+ error_setg_errno(errp, errno, "pread sysfs file \"%s\"", pathname);
+ } else if (res == 0) {
+ error_setg(errp, "pread sysfs file \"%s\": unexpected EOF", pathname);
+ }
+ close(fd);
+}
+
+static void ga_write_sysfs_file(int dirfd, const char *pathname,
+ const char *buf, int size, Error **errp)
+{
+ int fd;
+
+ errno = 0;
+ fd = openat(dirfd, pathname, O_WRONLY);
+ if (fd == -1) {
+ error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname);
+ return;
+ }
+
+ if (pwrite(fd, buf, size, 0) == -1) {
+ error_setg_errno(errp, errno, "pwrite sysfs file \"%s\"", pathname);
+ }
+
+ close(fd);
+}
+
+/* Transfer online/offline status between @mem_blk and the guest system.
+ *
+ * On input either @errp or *@errp must be NULL.
+ *
+ * In system-to-@mem_blk direction, the following @mem_blk fields are accessed:
+ * - R: mem_blk->phys_index
+ * - W: mem_blk->online
+ * - W: mem_blk->can_offline
+ *
+ * In @mem_blk-to-system direction, the following @mem_blk fields are accessed:
+ * - R: mem_blk->phys_index
+ * - R: mem_blk->online
+ *- R: mem_blk->can_offline
+ * Written members remain unmodified on error.
+ */
+static void transfer_memory_block(GuestMemoryBlock *mem_blk, bool sys2memblk,
+ GuestMemoryBlockResponse *result,
+ Error **errp)
+{
+ char *dirpath;
+ int dirfd;
+ char *status;
+ Error *local_err = NULL;
+
+ if (!sys2memblk) {
+ DIR *dp;
+
+ if (!result) {
+ error_setg(errp, "Internal error, 'result' should not be NULL");
+ return;
+ }
+ errno = 0;
+ dp = opendir("/sys/devices/system/memory/");
+ /* if there is no 'memory' directory in sysfs,
+ * we think this VM does not support online/offline memory block,
+ * any other solution?
+ */
+ if (!dp) {
+ if (errno == ENOENT) {
+ result->response =
+ GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
+ }
+ goto out1;
+ }
+ closedir(dp);
+ }
+
+ dirpath = g_strdup_printf("/sys/devices/system/memory/memory%" PRId64 "/",
+ mem_blk->phys_index);
+ dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
+ if (dirfd == -1) {
+ if (sys2memblk) {
+ error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
+ } else {
+ if (errno == ENOENT) {
+ result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_NOT_FOUND;
+ } else {
+ result->response =
+ GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
+ }
+ }
+ g_free(dirpath);
+ goto out1;
+ }
+ g_free(dirpath);
+
+ status = g_malloc0(10);
+ ga_read_sysfs_file(dirfd, "state", status, 10, &local_err);
+ if (local_err) {
+ /* treat with sysfs file that not exist in old kernel */
+ if (errno == ENOENT) {
+ error_free(local_err);
+ if (sys2memblk) {
+ mem_blk->online = true;
+ mem_blk->can_offline = false;
+ } else if (!mem_blk->online) {
+ result->response =
+ GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
+ }
+ } else {
+ if (sys2memblk) {
+ error_propagate(errp, local_err);
+ } else {
+ error_free(local_err);
+ result->response =
+ GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
+ }
+ }
+ goto out2;
+ }
+
+ if (sys2memblk) {
+ char removable = '0';
+
+ mem_blk->online = (strncmp(status, "online", 6) == 0);
+
+ ga_read_sysfs_file(dirfd, "removable", &removable, 1, &local_err);
+ if (local_err) {
+ /* if no 'removable' file, it doesn't support offline mem blk */
+ if (errno == ENOENT) {
+ error_free(local_err);
+ mem_blk->can_offline = false;
+ } else {
+ error_propagate(errp, local_err);
+ }
+ } else {
+ mem_blk->can_offline = (removable != '0');
+ }
+ } else {
+ if (mem_blk->online != (strncmp(status, "online", 6) == 0)) {
+ const char *new_state = mem_blk->online ? "online" : "offline";
+
+ ga_write_sysfs_file(dirfd, "state", new_state, strlen(new_state),
+ &local_err);
+ if (local_err) {
+ error_free(local_err);
+ result->response =
+ GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
+ goto out2;
+ }
+
+ result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_SUCCESS;
+ result->has_error_code = false;
+ } /* otherwise pretend successful re-(on|off)-lining */
+ }
+ g_free(status);
+ close(dirfd);
+ return;
+
+out2:
+ g_free(status);
+ close(dirfd);
+out1:
+ if (!sys2memblk) {
+ result->has_error_code = true;
+ result->error_code = errno;
+ }
+}
+
+GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
+{
+ GuestMemoryBlockList *head, **tail;
+ Error *local_err = NULL;
+ struct dirent *de;
+ DIR *dp;
+
+ head = NULL;
+ tail = &head;
+
+ dp = opendir("/sys/devices/system/memory/");
+ if (!dp) {
+ /* it's ok if this happens to be a system that doesn't expose
+ * memory blocks via sysfs, but otherwise we should report
+ * an error
+ */
+ if (errno != ENOENT) {
+ error_setg_errno(errp, errno, "Can't open directory"
+ "\"/sys/devices/system/memory/\"");
+ }
+ return NULL;
+ }
+
+ /* Note: the phys_index of memory block may be discontinuous,
+ * this is because a memblk is the unit of the Sparse Memory design, which
+ * allows discontinuous memory ranges (ex. NUMA), so here we should
+ * traverse the memory block directory.
+ */
+ while ((de = readdir(dp)) != NULL) {
+ GuestMemoryBlock *mem_blk;
+
+ if ((strncmp(de->d_name, "memory", 6) != 0) ||
+ !(de->d_type & DT_DIR)) {
+ continue;
+ }
+
+ mem_blk = g_malloc0(sizeof *mem_blk);
+ /* The d_name is "memoryXXX", phys_index is block id, same as XXX */
+ mem_blk->phys_index = strtoul(&de->d_name[6], NULL, 10);
+ mem_blk->has_can_offline = true; /* lolspeak ftw */
+ transfer_memory_block(mem_blk, true, NULL, &local_err);
+ if (local_err) {
+ break;
+ }
+
+ QAPI_LIST_APPEND(tail, mem_blk);
+ }
+
+ closedir(dp);
+ if (local_err == NULL) {
+ /* there's no guest with zero memory blocks */
+ if (head == NULL) {
+ error_setg(errp, "guest reported zero memory blocks!");
+ }
+ return head;
+ }
+
+ qapi_free_GuestMemoryBlockList(head);
+ error_propagate(errp, local_err);
+ return NULL;
+}
+
+GuestMemoryBlockResponseList *
+qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp)
+{
+ GuestMemoryBlockResponseList *head, **tail;
+ Error *local_err = NULL;
+
+ head = NULL;
+ tail = &head;
+
+ while (mem_blks != NULL) {
+ GuestMemoryBlockResponse *result;
+ GuestMemoryBlock *current_mem_blk = mem_blks->value;
+
+ result = g_malloc0(sizeof(*result));
+ result->phys_index = current_mem_blk->phys_index;
+ transfer_memory_block(current_mem_blk, false, result, &local_err);
+ if (local_err) { /* should never happen */
+ goto err;
+ }
+
+ QAPI_LIST_APPEND(tail, result);
+ mem_blks = mem_blks->next;
+ }
+
+ return head;
+err:
+ qapi_free_GuestMemoryBlockResponseList(head);
+ error_propagate(errp, local_err);
+ return NULL;
+}
+
+GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
+{
+ Error *local_err = NULL;
+ char *dirpath;
+ int dirfd;
+ char *buf;
+ GuestMemoryBlockInfo *info;
+
+ dirpath = g_strdup_printf("/sys/devices/system/memory/");
+ dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
+ if (dirfd == -1) {
+ error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
+ g_free(dirpath);
+ return NULL;
+ }
+ g_free(dirpath);
+
+ buf = g_malloc0(20);
+ ga_read_sysfs_file(dirfd, "block_size_bytes", buf, 20, &local_err);
+ close(dirfd);
+ if (local_err) {
+ g_free(buf);
+ error_propagate(errp, local_err);
+ return NULL;
+ }
+
+ info = g_new0(GuestMemoryBlockInfo, 1);
+ info->size = strtol(buf, NULL, 16); /* the unit is bytes */
+
+ g_free(buf);
+
+ return info;
+}
+
+#define MAX_NAME_LEN 128
+static GuestDiskStatsInfoList *guest_get_diskstats(Error **errp)
+{
+#ifdef CONFIG_LINUX
+ GuestDiskStatsInfoList *head = NULL, **tail = &head;
+ const char *diskstats = "/proc/diskstats";
+ FILE *fp;
+ size_t n;
+ char *line = NULL;
+
+ fp = fopen(diskstats, "r");
+ if (fp == NULL) {
+ error_setg_errno(errp, errno, "open(\"%s\")", diskstats);
+ return NULL;
+ }
+
+ while (getline(&line, &n, fp) != -1) {
+ g_autofree GuestDiskStatsInfo *diskstatinfo = NULL;
+ g_autofree GuestDiskStats *diskstat = NULL;
+ char dev_name[MAX_NAME_LEN];
+ unsigned int ios_pgr, tot_ticks, rq_ticks, wr_ticks, dc_ticks, fl_ticks;
+ unsigned long rd_ios, rd_merges_or_rd_sec, rd_ticks_or_wr_sec, wr_ios;
+ unsigned long wr_merges, rd_sec_or_wr_ios, wr_sec;
+ unsigned long dc_ios, dc_merges, dc_sec, fl_ios;
+ unsigned int major, minor;
+ int i;
+
+ i = sscanf(line, "%u %u %s %lu %lu %lu"
+ "%lu %lu %lu %lu %u %u %u %u"
+ "%lu %lu %lu %u %lu %u",
+ &major, &minor, dev_name,
+ &rd_ios, &rd_merges_or_rd_sec, &rd_sec_or_wr_ios,
+ &rd_ticks_or_wr_sec, &wr_ios, &wr_merges, &wr_sec,
+ &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks,
+ &dc_ios, &dc_merges, &dc_sec, &dc_ticks,
+ &fl_ios, &fl_ticks);
+
+ if (i < 7) {
+ continue;
+ }
+
+ diskstatinfo = g_new0(GuestDiskStatsInfo, 1);
+ diskstatinfo->name = g_strdup(dev_name);
+ diskstatinfo->major = major;
+ diskstatinfo->minor = minor;
+
+ diskstat = g_new0(GuestDiskStats, 1);
+ if (i == 7) {
+ diskstat->has_read_ios = true;
+ diskstat->read_ios = rd_ios;
+ diskstat->has_read_sectors = true;
+ diskstat->read_sectors = rd_merges_or_rd_sec;
+ diskstat->has_write_ios = true;
+ diskstat->write_ios = rd_sec_or_wr_ios;
+ diskstat->has_write_sectors = true;
+ diskstat->write_sectors = rd_ticks_or_wr_sec;
+ }
+ if (i >= 14) {
+ diskstat->has_read_ios = true;
+ diskstat->read_ios = rd_ios;
+ diskstat->has_read_sectors = true;
+ diskstat->read_sectors = rd_sec_or_wr_ios;
+ diskstat->has_read_merges = true;
+ diskstat->read_merges = rd_merges_or_rd_sec;
+ diskstat->has_read_ticks = true;
+ diskstat->read_ticks = rd_ticks_or_wr_sec;
+ diskstat->has_write_ios = true;
+ diskstat->write_ios = wr_ios;
+ diskstat->has_write_sectors = true;
+ diskstat->write_sectors = wr_sec;
+ diskstat->has_write_merges = true;
+ diskstat->write_merges = wr_merges;
+ diskstat->has_write_ticks = true;
+ diskstat->write_ticks = wr_ticks;
+ diskstat->has_ios_pgr = true;
+ diskstat->ios_pgr = ios_pgr;
+ diskstat->has_total_ticks = true;
+ diskstat->total_ticks = tot_ticks;
+ diskstat->has_weight_ticks = true;
+ diskstat->weight_ticks = rq_ticks;
+ }
+ if (i >= 18) {
+ diskstat->has_discard_ios = true;
+ diskstat->discard_ios = dc_ios;
+ diskstat->has_discard_merges = true;
+ diskstat->discard_merges = dc_merges;
+ diskstat->has_discard_sectors = true;
+ diskstat->discard_sectors = dc_sec;
+ diskstat->has_discard_ticks = true;
+ diskstat->discard_ticks = dc_ticks;
+ }
+ if (i >= 20) {
+ diskstat->has_flush_ios = true;
+ diskstat->flush_ios = fl_ios;
+ diskstat->has_flush_ticks = true;
+ diskstat->flush_ticks = fl_ticks;
+ }
+
+ diskstatinfo->stats = g_steal_pointer(&diskstat);
+ QAPI_LIST_APPEND(tail, diskstatinfo);
+ diskstatinfo = NULL;
+ }
+ free(line);
+ fclose(fp);
+ return head;
+#else
+ g_debug("disk stats reporting available only for Linux");
+ return NULL;
+#endif
+}
+
+GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp)
+{
+ return guest_get_diskstats(errp);
+}
+
+GuestCpuStatsList *qmp_guest_get_cpustats(Error **errp)
+{
+ GuestCpuStatsList *head = NULL, **tail = &head;
+ const char *cpustats = "/proc/stat";
+ int clk_tck = sysconf(_SC_CLK_TCK);
+ FILE *fp;
+ size_t n;
+ char *line = NULL;
+
+ fp = fopen(cpustats, "r");
+ if (fp == NULL) {
+ error_setg_errno(errp, errno, "open(\"%s\")", cpustats);
+ return NULL;
+ }
+
+ while (getline(&line, &n, fp) != -1) {
+ GuestCpuStats *cpustat = NULL;
+ GuestLinuxCpuStats *linuxcpustat;
+ int i;
+ unsigned long user, system, idle, iowait, irq, softirq, steal, guest;
+ unsigned long nice, guest_nice;
+ char name[64];
+
+ i = sscanf(line, "%s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
+ name, &user, &nice, &system, &idle, &iowait, &irq, &softirq,
+ &steal, &guest, &guest_nice);
+
+ /* drop "cpu 1 2 3 ...", get "cpuX 1 2 3 ..." only */
+ if ((i == EOF) || strncmp(name, "cpu", 3) || (name[3] == '\0')) {
+ continue;
+ }
+
+ if (i < 5) {
+ slog("Parsing cpu stat from %s failed, see \"man proc\"", cpustats);
+ break;
+ }
+
+ cpustat = g_new0(GuestCpuStats, 1);
+ cpustat->type = GUEST_CPU_STATS_TYPE_LINUX;
+
+ linuxcpustat = &cpustat->u.q_linux;
+ linuxcpustat->cpu = atoi(&name[3]);
+ linuxcpustat->user = user * 1000 / clk_tck;
+ linuxcpustat->nice = nice * 1000 / clk_tck;
+ linuxcpustat->system = system * 1000 / clk_tck;
+ linuxcpustat->idle = idle * 1000 / clk_tck;
+
+ if (i > 5) {
+ linuxcpustat->has_iowait = true;
+ linuxcpustat->iowait = iowait * 1000 / clk_tck;
+ }
+
+ if (i > 6) {
+ linuxcpustat->has_irq = true;
+ linuxcpustat->irq = irq * 1000 / clk_tck;
+ linuxcpustat->has_softirq = true;
+ linuxcpustat->softirq = softirq * 1000 / clk_tck;
+ }
+
+ if (i > 8) {
+ linuxcpustat->has_steal = true;
+ linuxcpustat->steal = steal * 1000 / clk_tck;
+ }
+
+ if (i > 9) {
+ linuxcpustat->has_guest = true;
+ linuxcpustat->guest = guest * 1000 / clk_tck;
+ }
+
+ if (i > 10) {
+ linuxcpustat->has_guest = true;
+ linuxcpustat->guest = guest * 1000 / clk_tck;
+ linuxcpustat->has_guestnice = true;
+ linuxcpustat->guestnice = guest_nice * 1000 / clk_tck;
+ }
+
+ QAPI_LIST_APPEND(tail, cpustat);
+ }
+
+ free(line);
+ fclose(fp);
+ return head;
+}
diff --git a/qga/commands-posix.c b/qga/commands-posix.c
index 954efed01b..0bb8b9e2f3 100644
--- a/qga/commands-posix.c
+++ b/qga/commands-posix.c
@@ -15,7 +15,6 @@
#include <sys/ioctl.h>
#include <sys/utsname.h>
#include <sys/wait.h>
-#include <dirent.h>
#include "guest-agent-core.h"
#include "qga-qapi-commands.h"
#include "qapi/error.h"
@@ -26,31 +25,12 @@
#include "qemu/base64.h"
#include "qemu/cutils.h"
#include "commands-common.h"
-#include "block/nvme.h"
#include "cutils.h"
#ifdef HAVE_UTMPX
#include <utmpx.h>
#endif
-#if defined(__linux__)
-#include <mntent.h>
-#include <linux/fs.h>
-#include <sys/statvfs.h>
-#include <linux/nvme_ioctl.h>
-
-#ifdef CONFIG_LIBUDEV
-#include <libudev.h>
-#endif
-
-#ifdef FIFREEZE
-#define CONFIG_FSFREEZE
-#endif
-#ifdef FITRIM
-#define CONFIG_FSTRIM
-#endif
-#endif
-
#ifdef HAVE_GETIFADDRS
#include <arpa/inet.h>
#include <sys/socket.h>
@@ -62,7 +42,173 @@
#endif
#endif
-static void ga_wait_child(pid_t pid, int *status, Error **errp)
+#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
+void free_fs_mount_list(FsMountList *mounts)
+{
+ FsMount *mount, *temp;
+
+ if (!mounts) {
+ return;
+ }
+
+ QTAILQ_FOREACH_SAFE(mount, mounts, next, temp) {
+ QTAILQ_REMOVE(mounts, mount, next);
+ g_free(mount->dirname);
+ g_free(mount->devtype);
+ g_free(mount);
+ }
+}
+#endif
+
+#if defined(CONFIG_FSFREEZE)
+typedef enum {
+ FSFREEZE_HOOK_THAW = 0,
+ FSFREEZE_HOOK_FREEZE,
+} FsfreezeHookArg;
+
+static const char *fsfreeze_hook_arg_string[] = {
+ "thaw",
+ "freeze",
+};
+
+static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **errp)
+{
+ 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(errp, 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);
+
+ execl(hook, hook, arg_str, NULL);
+ _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 (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ if (!WIFEXITED(status)) {
+ error_setg(errp, "fsfreeze hook has terminated abnormally");
+ return;
+ }
+
+ status = WEXITSTATUS(status);
+ if (status) {
+ error_setg(errp, "fsfreeze hook has failed with status %d", status);
+ return;
+ }
+}
+
+int64_t qmp_guest_fsfreeze_thaw(Error **errp)
+{
+ int ret;
+
+ ret = qmp_guest_fsfreeze_do_thaw(errp);
+ if (ret >= 0) {
+ ga_unset_frozen(ga_state);
+ execute_fsfreeze_hook(FSFREEZE_HOOK_THAW, errp);
+ } else {
+ ret = 0;
+ }
+
+ return ret;
+}
+
+int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints,
+ strList *mountpoints,
+ Error **errp)
+{
+ int ret;
+ FsMountList mounts;
+ Error *local_err = NULL;
+
+ slog("guest-fsfreeze called");
+
+ execute_fsfreeze_hook(FSFREEZE_HOOK_FREEZE, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return -1;
+ }
+
+ QTAILQ_INIT(&mounts);
+ if (!build_fs_mount_list(&mounts, &local_err)) {
+ error_propagate(errp, local_err);
+ return -1;
+ }
+
+ /* cannot risk guest agent blocking itself on a write in this state */
+ ga_set_frozen(ga_state);
+
+ ret = qmp_guest_fsfreeze_do_freeze_list(has_mountpoints, mountpoints,
+ mounts, errp);
+
+ free_fs_mount_list(&mounts);
+ /* We may not issue any FIFREEZE here.
+ * Just unset ga_state here and ready for the next call.
+ */
+ if (ret == 0) {
+ ga_unset_frozen(ga_state);
+ } else if (ret < 0) {
+ qmp_guest_fsfreeze_thaw(NULL);
+ }
+ return ret;
+}
+
+/*
+ * Return status of freeze/thaw
+ */
+GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
+{
+ if (ga_is_frozen(ga_state)) {
+ return GUEST_FSFREEZE_STATUS_FROZEN;
+ }
+
+ return GUEST_FSFREEZE_STATUS_THAWED;
+}
+
+int64_t qmp_guest_fsfreeze_freeze(Error **errp)
+{
+ return qmp_guest_fsfreeze_freeze_list(false, NULL, errp);
+}
+
+static void guest_fsfreeze_cleanup(void)
+{
+ Error *err = NULL;
+
+ if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
+ qmp_guest_fsfreeze_thaw(&err);
+ if (err) {
+ slog("failed to clean up frozen filesystems: %s",
+ error_get_pretty(err));
+ error_free(err);
+ }
+ }
+}
+#endif /* CONFIG_FSFREEZE */
+
+void ga_wait_child(pid_t pid, int *status, Error **errp)
{
pid_t rpid;
@@ -617,2375 +763,7 @@ void qmp_guest_file_flush(int64_t handle, Error **errp)
}
}
-/* linux-specific implementations. avoid this if at all possible. */
-#if defined(__linux__)
-
-#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
-typedef struct FsMount {
- char *dirname;
- char *devtype;
- unsigned int devmajor, devminor;
- QTAILQ_ENTRY(FsMount) next;
-} FsMount;
-
-typedef QTAILQ_HEAD(FsMountList, FsMount) FsMountList;
-
-static void free_fs_mount_list(FsMountList *mounts)
-{
- FsMount *mount, *temp;
-
- if (!mounts) {
- return;
- }
-
- QTAILQ_FOREACH_SAFE(mount, mounts, next, temp) {
- QTAILQ_REMOVE(mounts, mount, next);
- g_free(mount->dirname);
- g_free(mount->devtype);
- g_free(mount);
- }
-}
-
-static int dev_major_minor(const char *devpath,
- unsigned int *devmajor, unsigned int *devminor)
-{
- struct stat st;
-
- *devmajor = 0;
- *devminor = 0;
-
- if (stat(devpath, &st) < 0) {
- slog("failed to stat device file '%s': %s", devpath, strerror(errno));
- return -1;
- }
- if (S_ISDIR(st.st_mode)) {
- /* It is bind mount */
- return -2;
- }
- if (S_ISBLK(st.st_mode)) {
- *devmajor = major(st.st_rdev);
- *devminor = minor(st.st_rdev);
- return 0;
- }
- return -1;
-}
-
-/*
- * Walk the mount table and build a list of local file systems
- */
-static bool build_fs_mount_list_from_mtab(FsMountList *mounts, Error **errp)
-{
- struct mntent *ment;
- FsMount *mount;
- char const *mtab = "/proc/self/mounts";
- FILE *fp;
- unsigned int devmajor, devminor;
-
- fp = setmntent(mtab, "r");
- if (!fp) {
- error_setg(errp, "failed to open mtab file: '%s'", mtab);
- return false;
- }
-
- while ((ment = getmntent(fp))) {
- /*
- * An entry which device name doesn't start with a '/' is
- * either a dummy file system or a network file system.
- * Add special handling for smbfs and cifs as is done by
- * coreutils as well.
- */
- if ((ment->mnt_fsname[0] != '/') ||
- (strcmp(ment->mnt_type, "smbfs") == 0) ||
- (strcmp(ment->mnt_type, "cifs") == 0)) {
- continue;
- }
- if (dev_major_minor(ment->mnt_fsname, &devmajor, &devminor) == -2) {
- /* Skip bind mounts */
- continue;
- }
-
- mount = g_new0(FsMount, 1);
- mount->dirname = g_strdup(ment->mnt_dir);
- mount->devtype = g_strdup(ment->mnt_type);
- mount->devmajor = devmajor;
- mount->devminor = devminor;
-
- QTAILQ_INSERT_TAIL(mounts, mount, next);
- }
-
- endmntent(fp);
- return true;
-}
-
-static void decode_mntname(char *name, int len)
-{
- int i, j = 0;
- for (i = 0; i <= len; i++) {
- if (name[i] != '\\') {
- name[j++] = name[i];
- } else if (name[i + 1] == '\\') {
- name[j++] = '\\';
- i++;
- } else if (name[i + 1] >= '0' && name[i + 1] <= '3' &&
- name[i + 2] >= '0' && name[i + 2] <= '7' &&
- name[i + 3] >= '0' && name[i + 3] <= '7') {
- name[j++] = (name[i + 1] - '0') * 64 +
- (name[i + 2] - '0') * 8 +
- (name[i + 3] - '0');
- i += 3;
- } else {
- name[j++] = name[i];
- }
- }
-}
-
-static bool build_fs_mount_list(FsMountList *mounts, Error **errp)
-{
- FsMount *mount;
- char const *mountinfo = "/proc/self/mountinfo";
- FILE *fp;
- char *line = NULL, *dash;
- size_t n;
- char check;
- unsigned int devmajor, devminor;
- int ret, dir_s, dir_e, type_s, type_e, dev_s, dev_e;
-
- fp = fopen(mountinfo, "r");
- if (!fp) {
- return build_fs_mount_list_from_mtab(mounts, errp);
- }
-
- while (getline(&line, &n, fp) != -1) {
- ret = sscanf(line, "%*u %*u %u:%u %*s %n%*s%n%c",
- &devmajor, &devminor, &dir_s, &dir_e, &check);
- if (ret < 3) {
- continue;
- }
- dash = strstr(line + dir_e, " - ");
- if (!dash) {
- continue;
- }
- ret = sscanf(dash, " - %n%*s%n %n%*s%n%c",
- &type_s, &type_e, &dev_s, &dev_e, &check);
- if (ret < 1) {
- continue;
- }
- line[dir_e] = 0;
- dash[type_e] = 0;
- dash[dev_e] = 0;
- decode_mntname(line + dir_s, dir_e - dir_s);
- decode_mntname(dash + dev_s, dev_e - dev_s);
- if (devmajor == 0) {
- /* btrfs reports major number = 0 */
- if (strcmp("btrfs", dash + type_s) != 0 ||
- dev_major_minor(dash + dev_s, &devmajor, &devminor) < 0) {
- continue;
- }
- }
-
- mount = g_new0(FsMount, 1);
- mount->dirname = g_strdup(line + dir_s);
- mount->devtype = g_strdup(dash + type_s);
- mount->devmajor = devmajor;
- mount->devminor = devminor;
-
- QTAILQ_INSERT_TAIL(mounts, mount, next);
- }
- free(line);
-
- fclose(fp);
- return true;
-}
-#endif
-
-#if defined(CONFIG_FSFREEZE)
-
-static char *get_pci_driver(char const *syspath, int pathlen, Error **errp)
-{
- char *path;
- char *dpath;
- char *driver = NULL;
- char buf[PATH_MAX];
- ssize_t len;
-
- path = g_strndup(syspath, pathlen);
- dpath = g_strdup_printf("%s/driver", path);
- len = readlink(dpath, buf, sizeof(buf) - 1);
- if (len != -1) {
- buf[len] = 0;
- driver = g_path_get_basename(buf);
- }
- g_free(dpath);
- g_free(path);
- return driver;
-}
-
-static int compare_uint(const void *_a, const void *_b)
-{
- unsigned int a = *(unsigned int *)_a;
- unsigned int b = *(unsigned int *)_b;
-
- return a < b ? -1 : a > b ? 1 : 0;
-}
-
-/* Walk the specified sysfs and build a sorted list of host or ata numbers */
-static int build_hosts(char const *syspath, char const *host, bool ata,
- unsigned int *hosts, int hosts_max, Error **errp)
-{
- char *path;
- DIR *dir;
- struct dirent *entry;
- int i = 0;
-
- path = g_strndup(syspath, host - syspath);
- dir = opendir(path);
- if (!dir) {
- error_setg_errno(errp, errno, "opendir(\"%s\")", path);
- g_free(path);
- return -1;
- }
-
- while (i < hosts_max) {
- entry = readdir(dir);
- if (!entry) {
- break;
- }
- if (ata && sscanf(entry->d_name, "ata%d", hosts + i) == 1) {
- ++i;
- } else if (!ata && sscanf(entry->d_name, "host%d", hosts + i) == 1) {
- ++i;
- }
- }
-
- qsort(hosts, i, sizeof(hosts[0]), compare_uint);
-
- g_free(path);
- closedir(dir);
- return i;
-}
-
-/*
- * Store disk device info for devices on the PCI bus.
- * Returns true if information has been stored, or false for failure.
- */
-static bool build_guest_fsinfo_for_pci_dev(char const *syspath,
- GuestDiskAddress *disk,
- Error **errp)
-{
- unsigned int pci[4], host, hosts[8], tgt[3];
- int i, nhosts = 0, pcilen;
- GuestPCIAddress *pciaddr = disk->pci_controller;
- bool has_ata = false, has_host = false, has_tgt = false;
- char *p, *q, *driver = NULL;
- bool ret = false;
-
- p = strstr(syspath, "/devices/pci");
- if (!p || sscanf(p + 12, "%*x:%*x/%x:%x:%x.%x%n",
- pci, pci + 1, pci + 2, pci + 3, &pcilen) < 4) {
- g_debug("only pci device is supported: sysfs path '%s'", syspath);
- return false;
- }
-
- p += 12 + pcilen;
- while (true) {
- driver = get_pci_driver(syspath, p - syspath, errp);
- if (driver && (g_str_equal(driver, "ata_piix") ||
- g_str_equal(driver, "sym53c8xx") ||
- g_str_equal(driver, "virtio-pci") ||
- g_str_equal(driver, "ahci") ||
- g_str_equal(driver, "nvme"))) {
- break;
- }
-
- g_free(driver);
- if (sscanf(p, "/%x:%x:%x.%x%n",
- pci, pci + 1, pci + 2, pci + 3, &pcilen) == 4) {
- p += pcilen;
- continue;
- }
-
- g_debug("unsupported driver or sysfs path '%s'", syspath);
- return false;
- }
-
- p = strstr(syspath, "/target");
- if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u",
- tgt, tgt + 1, tgt + 2) == 3) {
- has_tgt = true;
- }
-
- p = strstr(syspath, "/ata");
- if (p) {
- q = p + 4;
- has_ata = true;
- } else {
- p = strstr(syspath, "/host");
- q = p + 5;
- }
- if (p && sscanf(q, "%u", &host) == 1) {
- has_host = true;
- nhosts = build_hosts(syspath, p, has_ata, hosts,
- ARRAY_SIZE(hosts), errp);
- if (nhosts < 0) {
- goto cleanup;
- }
- }
-
- pciaddr->domain = pci[0];
- pciaddr->bus = pci[1];
- pciaddr->slot = pci[2];
- pciaddr->function = pci[3];
-
- if (strcmp(driver, "ata_piix") == 0) {
- /* a host per ide bus, target*:0:<unit>:0 */
- if (!has_host || !has_tgt) {
- g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver);
- goto cleanup;
- }
- for (i = 0; i < nhosts; i++) {
- if (host == hosts[i]) {
- disk->bus_type = GUEST_DISK_BUS_TYPE_IDE;
- disk->bus = i;
- disk->unit = tgt[1];
- break;
- }
- }
- if (i >= nhosts) {
- g_debug("no host for '%s' (driver '%s')", syspath, driver);
- goto cleanup;
- }
- } else if (strcmp(driver, "sym53c8xx") == 0) {
- /* scsi(LSI Logic): target*:0:<unit>:0 */
- if (!has_tgt) {
- g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver);
- goto cleanup;
- }
- disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
- disk->unit = tgt[1];
- } else if (strcmp(driver, "virtio-pci") == 0) {
- if (has_tgt) {
- /* virtio-scsi: target*:0:0:<unit> */
- disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
- disk->unit = tgt[2];
- } else {
- /* virtio-blk: 1 disk per 1 device */
- disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
- }
- } else if (strcmp(driver, "ahci") == 0) {
- /* ahci: 1 host per 1 unit */
- if (!has_host || !has_tgt) {
- g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver);
- goto cleanup;
- }
- for (i = 0; i < nhosts; i++) {
- if (host == hosts[i]) {
- disk->unit = i;
- disk->bus_type = GUEST_DISK_BUS_TYPE_SATA;
- break;
- }
- }
- if (i >= nhosts) {
- g_debug("no host for '%s' (driver '%s')", syspath, driver);
- goto cleanup;
- }
- } else if (strcmp(driver, "nvme") == 0) {
- disk->bus_type = GUEST_DISK_BUS_TYPE_NVME;
- } else {
- g_debug("unknown driver '%s' (sysfs path '%s')", driver, syspath);
- goto cleanup;
- }
-
- ret = true;
-
-cleanup:
- g_free(driver);
- return ret;
-}
-
-/*
- * Store disk device info for non-PCI virtio devices (for example s390x
- * channel I/O devices). Returns true if information has been stored, or
- * false for failure.
- */
-static bool build_guest_fsinfo_for_nonpci_virtio(char const *syspath,
- GuestDiskAddress *disk,
- Error **errp)
-{
- unsigned int tgt[3];
- char *p;
-
- if (!strstr(syspath, "/virtio") || !strstr(syspath, "/block")) {
- g_debug("Unsupported virtio device '%s'", syspath);
- return false;
- }
-
- p = strstr(syspath, "/target");
- if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u",
- &tgt[0], &tgt[1], &tgt[2]) == 3) {
- /* virtio-scsi: target*:0:<target>:<unit> */
- disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
- disk->bus = tgt[0];
- disk->target = tgt[1];
- disk->unit = tgt[2];
- } else {
- /* virtio-blk: 1 disk per 1 device */
- disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
- }
-
- return true;
-}
-
-/*
- * Store disk device info for CCW devices (s390x channel I/O devices).
- * Returns true if information has been stored, or false for failure.
- */
-static bool build_guest_fsinfo_for_ccw_dev(char const *syspath,
- GuestDiskAddress *disk,
- Error **errp)
-{
- unsigned int cssid, ssid, subchno, devno;
- char *p;
-
- p = strstr(syspath, "/devices/css");
- if (!p || sscanf(p + 12, "%*x/%x.%x.%x/%*x.%*x.%x/",
- &cssid, &ssid, &subchno, &devno) < 4) {
- g_debug("could not parse ccw device sysfs path: %s", syspath);
- return false;
- }
-
- disk->has_ccw_address = true;
- disk->ccw_address = g_new0(GuestCCWAddress, 1);
- disk->ccw_address->cssid = cssid;
- disk->ccw_address->ssid = ssid;
- disk->ccw_address->subchno = subchno;
- disk->ccw_address->devno = devno;
-
- if (strstr(p, "/virtio")) {
- build_guest_fsinfo_for_nonpci_virtio(syspath, disk, errp);
- }
-
- return true;
-}
-
-/* Store disk device info specified by @sysfs into @fs */
-static void build_guest_fsinfo_for_real_device(char const *syspath,
- GuestFilesystemInfo *fs,
- Error **errp)
-{
- GuestDiskAddress *disk;
- GuestPCIAddress *pciaddr;
- bool has_hwinf;
-#ifdef CONFIG_LIBUDEV
- struct udev *udev = NULL;
- struct udev_device *udevice = NULL;
-#endif
-
- pciaddr = g_new0(GuestPCIAddress, 1);
- pciaddr->domain = -1; /* -1 means field is invalid */
- pciaddr->bus = -1;
- pciaddr->slot = -1;
- pciaddr->function = -1;
-
- disk = g_new0(GuestDiskAddress, 1);
- disk->pci_controller = pciaddr;
- disk->bus_type = GUEST_DISK_BUS_TYPE_UNKNOWN;
-
-#ifdef CONFIG_LIBUDEV
- udev = udev_new();
- udevice = udev_device_new_from_syspath(udev, syspath);
- if (udev == NULL || udevice == NULL) {
- g_debug("failed to query udev");
- } else {
- const char *devnode, *serial;
- devnode = udev_device_get_devnode(udevice);
- if (devnode != NULL) {
- disk->dev = g_strdup(devnode);
- disk->has_dev = true;
- }
- serial = udev_device_get_property_value(udevice, "ID_SERIAL");
- if (serial != NULL && *serial != 0) {
- disk->serial = g_strdup(serial);
- disk->has_serial = true;
- }
- }
-
- udev_unref(udev);
- udev_device_unref(udevice);
-#endif
-
- if (strstr(syspath, "/devices/pci")) {
- has_hwinf = build_guest_fsinfo_for_pci_dev(syspath, disk, errp);
- } else if (strstr(syspath, "/devices/css")) {
- has_hwinf = build_guest_fsinfo_for_ccw_dev(syspath, disk, errp);
- } else if (strstr(syspath, "/virtio")) {
- has_hwinf = build_guest_fsinfo_for_nonpci_virtio(syspath, disk, errp);
- } else {
- g_debug("Unsupported device type for '%s'", syspath);
- has_hwinf = false;
- }
-
- if (has_hwinf || disk->has_dev || disk->has_serial) {
- QAPI_LIST_PREPEND(fs->disk, disk);
- } else {
- qapi_free_GuestDiskAddress(disk);
- }
-}
-
-static void build_guest_fsinfo_for_device(char const *devpath,
- GuestFilesystemInfo *fs,
- Error **errp);
-
-/* Store a list of slave devices of virtual volume specified by @syspath into
- * @fs */
-static void build_guest_fsinfo_for_virtual_device(char const *syspath,
- GuestFilesystemInfo *fs,
- Error **errp)
-{
- Error *err = NULL;
- DIR *dir;
- char *dirpath;
- struct dirent *entry;
-
- dirpath = g_strdup_printf("%s/slaves", syspath);
- dir = opendir(dirpath);
- if (!dir) {
- if (errno != ENOENT) {
- error_setg_errno(errp, errno, "opendir(\"%s\")", dirpath);
- }
- g_free(dirpath);
- return;
- }
-
- for (;;) {
- errno = 0;
- entry = readdir(dir);
- if (entry == NULL) {
- if (errno) {
- error_setg_errno(errp, errno, "readdir(\"%s\")", dirpath);
- }
- break;
- }
-
- if (entry->d_type == DT_LNK) {
- char *path;
-
- g_debug(" slave device '%s'", entry->d_name);
- path = g_strdup_printf("%s/slaves/%s", syspath, entry->d_name);
- build_guest_fsinfo_for_device(path, fs, &err);
- g_free(path);
-
- if (err) {
- error_propagate(errp, err);
- break;
- }
- }
- }
-
- g_free(dirpath);
- closedir(dir);
-}
-
-static bool is_disk_virtual(const char *devpath, Error **errp)
-{
- g_autofree char *syspath = realpath(devpath, NULL);
-
- if (!syspath) {
- error_setg_errno(errp, errno, "realpath(\"%s\")", devpath);
- return false;
- }
- return strstr(syspath, "/devices/virtual/block/") != NULL;
-}
-
-/* Dispatch to functions for virtual/real device */
-static void build_guest_fsinfo_for_device(char const *devpath,
- GuestFilesystemInfo *fs,
- Error **errp)
-{
- ERRP_GUARD();
- g_autofree char *syspath = NULL;
- bool is_virtual = false;
-
- syspath = realpath(devpath, NULL);
- if (!syspath) {
- if (errno != ENOENT) {
- error_setg_errno(errp, errno, "realpath(\"%s\")", devpath);
- return;
- }
-
- /* ENOENT: This devpath may not exist because of container config */
- if (!fs->name) {
- fs->name = g_path_get_basename(devpath);
- }
- return;
- }
-
- if (!fs->name) {
- fs->name = g_path_get_basename(syspath);
- }
-
- g_debug(" parse sysfs path '%s'", syspath);
- is_virtual = is_disk_virtual(syspath, errp);
- if (*errp != NULL) {
- return;
- }
- if (is_virtual) {
- build_guest_fsinfo_for_virtual_device(syspath, fs, errp);
- } else {
- build_guest_fsinfo_for_real_device(syspath, fs, errp);
- }
-}
-
-#ifdef CONFIG_LIBUDEV
-
-/*
- * Wrapper around build_guest_fsinfo_for_device() for getting just
- * the disk address.
- */
-static GuestDiskAddress *get_disk_address(const char *syspath, Error **errp)
-{
- g_autoptr(GuestFilesystemInfo) fs = NULL;
-
- fs = g_new0(GuestFilesystemInfo, 1);
- build_guest_fsinfo_for_device(syspath, fs, errp);
- if (fs->disk != NULL) {
- return g_steal_pointer(&fs->disk->value);
- }
- return NULL;
-}
-
-static char *get_alias_for_syspath(const char *syspath)
-{
- struct udev *udev = NULL;
- struct udev_device *udevice = NULL;
- char *ret = NULL;
-
- udev = udev_new();
- if (udev == NULL) {
- g_debug("failed to query udev");
- goto out;
- }
- udevice = udev_device_new_from_syspath(udev, syspath);
- if (udevice == NULL) {
- g_debug("failed to query udev for path: %s", syspath);
- goto out;
- } else {
- const char *alias = udev_device_get_property_value(
- udevice, "DM_NAME");
- /*
- * NULL means there was an error and empty string means there is no
- * alias. In case of no alias we return NULL instead of empty string.
- */
- if (alias == NULL) {
- g_debug("failed to query udev for device alias for: %s",
- syspath);
- } else if (*alias != 0) {
- ret = g_strdup(alias);
- }
- }
-
-out:
- udev_unref(udev);
- udev_device_unref(udevice);
- return ret;
-}
-
-static char *get_device_for_syspath(const char *syspath)
-{
- struct udev *udev = NULL;
- struct udev_device *udevice = NULL;
- char *ret = NULL;
-
- udev = udev_new();
- if (udev == NULL) {
- g_debug("failed to query udev");
- goto out;
- }
- udevice = udev_device_new_from_syspath(udev, syspath);
- if (udevice == NULL) {
- g_debug("failed to query udev for path: %s", syspath);
- goto out;
- } else {
- ret = g_strdup(udev_device_get_devnode(udevice));
- }
-
-out:
- udev_unref(udev);
- udev_device_unref(udevice);
- return ret;
-}
-
-static void get_disk_deps(const char *disk_dir, GuestDiskInfo *disk)
-{
- g_autofree char *deps_dir = NULL;
- const gchar *dep;
- GDir *dp_deps = NULL;
-
- /* List dependent disks */
- deps_dir = g_strdup_printf("%s/slaves", disk_dir);
- g_debug(" listing entries in: %s", deps_dir);
- dp_deps = g_dir_open(deps_dir, 0, NULL);
- if (dp_deps == NULL) {
- g_debug("failed to list entries in %s", deps_dir);
- return;
- }
- disk->has_dependencies = true;
- while ((dep = g_dir_read_name(dp_deps)) != NULL) {
- g_autofree char *dep_dir = NULL;
- char *dev_name;
-
- /* Add dependent disks */
- dep_dir = g_strdup_printf("%s/%s", deps_dir, dep);
- dev_name = get_device_for_syspath(dep_dir);
- if (dev_name != NULL) {
- g_debug(" adding dependent device: %s", dev_name);
- QAPI_LIST_PREPEND(disk->dependencies, dev_name);
- }
- }
- g_dir_close(dp_deps);
-}
-
-/*
- * Detect partitions subdirectory, name is "<disk_name><number>" or
- * "<disk_name>p<number>"
- *
- * @disk_name -- last component of /sys path (e.g. sda)
- * @disk_dir -- sys path of the disk (e.g. /sys/block/sda)
- * @disk_dev -- device node of the disk (e.g. /dev/sda)
- */
-static GuestDiskInfoList *get_disk_partitions(
- GuestDiskInfoList *list,
- const char *disk_name, const char *disk_dir,
- const char *disk_dev)
-{
- GuestDiskInfoList *ret = list;
- struct dirent *de_disk;
- DIR *dp_disk = NULL;
- size_t len = strlen(disk_name);
-
- dp_disk = opendir(disk_dir);
- while ((de_disk = readdir(dp_disk)) != NULL) {
- g_autofree char *partition_dir = NULL;
- char *dev_name;
- GuestDiskInfo *partition;
-
- if (!(de_disk->d_type & DT_DIR)) {
- continue;
- }
-
- if (!(strncmp(disk_name, de_disk->d_name, len) == 0 &&
- ((*(de_disk->d_name + len) == 'p' &&
- isdigit(*(de_disk->d_name + len + 1))) ||
- isdigit(*(de_disk->d_name + len))))) {
- continue;
- }
-
- partition_dir = g_strdup_printf("%s/%s",
- disk_dir, de_disk->d_name);
- dev_name = get_device_for_syspath(partition_dir);
- if (dev_name == NULL) {
- g_debug("Failed to get device name for syspath: %s",
- disk_dir);
- continue;
- }
- partition = g_new0(GuestDiskInfo, 1);
- partition->name = dev_name;
- partition->partition = true;
- partition->has_dependencies = true;
- /* Add parent disk as dependent for easier tracking of hierarchy */
- QAPI_LIST_PREPEND(partition->dependencies, g_strdup(disk_dev));
-
- QAPI_LIST_PREPEND(ret, partition);
- }
- closedir(dp_disk);
-
- return ret;
-}
-
-static void get_nvme_smart(GuestDiskInfo *disk)
-{
- int fd;
- GuestNVMeSmart *smart;
- NvmeSmartLog log = {0};
- struct nvme_admin_cmd cmd = {
- .opcode = NVME_ADM_CMD_GET_LOG_PAGE,
- .nsid = NVME_NSID_BROADCAST,
- .addr = (uintptr_t)&log,
- .data_len = sizeof(log),
- .cdw10 = NVME_LOG_SMART_INFO | (1 << 15) /* RAE bit */
- | (((sizeof(log) >> 2) - 1) << 16)
- };
-
- fd = qga_open_cloexec(disk->name, O_RDONLY, 0);
- if (fd == -1) {
- g_debug("Failed to open device: %s: %s", disk->name, g_strerror(errno));
- return;
- }
-
- if (ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd)) {
- g_debug("Failed to get smart: %s: %s", disk->name, g_strerror(errno));
- close(fd);
- return;
- }
-
- disk->has_smart = true;
- disk->smart = g_new0(GuestDiskSmart, 1);
- disk->smart->type = GUEST_DISK_BUS_TYPE_NVME;
-
- smart = &disk->smart->u.nvme;
- smart->critical_warning = log.critical_warning;
- smart->temperature = lduw_le_p(&log.temperature); /* unaligned field */
- smart->available_spare = log.available_spare;
- smart->available_spare_threshold = log.available_spare_threshold;
- smart->percentage_used = log.percentage_used;
- smart->data_units_read_lo = le64_to_cpu(log.data_units_read[0]);
- smart->data_units_read_hi = le64_to_cpu(log.data_units_read[1]);
- smart->data_units_written_lo = le64_to_cpu(log.data_units_written[0]);
- smart->data_units_written_hi = le64_to_cpu(log.data_units_written[1]);
- smart->host_read_commands_lo = le64_to_cpu(log.host_read_commands[0]);
- smart->host_read_commands_hi = le64_to_cpu(log.host_read_commands[1]);
- smart->host_write_commands_lo = le64_to_cpu(log.host_write_commands[0]);
- smart->host_write_commands_hi = le64_to_cpu(log.host_write_commands[1]);
- smart->controller_busy_time_lo = le64_to_cpu(log.controller_busy_time[0]);
- smart->controller_busy_time_hi = le64_to_cpu(log.controller_busy_time[1]);
- smart->power_cycles_lo = le64_to_cpu(log.power_cycles[0]);
- smart->power_cycles_hi = le64_to_cpu(log.power_cycles[1]);
- smart->power_on_hours_lo = le64_to_cpu(log.power_on_hours[0]);
- smart->power_on_hours_hi = le64_to_cpu(log.power_on_hours[1]);
- smart->unsafe_shutdowns_lo = le64_to_cpu(log.unsafe_shutdowns[0]);
- smart->unsafe_shutdowns_hi = le64_to_cpu(log.unsafe_shutdowns[1]);
- smart->media_errors_lo = le64_to_cpu(log.media_errors[0]);
- smart->media_errors_hi = le64_to_cpu(log.media_errors[1]);
- smart->number_of_error_log_entries_lo =
- le64_to_cpu(log.number_of_error_log_entries[0]);
- smart->number_of_error_log_entries_hi =
- le64_to_cpu(log.number_of_error_log_entries[1]);
-
- close(fd);
-}
-
-static void get_disk_smart(GuestDiskInfo *disk)
-{
- if (disk->has_address
- && (disk->address->bus_type == GUEST_DISK_BUS_TYPE_NVME)) {
- get_nvme_smart(disk);
- }
-}
-
-GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
-{
- GuestDiskInfoList *ret = NULL;
- GuestDiskInfo *disk;
- DIR *dp = NULL;
- struct dirent *de = NULL;
-
- g_debug("listing /sys/block directory");
- dp = opendir("/sys/block");
- if (dp == NULL) {
- error_setg_errno(errp, errno, "Can't open directory \"/sys/block\"");
- return NULL;
- }
- while ((de = readdir(dp)) != NULL) {
- g_autofree char *disk_dir = NULL, *line = NULL,
- *size_path = NULL;
- char *dev_name;
- Error *local_err = NULL;
- if (de->d_type != DT_LNK) {
- g_debug(" skipping entry: %s", de->d_name);
- continue;
- }
-
- /* Check size and skip zero-sized disks */
- g_debug(" checking disk size");
- size_path = g_strdup_printf("/sys/block/%s/size", de->d_name);
- if (!g_file_get_contents(size_path, &line, NULL, NULL)) {
- g_debug(" failed to read disk size");
- continue;
- }
- if (g_strcmp0(line, "0\n") == 0) {
- g_debug(" skipping zero-sized disk");
- continue;
- }
-
- g_debug(" adding %s", de->d_name);
- disk_dir = g_strdup_printf("/sys/block/%s", de->d_name);
- dev_name = get_device_for_syspath(disk_dir);
- if (dev_name == NULL) {
- g_debug("Failed to get device name for syspath: %s",
- disk_dir);
- continue;
- }
- disk = g_new0(GuestDiskInfo, 1);
- disk->name = dev_name;
- disk->partition = false;
- disk->alias = get_alias_for_syspath(disk_dir);
- disk->has_alias = (disk->alias != NULL);
- QAPI_LIST_PREPEND(ret, disk);
-
- /* Get address for non-virtual devices */
- bool is_virtual = is_disk_virtual(disk_dir, &local_err);
- if (local_err != NULL) {
- g_debug(" failed to check disk path, ignoring error: %s",
- error_get_pretty(local_err));
- error_free(local_err);
- local_err = NULL;
- /* Don't try to get the address */
- is_virtual = true;
- }
- if (!is_virtual) {
- disk->address = get_disk_address(disk_dir, &local_err);
- if (local_err != NULL) {
- g_debug(" failed to get device info, ignoring error: %s",
- error_get_pretty(local_err));
- error_free(local_err);
- local_err = NULL;
- } else if (disk->address != NULL) {
- disk->has_address = true;
- }
- }
-
- get_disk_deps(disk_dir, disk);
- get_disk_smart(disk);
- ret = get_disk_partitions(ret, de->d_name, disk_dir, dev_name);
- }
-
- closedir(dp);
-
- return ret;
-}
-
-#else
-
-GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
-{
- error_setg(errp, QERR_UNSUPPORTED);
- return NULL;
-}
-
-#endif
-
-/* Return a list of the disk device(s)' info which @mount lies on */
-static GuestFilesystemInfo *build_guest_fsinfo(struct FsMount *mount,
- Error **errp)
-{
- GuestFilesystemInfo *fs = g_malloc0(sizeof(*fs));
- struct statvfs buf;
- unsigned long used, nonroot_total, fr_size;
- char *devpath = g_strdup_printf("/sys/dev/block/%u:%u",
- mount->devmajor, mount->devminor);
-
- fs->mountpoint = g_strdup(mount->dirname);
- fs->type = g_strdup(mount->devtype);
- build_guest_fsinfo_for_device(devpath, fs, errp);
-
- if (statvfs(fs->mountpoint, &buf) == 0) {
- fr_size = buf.f_frsize;
- used = buf.f_blocks - buf.f_bfree;
- nonroot_total = used + buf.f_bavail;
- fs->used_bytes = used * fr_size;
- fs->total_bytes = nonroot_total * fr_size;
-
- fs->has_total_bytes = true;
- fs->has_used_bytes = true;
- }
-
- g_free(devpath);
-
- return fs;
-}
-
-GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
-{
- FsMountList mounts;
- struct FsMount *mount;
- GuestFilesystemInfoList *ret = NULL;
- Error *local_err = NULL;
-
- QTAILQ_INIT(&mounts);
- if (!build_fs_mount_list(&mounts, &local_err)) {
- error_propagate(errp, local_err);
- return NULL;
- }
-
- QTAILQ_FOREACH(mount, &mounts, next) {
- g_debug("Building guest fsinfo for '%s'", mount->dirname);
-
- QAPI_LIST_PREPEND(ret, build_guest_fsinfo(mount, &local_err));
- if (local_err) {
- error_propagate(errp, local_err);
- qapi_free_GuestFilesystemInfoList(ret);
- ret = NULL;
- break;
- }
- }
-
- free_fs_mount_list(&mounts);
- return ret;
-}
-
-
-typedef enum {
- FSFREEZE_HOOK_THAW = 0,
- FSFREEZE_HOOK_FREEZE,
-} FsfreezeHookArg;
-
-static const char *fsfreeze_hook_arg_string[] = {
- "thaw",
- "freeze",
-};
-
-static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **errp)
-{
- 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(errp, 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);
-
- execl(hook, hook, arg_str, NULL);
- _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 (local_err) {
- error_propagate(errp, local_err);
- return;
- }
-
- if (!WIFEXITED(status)) {
- error_setg(errp, "fsfreeze hook has terminated abnormally");
- return;
- }
-
- status = WEXITSTATUS(status);
- if (status) {
- error_setg(errp, "fsfreeze hook has failed with status %d", status);
- return;
- }
-}
-
-/*
- * Return status of freeze/thaw
- */
-GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
-{
- if (ga_is_frozen(ga_state)) {
- return GUEST_FSFREEZE_STATUS_FROZEN;
- }
-
- return GUEST_FSFREEZE_STATUS_THAWED;
-}
-
-int64_t qmp_guest_fsfreeze_freeze(Error **errp)
-{
- return qmp_guest_fsfreeze_freeze_list(false, NULL, errp);
-}
-
-/*
- * Walk list of mounted file systems in the guest, and freeze the ones which
- * are real local file systems.
- */
-int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints,
- strList *mountpoints,
- Error **errp)
-{
- int ret = 0, i = 0;
- strList *list;
- FsMountList mounts;
- struct FsMount *mount;
- Error *local_err = NULL;
- int fd;
-
- slog("guest-fsfreeze called");
-
- execute_fsfreeze_hook(FSFREEZE_HOOK_FREEZE, &local_err);
- if (local_err) {
- error_propagate(errp, local_err);
- return -1;
- }
-
- QTAILQ_INIT(&mounts);
- if (!build_fs_mount_list(&mounts, &local_err)) {
- error_propagate(errp, local_err);
- return -1;
- }
-
- /* cannot risk guest agent blocking itself on a write in this state */
- ga_set_frozen(ga_state);
-
- QTAILQ_FOREACH_REVERSE(mount, &mounts, next) {
- /* To issue fsfreeze in the reverse order of mounts, check if the
- * mount is listed in the list here */
- if (has_mountpoints) {
- for (list = mountpoints; list; list = list->next) {
- if (strcmp(list->value, mount->dirname) == 0) {
- break;
- }
- }
- if (!list) {
- continue;
- }
- }
-
- fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
- if (fd == -1) {
- error_setg_errno(errp, errno, "failed to open %s", mount->dirname);
- goto error;
- }
-
- /* we try to cull filesystems we know won't work in advance, but other
- * filesystems may not implement fsfreeze for less obvious reasons.
- * these will report EOPNOTSUPP. we simply ignore these when tallying
- * the number of frozen filesystems.
- * if a filesystem is mounted more than once (aka bind mount) a
- * consecutive attempt to freeze an already frozen filesystem will
- * return EBUSY.
- *
- * any other error means a failure to freeze a filesystem we
- * expect to be freezable, so return an error in those cases
- * and return system to thawed state.
- */
- ret = ioctl(fd, FIFREEZE);
- if (ret == -1) {
- if (errno != EOPNOTSUPP && errno != EBUSY) {
- error_setg_errno(errp, errno, "failed to freeze %s",
- mount->dirname);
- close(fd);
- goto error;
- }
- } else {
- i++;
- }
- close(fd);
- }
-
- free_fs_mount_list(&mounts);
- /* We may not issue any FIFREEZE here.
- * Just unset ga_state here and ready for the next call.
- */
- if (i == 0) {
- ga_unset_frozen(ga_state);
- }
- return i;
-
-error:
- free_fs_mount_list(&mounts);
- qmp_guest_fsfreeze_thaw(NULL);
- return 0;
-}
-
-/*
- * Walk list of frozen file systems in the guest, and thaw them.
- */
-int64_t qmp_guest_fsfreeze_thaw(Error **errp)
-{
- int ret;
- FsMountList mounts;
- FsMount *mount;
- int fd, i = 0, logged;
- Error *local_err = NULL;
-
- QTAILQ_INIT(&mounts);
- if (!build_fs_mount_list(&mounts, &local_err)) {
- error_propagate(errp, local_err);
- return 0;
- }
-
- QTAILQ_FOREACH(mount, &mounts, next) {
- logged = false;
- fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
- if (fd == -1) {
- continue;
- }
- /* we have no way of knowing whether a filesystem was actually unfrozen
- * as a result of a successful call to FITHAW, only that if an error
- * was returned the filesystem was *not* unfrozen by that particular
- * call.
- *
- * since multiple preceding FIFREEZEs require multiple calls to FITHAW
- * to unfreeze, continuing issuing FITHAW until an error is returned,
- * in which case either the filesystem is in an unfreezable state, or,
- * more likely, it was thawed previously (and remains so afterward).
- *
- * also, since the most recent successful call is the one that did
- * the actual unfreeze, we can use this to provide an accurate count
- * of the number of filesystems unfrozen by guest-fsfreeze-thaw, which
- * may * be useful for determining whether a filesystem was unfrozen
- * during the freeze/thaw phase by a process other than qemu-ga.
- */
- do {
- ret = ioctl(fd, FITHAW);
- if (ret == 0 && !logged) {
- i++;
- logged = true;
- }
- } while (ret == 0);
- close(fd);
- }
-
- ga_unset_frozen(ga_state);
- free_fs_mount_list(&mounts);
-
- execute_fsfreeze_hook(FSFREEZE_HOOK_THAW, errp);
-
- return i;
-}
-
-static void guest_fsfreeze_cleanup(void)
-{
- Error *err = NULL;
-
- if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
- qmp_guest_fsfreeze_thaw(&err);
- if (err) {
- slog("failed to clean up frozen filesystems: %s",
- error_get_pretty(err));
- error_free(err);
- }
- }
-}
-#endif /* CONFIG_FSFREEZE */
-
-#if defined(CONFIG_FSTRIM)
-/*
- * Walk list of mounted file systems in the guest, and trim them.
- */
-GuestFilesystemTrimResponse *
-qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
-{
- GuestFilesystemTrimResponse *response;
- GuestFilesystemTrimResult *result;
- int ret = 0;
- FsMountList mounts;
- struct FsMount *mount;
- int fd;
- struct fstrim_range r;
-
- slog("guest-fstrim called");
-
- QTAILQ_INIT(&mounts);
- if (!build_fs_mount_list(&mounts, errp)) {
- return NULL;
- }
-
- response = g_malloc0(sizeof(*response));
-
- QTAILQ_FOREACH(mount, &mounts, next) {
- result = g_malloc0(sizeof(*result));
- result->path = g_strdup(mount->dirname);
-
- QAPI_LIST_PREPEND(response->paths, result);
-
- fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
- if (fd == -1) {
- result->error = g_strdup_printf("failed to open: %s",
- strerror(errno));
- result->has_error = true;
- continue;
- }
-
- /* We try to cull filesystems we know won't work in advance, but other
- * filesystems may not implement fstrim for less obvious reasons.
- * These will report EOPNOTSUPP; while in some other cases ENOTTY
- * will be reported (e.g. CD-ROMs).
- * Any other error means an unexpected error.
- */
- r.start = 0;
- r.len = -1;
- r.minlen = has_minimum ? minimum : 0;
- ret = ioctl(fd, FITRIM, &r);
- if (ret == -1) {
- result->has_error = true;
- if (errno == ENOTTY || errno == EOPNOTSUPP) {
- result->error = g_strdup("trim not supported");
- } else {
- result->error = g_strdup_printf("failed to trim: %s",
- strerror(errno));
- }
- close(fd);
- continue;
- }
-
- result->has_minimum = true;
- result->minimum = r.minlen;
- result->has_trimmed = true;
- result->trimmed = r.len;
- close(fd);
- }
-
- free_fs_mount_list(&mounts);
- return response;
-}
-#endif /* CONFIG_FSTRIM */
-
-
-#define LINUX_SYS_STATE_FILE "/sys/power/state"
-#define SUSPEND_SUPPORTED 0
-#define SUSPEND_NOT_SUPPORTED 1
-
-typedef enum {
- SUSPEND_MODE_DISK = 0,
- SUSPEND_MODE_RAM = 1,
- SUSPEND_MODE_HYBRID = 2,
-} SuspendMode;
-
-/*
- * Executes a command in a child process using g_spawn_sync,
- * returning an int >= 0 representing the exit status of the
- * process.
- *
- * If the program wasn't found in path, returns -1.
- *
- * If a problem happened when creating the child process,
- * returns -1 and errp is set.
- */
-static int run_process_child(const char *command[], Error **errp)
-{
- int exit_status, spawn_flag;
- GError *g_err = NULL;
- bool success;
-
- spawn_flag = G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL |
- G_SPAWN_STDERR_TO_DEV_NULL;
-
- success = g_spawn_sync(NULL, (char **)command, NULL, spawn_flag,
- NULL, NULL, NULL, NULL,
- &exit_status, &g_err);
-
- if (success) {
- return WEXITSTATUS(exit_status);
- }
-
- if (g_err && (g_err->code != G_SPAWN_ERROR_NOENT)) {
- error_setg(errp, "failed to create child process, error '%s'",
- g_err->message);
- }
-
- g_error_free(g_err);
- return -1;
-}
-
-static bool systemd_supports_mode(SuspendMode mode, Error **errp)
-{
- const char *systemctl_args[3] = {"systemd-hibernate", "systemd-suspend",
- "systemd-hybrid-sleep"};
- const char *cmd[4] = {"systemctl", "status", systemctl_args[mode], NULL};
- int status;
-
- status = run_process_child(cmd, errp);
-
- /*
- * systemctl status uses LSB return codes so we can expect
- * status > 0 and be ok. To assert if the guest has support
- * for the selected suspend mode, status should be < 4. 4 is
- * the code for unknown service status, the return value when
- * the service does not exist. A common value is status = 3
- * (program is not running).
- */
- if (status > 0 && status < 4) {
- return true;
- }
-
- return false;
-}
-
-static void systemd_suspend(SuspendMode mode, Error **errp)
-{
- Error *local_err = NULL;
- const char *systemctl_args[3] = {"hibernate", "suspend", "hybrid-sleep"};
- const char *cmd[3] = {"systemctl", systemctl_args[mode], NULL};
- int status;
-
- status = run_process_child(cmd, &local_err);
-
- if (status == 0) {
- return;
- }
-
- if ((status == -1) && !local_err) {
- error_setg(errp, "the helper program 'systemctl %s' was not found",
- systemctl_args[mode]);
- return;
- }
-
- if (local_err) {
- error_propagate(errp, local_err);
- } else {
- error_setg(errp, "the helper program 'systemctl %s' returned an "
- "unexpected exit status code (%d)",
- systemctl_args[mode], status);
- }
-}
-
-static bool pmutils_supports_mode(SuspendMode mode, Error **errp)
-{
- Error *local_err = NULL;
- const char *pmutils_args[3] = {"--hibernate", "--suspend",
- "--suspend-hybrid"};
- const char *cmd[3] = {"pm-is-supported", pmutils_args[mode], NULL};
- int status;
-
- status = run_process_child(cmd, &local_err);
-
- if (status == SUSPEND_SUPPORTED) {
- return true;
- }
-
- if ((status == -1) && !local_err) {
- return false;
- }
-
- if (local_err) {
- error_propagate(errp, local_err);
- } else {
- error_setg(errp,
- "the helper program '%s' returned an unexpected exit"
- " status code (%d)", "pm-is-supported", status);
- }
-
- return false;
-}
-
-static void pmutils_suspend(SuspendMode mode, Error **errp)
-{
- Error *local_err = NULL;
- const char *pmutils_binaries[3] = {"pm-hibernate", "pm-suspend",
- "pm-suspend-hybrid"};
- const char *cmd[2] = {pmutils_binaries[mode], NULL};
- int status;
-
- status = run_process_child(cmd, &local_err);
-
- if (status == 0) {
- return;
- }
-
- if ((status == -1) && !local_err) {
- error_setg(errp, "the helper program '%s' was not found",
- pmutils_binaries[mode]);
- return;
- }
-
- if (local_err) {
- error_propagate(errp, local_err);
- } else {
- error_setg(errp,
- "the helper program '%s' returned an unexpected exit"
- " status code (%d)", pmutils_binaries[mode], status);
- }
-}
-
-static bool linux_sys_state_supports_mode(SuspendMode mode, Error **errp)
-{
- const char *sysfile_strs[3] = {"disk", "mem", NULL};
- const char *sysfile_str = sysfile_strs[mode];
- char buf[32]; /* hopefully big enough */
- int fd;
- ssize_t ret;
-
- if (!sysfile_str) {
- error_setg(errp, "unknown guest suspend mode");
- return false;
- }
-
- fd = open(LINUX_SYS_STATE_FILE, O_RDONLY);
- if (fd < 0) {
- return false;
- }
-
- ret = read(fd, buf, sizeof(buf) - 1);
- close(fd);
- if (ret <= 0) {
- return false;
- }
- buf[ret] = '\0';
-
- if (strstr(buf, sysfile_str)) {
- return true;
- }
- return false;
-}
-
-static void linux_sys_state_suspend(SuspendMode mode, Error **errp)
-{
- Error *local_err = NULL;
- const char *sysfile_strs[3] = {"disk", "mem", NULL};
- const char *sysfile_str = sysfile_strs[mode];
- pid_t pid;
- int status;
-
- if (!sysfile_str) {
- error_setg(errp, "unknown guest suspend mode");
- return;
- }
-
- pid = fork();
- if (!pid) {
- /* child */
- int fd;
-
- setsid();
- reopen_fd_to_null(0);
- reopen_fd_to_null(1);
- reopen_fd_to_null(2);
-
- fd = open(LINUX_SYS_STATE_FILE, O_WRONLY);
- if (fd < 0) {
- _exit(EXIT_FAILURE);
- }
-
- if (write(fd, sysfile_str, strlen(sysfile_str)) < 0) {
- _exit(EXIT_FAILURE);
- }
-
- _exit(EXIT_SUCCESS);
- } else if (pid < 0) {
- error_setg_errno(errp, errno, "failed to create child process");
- return;
- }
-
- ga_wait_child(pid, &status, &local_err);
- if (local_err) {
- error_propagate(errp, local_err);
- return;
- }
-
- if (WEXITSTATUS(status)) {
- error_setg(errp, "child process has failed to suspend");
- }
-
-}
-
-static void guest_suspend(SuspendMode mode, Error **errp)
-{
- Error *local_err = NULL;
- bool mode_supported = false;
-
- if (systemd_supports_mode(mode, &local_err)) {
- mode_supported = true;
- systemd_suspend(mode, &local_err);
- }
-
- if (!local_err) {
- return;
- }
-
- error_free(local_err);
- local_err = NULL;
-
- if (pmutils_supports_mode(mode, &local_err)) {
- mode_supported = true;
- pmutils_suspend(mode, &local_err);
- }
-
- if (!local_err) {
- return;
- }
-
- error_free(local_err);
- local_err = NULL;
-
- if (linux_sys_state_supports_mode(mode, &local_err)) {
- mode_supported = true;
- linux_sys_state_suspend(mode, &local_err);
- }
-
- if (!mode_supported) {
- error_free(local_err);
- error_setg(errp,
- "the requested suspend mode is not supported by the guest");
- } else {
- error_propagate(errp, local_err);
- }
-}
-
-void qmp_guest_suspend_disk(Error **errp)
-{
- guest_suspend(SUSPEND_MODE_DISK, errp);
-}
-
-void qmp_guest_suspend_ram(Error **errp)
-{
- guest_suspend(SUSPEND_MODE_RAM, errp);
-}
-
-void qmp_guest_suspend_hybrid(Error **errp)
-{
- guest_suspend(SUSPEND_MODE_HYBRID, errp);
-}
-
-/* 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,
- char *dirpath, Error **errp)
-{
- int fd;
- int res;
- int dirfd;
- static const char fn[] = "online";
-
- dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
- if (dirfd == -1) {
- error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
- return;
- }
-
- 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);
-}
-
-GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
-{
- GuestLogicalProcessorList *head, **tail;
- const char *cpu_dir = "/sys/devices/system/cpu";
- const gchar *line;
- g_autoptr(GDir) cpu_gdir = NULL;
- Error *local_err = NULL;
-
- head = NULL;
- tail = &head;
- cpu_gdir = g_dir_open(cpu_dir, 0, NULL);
-
- if (cpu_gdir == NULL) {
- error_setg_errno(errp, errno, "failed to list entries: %s", cpu_dir);
- return NULL;
- }
-
- while (local_err == NULL && (line = g_dir_read_name(cpu_gdir)) != NULL) {
- GuestLogicalProcessor *vcpu;
- int64_t id;
- if (sscanf(line, "cpu%" PRId64, &id)) {
- g_autofree char *path = g_strdup_printf("/sys/devices/system/cpu/"
- "cpu%" PRId64 "/", id);
- vcpu = g_malloc0(sizeof *vcpu);
- vcpu->logical_id = id;
- vcpu->has_can_offline = true; /* lolspeak ftw */
- transfer_vcpu(vcpu, true, path, &local_err);
- QAPI_LIST_APPEND(tail, vcpu);
- }
- }
-
- 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) {
- char *path = g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/",
- vcpus->value->logical_id);
-
- transfer_vcpu(vcpus->value, false, path, &local_err);
- g_free(path);
- 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;
-}
-
-void qmp_guest_set_user_password(const char *username,
- const char *password,
- bool crypted,
- Error **errp)
-{
- Error *local_err = NULL;
- char *passwd_path = NULL;
- pid_t pid;
- int status;
- int datafd[2] = { -1, -1 };
- char *rawpasswddata = NULL;
- size_t rawpasswdlen;
- char *chpasswddata = NULL;
- size_t chpasswdlen;
-
- rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp);
- if (!rawpasswddata) {
- return;
- }
- rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
- rawpasswddata[rawpasswdlen] = '\0';
-
- if (strchr(rawpasswddata, '\n')) {
- error_setg(errp, "forbidden characters in raw password");
- goto out;
- }
-
- if (strchr(username, '\n') ||
- strchr(username, ':')) {
- error_setg(errp, "forbidden characters in username");
- goto out;
- }
-
- chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata);
- chpasswdlen = strlen(chpasswddata);
-
- passwd_path = g_find_program_in_path("chpasswd");
-
- if (!passwd_path) {
- error_setg(errp, "cannot find 'passwd' program in PATH");
- goto out;
- }
-
- if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) {
- error_setg(errp, "cannot create pipe FDs");
- goto out;
- }
-
- pid = fork();
- if (pid == 0) {
- close(datafd[1]);
- /* child */
- setsid();
- dup2(datafd[0], 0);
- reopen_fd_to_null(1);
- reopen_fd_to_null(2);
-
- if (crypted) {
- execl(passwd_path, "chpasswd", "-e", NULL);
- } else {
- execl(passwd_path, "chpasswd", NULL);
- }
- _exit(EXIT_FAILURE);
- } else if (pid < 0) {
- error_setg_errno(errp, errno, "failed to create child process");
- goto out;
- }
- close(datafd[0]);
- datafd[0] = -1;
-
- if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) != chpasswdlen) {
- error_setg_errno(errp, errno, "cannot write new account password");
- goto out;
- }
- close(datafd[1]);
- datafd[1] = -1;
-
- ga_wait_child(pid, &status, &local_err);
- if (local_err) {
- error_propagate(errp, local_err);
- goto out;
- }
-
- if (!WIFEXITED(status)) {
- error_setg(errp, "child process has terminated abnormally");
- goto out;
- }
-
- if (WEXITSTATUS(status)) {
- error_setg(errp, "child process has failed to set user password");
- goto out;
- }
-
-out:
- g_free(chpasswddata);
- g_free(rawpasswddata);
- g_free(passwd_path);
- if (datafd[0] != -1) {
- close(datafd[0]);
- }
- if (datafd[1] != -1) {
- close(datafd[1]);
- }
-}
-
-static void ga_read_sysfs_file(int dirfd, const char *pathname, char *buf,
- int size, Error **errp)
-{
- int fd;
- int res;
-
- errno = 0;
- fd = openat(dirfd, pathname, O_RDONLY);
- if (fd == -1) {
- error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname);
- return;
- }
-
- res = pread(fd, buf, size, 0);
- if (res == -1) {
- error_setg_errno(errp, errno, "pread sysfs file \"%s\"", pathname);
- } else if (res == 0) {
- error_setg(errp, "pread sysfs file \"%s\": unexpected EOF", pathname);
- }
- close(fd);
-}
-
-static void ga_write_sysfs_file(int dirfd, const char *pathname,
- const char *buf, int size, Error **errp)
-{
- int fd;
-
- errno = 0;
- fd = openat(dirfd, pathname, O_WRONLY);
- if (fd == -1) {
- error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname);
- return;
- }
-
- if (pwrite(fd, buf, size, 0) == -1) {
- error_setg_errno(errp, errno, "pwrite sysfs file \"%s\"", pathname);
- }
-
- close(fd);
-}
-
-/* Transfer online/offline status between @mem_blk and the guest system.
- *
- * On input either @errp or *@errp must be NULL.
- *
- * In system-to-@mem_blk direction, the following @mem_blk fields are accessed:
- * - R: mem_blk->phys_index
- * - W: mem_blk->online
- * - W: mem_blk->can_offline
- *
- * In @mem_blk-to-system direction, the following @mem_blk fields are accessed:
- * - R: mem_blk->phys_index
- * - R: mem_blk->online
- *- R: mem_blk->can_offline
- * Written members remain unmodified on error.
- */
-static void transfer_memory_block(GuestMemoryBlock *mem_blk, bool sys2memblk,
- GuestMemoryBlockResponse *result,
- Error **errp)
-{
- char *dirpath;
- int dirfd;
- char *status;
- Error *local_err = NULL;
-
- if (!sys2memblk) {
- DIR *dp;
-
- if (!result) {
- error_setg(errp, "Internal error, 'result' should not be NULL");
- return;
- }
- errno = 0;
- dp = opendir("/sys/devices/system/memory/");
- /* if there is no 'memory' directory in sysfs,
- * we think this VM does not support online/offline memory block,
- * any other solution?
- */
- if (!dp) {
- if (errno == ENOENT) {
- result->response =
- GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
- }
- goto out1;
- }
- closedir(dp);
- }
-
- dirpath = g_strdup_printf("/sys/devices/system/memory/memory%" PRId64 "/",
- mem_blk->phys_index);
- dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
- if (dirfd == -1) {
- if (sys2memblk) {
- error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
- } else {
- if (errno == ENOENT) {
- result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_NOT_FOUND;
- } else {
- result->response =
- GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
- }
- }
- g_free(dirpath);
- goto out1;
- }
- g_free(dirpath);
-
- status = g_malloc0(10);
- ga_read_sysfs_file(dirfd, "state", status, 10, &local_err);
- if (local_err) {
- /* treat with sysfs file that not exist in old kernel */
- if (errno == ENOENT) {
- error_free(local_err);
- if (sys2memblk) {
- mem_blk->online = true;
- mem_blk->can_offline = false;
- } else if (!mem_blk->online) {
- result->response =
- GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
- }
- } else {
- if (sys2memblk) {
- error_propagate(errp, local_err);
- } else {
- error_free(local_err);
- result->response =
- GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
- }
- }
- goto out2;
- }
-
- if (sys2memblk) {
- char removable = '0';
-
- mem_blk->online = (strncmp(status, "online", 6) == 0);
-
- ga_read_sysfs_file(dirfd, "removable", &removable, 1, &local_err);
- if (local_err) {
- /* if no 'removable' file, it doesn't support offline mem blk */
- if (errno == ENOENT) {
- error_free(local_err);
- mem_blk->can_offline = false;
- } else {
- error_propagate(errp, local_err);
- }
- } else {
- mem_blk->can_offline = (removable != '0');
- }
- } else {
- if (mem_blk->online != (strncmp(status, "online", 6) == 0)) {
- const char *new_state = mem_blk->online ? "online" : "offline";
-
- ga_write_sysfs_file(dirfd, "state", new_state, strlen(new_state),
- &local_err);
- if (local_err) {
- error_free(local_err);
- result->response =
- GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
- goto out2;
- }
-
- result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_SUCCESS;
- result->has_error_code = false;
- } /* otherwise pretend successful re-(on|off)-lining */
- }
- g_free(status);
- close(dirfd);
- return;
-
-out2:
- g_free(status);
- close(dirfd);
-out1:
- if (!sys2memblk) {
- result->has_error_code = true;
- result->error_code = errno;
- }
-}
-
-GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
-{
- GuestMemoryBlockList *head, **tail;
- Error *local_err = NULL;
- struct dirent *de;
- DIR *dp;
-
- head = NULL;
- tail = &head;
-
- dp = opendir("/sys/devices/system/memory/");
- if (!dp) {
- /* it's ok if this happens to be a system that doesn't expose
- * memory blocks via sysfs, but otherwise we should report
- * an error
- */
- if (errno != ENOENT) {
- error_setg_errno(errp, errno, "Can't open directory"
- "\"/sys/devices/system/memory/\"");
- }
- return NULL;
- }
-
- /* Note: the phys_index of memory block may be discontinuous,
- * this is because a memblk is the unit of the Sparse Memory design, which
- * allows discontinuous memory ranges (ex. NUMA), so here we should
- * traverse the memory block directory.
- */
- while ((de = readdir(dp)) != NULL) {
- GuestMemoryBlock *mem_blk;
-
- if ((strncmp(de->d_name, "memory", 6) != 0) ||
- !(de->d_type & DT_DIR)) {
- continue;
- }
-
- mem_blk = g_malloc0(sizeof *mem_blk);
- /* The d_name is "memoryXXX", phys_index is block id, same as XXX */
- mem_blk->phys_index = strtoul(&de->d_name[6], NULL, 10);
- mem_blk->has_can_offline = true; /* lolspeak ftw */
- transfer_memory_block(mem_blk, true, NULL, &local_err);
- if (local_err) {
- break;
- }
-
- QAPI_LIST_APPEND(tail, mem_blk);
- }
-
- closedir(dp);
- if (local_err == NULL) {
- /* there's no guest with zero memory blocks */
- if (head == NULL) {
- error_setg(errp, "guest reported zero memory blocks!");
- }
- return head;
- }
-
- qapi_free_GuestMemoryBlockList(head);
- error_propagate(errp, local_err);
- return NULL;
-}
-
-GuestMemoryBlockResponseList *
-qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp)
-{
- GuestMemoryBlockResponseList *head, **tail;
- Error *local_err = NULL;
-
- head = NULL;
- tail = &head;
-
- while (mem_blks != NULL) {
- GuestMemoryBlockResponse *result;
- GuestMemoryBlock *current_mem_blk = mem_blks->value;
-
- result = g_malloc0(sizeof(*result));
- result->phys_index = current_mem_blk->phys_index;
- transfer_memory_block(current_mem_blk, false, result, &local_err);
- if (local_err) { /* should never happen */
- goto err;
- }
-
- QAPI_LIST_APPEND(tail, result);
- mem_blks = mem_blks->next;
- }
-
- return head;
-err:
- qapi_free_GuestMemoryBlockResponseList(head);
- error_propagate(errp, local_err);
- return NULL;
-}
-
-GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
-{
- Error *local_err = NULL;
- char *dirpath;
- int dirfd;
- char *buf;
- GuestMemoryBlockInfo *info;
-
- dirpath = g_strdup_printf("/sys/devices/system/memory/");
- dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
- if (dirfd == -1) {
- error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
- g_free(dirpath);
- return NULL;
- }
- g_free(dirpath);
-
- buf = g_malloc0(20);
- ga_read_sysfs_file(dirfd, "block_size_bytes", buf, 20, &local_err);
- close(dirfd);
- if (local_err) {
- g_free(buf);
- error_propagate(errp, local_err);
- return NULL;
- }
-
- info = g_new0(GuestMemoryBlockInfo, 1);
- info->size = strtol(buf, NULL, 16); /* the unit is bytes */
-
- g_free(buf);
-
- return info;
-}
-
-#define MAX_NAME_LEN 128
-static GuestDiskStatsInfoList *guest_get_diskstats(Error **errp)
-{
-#ifdef CONFIG_LINUX
- GuestDiskStatsInfoList *head = NULL, **tail = &head;
- const char *diskstats = "/proc/diskstats";
- FILE *fp;
- size_t n;
- char *line = NULL;
-
- fp = fopen(diskstats, "r");
- if (fp == NULL) {
- error_setg_errno(errp, errno, "open(\"%s\")", diskstats);
- return NULL;
- }
-
- while (getline(&line, &n, fp) != -1) {
- g_autofree GuestDiskStatsInfo *diskstatinfo = NULL;
- g_autofree GuestDiskStats *diskstat = NULL;
- char dev_name[MAX_NAME_LEN];
- unsigned int ios_pgr, tot_ticks, rq_ticks, wr_ticks, dc_ticks, fl_ticks;
- unsigned long rd_ios, rd_merges_or_rd_sec, rd_ticks_or_wr_sec, wr_ios;
- unsigned long wr_merges, rd_sec_or_wr_ios, wr_sec;
- unsigned long dc_ios, dc_merges, dc_sec, fl_ios;
- unsigned int major, minor;
- int i;
-
- i = sscanf(line, "%u %u %s %lu %lu %lu"
- "%lu %lu %lu %lu %u %u %u %u"
- "%lu %lu %lu %u %lu %u",
- &major, &minor, dev_name,
- &rd_ios, &rd_merges_or_rd_sec, &rd_sec_or_wr_ios,
- &rd_ticks_or_wr_sec, &wr_ios, &wr_merges, &wr_sec,
- &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks,
- &dc_ios, &dc_merges, &dc_sec, &dc_ticks,
- &fl_ios, &fl_ticks);
-
- if (i < 7) {
- continue;
- }
-
- diskstatinfo = g_new0(GuestDiskStatsInfo, 1);
- diskstatinfo->name = g_strdup(dev_name);
- diskstatinfo->major = major;
- diskstatinfo->minor = minor;
-
- diskstat = g_new0(GuestDiskStats, 1);
- if (i == 7) {
- diskstat->has_read_ios = true;
- diskstat->read_ios = rd_ios;
- diskstat->has_read_sectors = true;
- diskstat->read_sectors = rd_merges_or_rd_sec;
- diskstat->has_write_ios = true;
- diskstat->write_ios = rd_sec_or_wr_ios;
- diskstat->has_write_sectors = true;
- diskstat->write_sectors = rd_ticks_or_wr_sec;
- }
- if (i >= 14) {
- diskstat->has_read_ios = true;
- diskstat->read_ios = rd_ios;
- diskstat->has_read_sectors = true;
- diskstat->read_sectors = rd_sec_or_wr_ios;
- diskstat->has_read_merges = true;
- diskstat->read_merges = rd_merges_or_rd_sec;
- diskstat->has_read_ticks = true;
- diskstat->read_ticks = rd_ticks_or_wr_sec;
- diskstat->has_write_ios = true;
- diskstat->write_ios = wr_ios;
- diskstat->has_write_sectors = true;
- diskstat->write_sectors = wr_sec;
- diskstat->has_write_merges = true;
- diskstat->write_merges = wr_merges;
- diskstat->has_write_ticks = true;
- diskstat->write_ticks = wr_ticks;
- diskstat->has_ios_pgr = true;
- diskstat->ios_pgr = ios_pgr;
- diskstat->has_total_ticks = true;
- diskstat->total_ticks = tot_ticks;
- diskstat->has_weight_ticks = true;
- diskstat->weight_ticks = rq_ticks;
- }
- if (i >= 18) {
- diskstat->has_discard_ios = true;
- diskstat->discard_ios = dc_ios;
- diskstat->has_discard_merges = true;
- diskstat->discard_merges = dc_merges;
- diskstat->has_discard_sectors = true;
- diskstat->discard_sectors = dc_sec;
- diskstat->has_discard_ticks = true;
- diskstat->discard_ticks = dc_ticks;
- }
- if (i >= 20) {
- diskstat->has_flush_ios = true;
- diskstat->flush_ios = fl_ios;
- diskstat->has_flush_ticks = true;
- diskstat->flush_ticks = fl_ticks;
- }
-
- diskstatinfo->stats = g_steal_pointer(&diskstat);
- QAPI_LIST_APPEND(tail, diskstatinfo);
- diskstatinfo = NULL;
- }
- free(line);
- fclose(fp);
- return head;
-#else
- g_debug("disk stats reporting available only for Linux");
- return NULL;
-#endif
-}
-
-GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp)
-{
- return guest_get_diskstats(errp);
-}
-
-GuestCpuStatsList *qmp_guest_get_cpustats(Error **errp)
-{
- GuestCpuStatsList *head = NULL, **tail = &head;
- const char *cpustats = "/proc/stat";
- int clk_tck = sysconf(_SC_CLK_TCK);
- FILE *fp;
- size_t n;
- char *line = NULL;
-
- fp = fopen(cpustats, "r");
- if (fp == NULL) {
- error_setg_errno(errp, errno, "open(\"%s\")", cpustats);
- return NULL;
- }
-
- while (getline(&line, &n, fp) != -1) {
- GuestCpuStats *cpustat = NULL;
- GuestLinuxCpuStats *linuxcpustat;
- int i;
- unsigned long user, system, idle, iowait, irq, softirq, steal, guest;
- unsigned long nice, guest_nice;
- char name[64];
-
- i = sscanf(line, "%s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
- name, &user, &nice, &system, &idle, &iowait, &irq, &softirq,
- &steal, &guest, &guest_nice);
-
- /* drop "cpu 1 2 3 ...", get "cpuX 1 2 3 ..." only */
- if ((i == EOF) || strncmp(name, "cpu", 3) || (name[3] == '\0')) {
- continue;
- }
-
- if (i < 5) {
- slog("Parsing cpu stat from %s failed, see \"man proc\"", cpustats);
- break;
- }
-
- cpustat = g_new0(GuestCpuStats, 1);
- cpustat->type = GUEST_CPU_STATS_TYPE_LINUX;
-
- linuxcpustat = &cpustat->u.q_linux;
- linuxcpustat->cpu = atoi(&name[3]);
- linuxcpustat->user = user * 1000 / clk_tck;
- linuxcpustat->nice = nice * 1000 / clk_tck;
- linuxcpustat->system = system * 1000 / clk_tck;
- linuxcpustat->idle = idle * 1000 / clk_tck;
-
- if (i > 5) {
- linuxcpustat->has_iowait = true;
- linuxcpustat->iowait = iowait * 1000 / clk_tck;
- }
-
- if (i > 6) {
- linuxcpustat->has_irq = true;
- linuxcpustat->irq = irq * 1000 / clk_tck;
- linuxcpustat->has_softirq = true;
- linuxcpustat->softirq = softirq * 1000 / clk_tck;
- }
-
- if (i > 8) {
- linuxcpustat->has_steal = true;
- linuxcpustat->steal = steal * 1000 / clk_tck;
- }
-
- if (i > 9) {
- linuxcpustat->has_guest = true;
- linuxcpustat->guest = guest * 1000 / clk_tck;
- }
-
- if (i > 10) {
- linuxcpustat->has_guest = true;
- linuxcpustat->guest = guest * 1000 / clk_tck;
- linuxcpustat->has_guestnice = true;
- linuxcpustat->guestnice = guest_nice * 1000 / clk_tck;
- }
-
- QAPI_LIST_APPEND(tail, cpustat);
- }
-
- free(line);
- fclose(fp);
- return head;
-}
-
-#else /* defined(__linux__) */
+#if !defined(__linux__)
void qmp_guest_suspend_disk(Error **errp)
{
diff --git a/qga/meson.build b/qga/meson.build
index 65c1e93846..409f49a000 100644
--- a/qga/meson.build
+++ b/qga/meson.build
@@ -72,6 +72,9 @@ qga_ss.add(when: 'CONFIG_POSIX', if_true: files(
'commands-posix.c',
'commands-posix-ssh.c',
))
+qga_ss.add(when: 'CONFIG_LINUX', if_true: files(
+ 'commands-linux.c',
+))
qga_ss.add(when: 'CONFIG_WIN32', if_true: files(
'channel-win32.c',
'commands-win32.c',
--
2.34.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 2/5] qga: Add initial FreeBSD support
2022-09-22 13:19 [PATCH 0/5] qga: Add FreeBSD support Alexander Ivanov
2022-09-22 13:19 ` [PATCH 1/5] qga: Move Linux-specific commands code to separate file Alexander Ivanov
@ 2022-09-22 13:19 ` Alexander Ivanov
2022-09-23 7:56 ` Marc-André Lureau
2022-09-22 13:19 ` [PATCH 3/5] qga: Add UFS freeze/thaw support for FreeBSD Alexander Ivanov
` (2 subsequent siblings)
4 siblings, 1 reply; 14+ messages in thread
From: Alexander Ivanov @ 2022-09-22 13:19 UTC (permalink / raw)
To: qemu-devel; +Cc: den, michael.roth, kkostiuk
Add commands-bsd.c file with dumb functions, fix device path and make
the code buildable in FreeBSD.
Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
---
meson.build | 2 +-
qga/commands-bsd.c | 121 +++++++++++++++++++++++++++++++++++++++++++
qga/commands-posix.c | 6 ++-
qga/main.c | 11 +++-
qga/meson.build | 3 ++
5 files changed, 140 insertions(+), 3 deletions(-)
create mode 100644 qga/commands-bsd.c
diff --git a/meson.build b/meson.build
index c2adb7caf4..574cc1e91e 100644
--- a/meson.build
+++ b/meson.build
@@ -75,7 +75,7 @@ have_tools = get_option('tools') \
.allowed()
have_ga = get_option('guest_agent') \
.disable_auto_if(not have_system and not have_tools) \
- .require(targetos in ['sunos', 'linux', 'windows'],
+ .require(targetos in ['sunos', 'linux', 'windows', 'freebsd'],
error_message: 'unsupported OS for QEMU guest agent') \
.allowed()
have_block = have_system or have_tools
diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
new file mode 100644
index 0000000000..c1e3ed13e9
--- /dev/null
+++ b/qga/commands-bsd.c
@@ -0,0 +1,121 @@
+/*
+ * QEMU Guest Agent BSD-specific command implementations
+ *
+ * Copyright (c) Virtuozzo International GmbH.
+ *
+ * Authors:
+ * Alexander Ivanov <alexander.ivanov@virtuozzo.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 "qemu/osdep.h"
+#include "qga-qapi-commands.h"
+#include "qapi/qmp/qerror.h"
+#include "qapi/error.h"
+#include "qemu/queue.h"
+#include "commands-common.h"
+
+#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
+bool build_fs_mount_list(FsMountList *mounts, Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return false;
+}
+#endif
+
+#if defined(CONFIG_FSFREEZE)
+int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints,
+ strList *mountpoints,
+ FsMountList mounts,
+ Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return 0;
+}
+
+int qmp_guest_fsfreeze_do_thaw(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return 0;
+}
+#endif
+
+GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+GuestCpuStatsList *qmp_guest_get_cpustats(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+void qmp_guest_suspend_disk(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+}
+
+void qmp_guest_suspend_ram(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+}
+
+void qmp_guest_suspend_hybrid(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+}
+
+GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return -1;
+}
+
+void qmp_guest_set_user_password(const char *username,
+ const char *password,
+ bool crypted,
+ Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+}
+
+GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+GuestMemoryBlockResponseList *
+qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
diff --git a/qga/commands-posix.c b/qga/commands-posix.c
index 0bb8b9e2f3..3a1055d5c3 100644
--- a/qga/commands-posix.c
+++ b/qga/commands-posix.c
@@ -31,6 +31,10 @@
#include <utmpx.h>
#endif
+#ifdef __FreeBSD__
+#undef HAVE_GETIFADDRS
+#endif
+
#ifdef HAVE_GETIFADDRS
#include <arpa/inet.h>
#include <sys/socket.h>
@@ -763,7 +767,7 @@ void qmp_guest_file_flush(int64_t handle, Error **errp)
}
}
-#if !defined(__linux__)
+#if !(defined(__linux__) || defined(__FreeBSD__))
void qmp_guest_suspend_disk(Error **errp)
{
diff --git a/qga/main.c b/qga/main.c
index 5f1efa2333..22b3c0df11 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -45,9 +45,14 @@
#endif
#ifndef _WIN32
+#ifdef __FreeBSD__
+#define QGA_VIRTIO_PATH_DEFAULT "/dev/vtcon/org.qemu.guest_agent.0"
+#define QGA_SERIAL_PATH_DEFAULT "/dev/vtcon/org.qemu.guest_agent.0"
+#else /* __FreeBSD__ */
#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent.0"
-#define QGA_STATE_RELATIVE_DIR "run"
#define QGA_SERIAL_PATH_DEFAULT "/dev/ttyS0"
+#endif /* __FreeBSD__ */
+#define QGA_STATE_RELATIVE_DIR "run"
#else
#define QGA_VIRTIO_PATH_DEFAULT "\\\\.\\Global\\org.qemu.guest_agent.0"
#define QGA_STATE_RELATIVE_DIR "qemu-ga"
@@ -1475,7 +1480,11 @@ int main(int argc, char **argv)
}
if (config->method == NULL) {
+#ifdef CONFIG_BSD
+ config->method = g_strdup("isa-serial");
+#else
config->method = g_strdup("virtio-serial");
+#endif
}
socket_activation = check_socket_activation();
diff --git a/qga/meson.build b/qga/meson.build
index 409f49a000..456ba4c29f 100644
--- a/qga/meson.build
+++ b/qga/meson.build
@@ -75,6 +75,9 @@ qga_ss.add(when: 'CONFIG_POSIX', if_true: files(
qga_ss.add(when: 'CONFIG_LINUX', if_true: files(
'commands-linux.c',
))
+qga_ss.add(when: 'CONFIG_BSD', if_true: files(
+ 'commands-bsd.c',
+))
qga_ss.add(when: 'CONFIG_WIN32', if_true: files(
'channel-win32.c',
'commands-win32.c',
--
2.34.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 3/5] qga: Add UFS freeze/thaw support for FreeBSD
2022-09-22 13:19 [PATCH 0/5] qga: Add FreeBSD support Alexander Ivanov
2022-09-22 13:19 ` [PATCH 1/5] qga: Move Linux-specific commands code to separate file Alexander Ivanov
2022-09-22 13:19 ` [PATCH 2/5] qga: Add initial FreeBSD support Alexander Ivanov
@ 2022-09-22 13:19 ` Alexander Ivanov
2022-09-23 8:09 ` Marc-André Lureau
2022-09-22 13:20 ` [PATCH 4/5] qga: Add shutdown/halt/reboot " Alexander Ivanov
2022-09-22 13:20 ` [PATCH 5/5] qga: Add support for user password settinng in FreeeBSD Alexander Ivanov
4 siblings, 1 reply; 14+ messages in thread
From: Alexander Ivanov @ 2022-09-22 13:19 UTC (permalink / raw)
To: qemu-devel; +Cc: den, michael.roth, kkostiuk
UFS supports FS freezing through ioctl UFSSUSPEND on /dev/ufssuspend.
Freezed FS can be thawed by closing /dev/ufssuspend file descriptior.
Use getmntinfo to get a list of mounted FS.
Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
---
qga/commands-bsd.c | 109 +++++++++++++++++++++++++++++++++++++++---
qga/commands-common.h | 11 +++++
qga/main.c | 6 +++
3 files changed, 120 insertions(+), 6 deletions(-)
diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
index c1e3ed13e9..5d3f46804a 100644
--- a/qga/commands-bsd.c
+++ b/qga/commands-bsd.c
@@ -17,28 +17,125 @@
#include "qemu/queue.h"
#include "commands-common.h"
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/ucred.h>
+#include <sys/mount.h>
+#include <paths.h>
+
#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
bool build_fs_mount_list(FsMountList *mounts, Error **errp)
{
- error_setg(errp, QERR_UNSUPPORTED);
- return false;
+ FsMount *mount;
+ struct statfs *mntbuf, *mntp;
+ struct stat statbuf;
+ int i, count, ret;
+
+ count = getmntinfo(&mntbuf, MNT_NOWAIT);
+ if (count == 0) {
+ error_setg_errno(errp, errno, "getmntinfo failed");
+ return false;
+ }
+
+ for (i = 0; i < count; i++) {
+ mntp = &mntbuf[i];
+ ret = stat(mntp->f_mntonname, &statbuf);
+ if (ret != 0) {
+ continue;
+ }
+
+ mount = g_new0(FsMount, 1);
+
+ mount->dirname = g_strdup(mntp->f_mntonname);
+ mount->devtype = g_strdup(mntp->f_fstypename);
+ mount->devmajor = major(mount->dev);
+ mount->devminor = minor(mount->dev);
+ mount->fsid = mntp->f_fsid;
+ mount->dev = statbuf.st_dev;
+
+ QTAILQ_INSERT_TAIL(mounts, mount, next);
+ }
+ return true;
}
#endif
#if defined(CONFIG_FSFREEZE)
+static int ufssuspend_fd = -1;
+static int ufssuspend_cnt;
+
int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints,
strList *mountpoints,
FsMountList mounts,
Error **errp)
{
- error_setg(errp, QERR_UNSUPPORTED);
- return 0;
+ int ret;
+ strList *list;
+ struct FsMount *mount;
+
+ if (ufssuspend_fd != -1) {
+ error_setg(errp, "filesystems have already frozen");
+ return -1;
+ }
+
+ ufssuspend_cnt = 0;
+ ufssuspend_fd = qemu_open(_PATH_UFSSUSPEND, O_RDWR, errp);
+ if (ufssuspend_fd == -1) {
+ return -1;
+ }
+
+ QTAILQ_FOREACH_REVERSE(mount, &mounts, next) {
+ /*
+ * To issue fsfreeze in the reverse order of mounts, check if the
+ * mount is listed in the list here
+ */
+ if (has_mountpoints) {
+ for (list = mountpoints; list; list = list->next) {
+ if (strcmp(list->value, mount->dirname) == 0) {
+ break;
+ }
+ }
+ if (!list) {
+ continue;
+ }
+ }
+
+ /* Only UFS supports suspend */
+ if (strcmp(mount->devtype, "ufs") != 0) {
+ continue;
+ }
+
+ ret = ioctl(ufssuspend_fd, UFSSUSPEND, &mount->fsid);
+ if (ret == -1) {
+ /*
+ * ioctl returns EBUSY for all the FS except the first one
+ * that was suspended
+ */
+ if (errno == EBUSY) {
+ continue;
+ }
+ error_setg_errno(errp, errno, "failed to freeze %s",
+ mount->dirname);
+ goto error;
+ }
+ ufssuspend_cnt++;
+ }
+ return ufssuspend_cnt;
+error:
+ close(ufssuspend_fd);
+ ufssuspend_fd = -1;
+ return -1;
+
}
int qmp_guest_fsfreeze_do_thaw(Error **errp)
{
- error_setg(errp, QERR_UNSUPPORTED);
- return 0;
+ int ret = ufssuspend_cnt;
+ ufssuspend_cnt = 0;
+ if (ufssuspend_fd != -1) {
+ close(ufssuspend_fd);
+ ufssuspend_fd = -1;
+ }
+ return ret;
}
#endif
diff --git a/qga/commands-common.h b/qga/commands-common.h
index aa0472ea4c..c3be6db3a9 100644
--- a/qga/commands-common.h
+++ b/qga/commands-common.h
@@ -41,11 +41,22 @@ void ga_wait_child(pid_t pid, int *status, Error **errp);
#endif
#endif /* __linux__*/
+#ifdef __FreeBSD__
+#include <ufs/ffs/fs.h>
+#ifdef UFSSUSPEND
+#define CONFIG_FSFREEZE
+#endif
+#endif /* __FreeBSD__ */
+
#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
typedef struct FsMount {
char *dirname;
char *devtype;
unsigned int devmajor, devminor;
+#if defined(__FreeBSD__)
+ dev_t dev;
+ fsid_t fsid;
+#endif
QTAILQ_ENTRY(FsMount) next;
} FsMount;
diff --git a/qga/main.c b/qga/main.c
index 22b3c0df11..ab420051fb 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -43,6 +43,12 @@
#define CONFIG_FSFREEZE
#endif
#endif
+#ifdef __FreeBSD__
+#include <ufs/ffs/fs.h>
+#ifdef UFSSUSPEND
+#define CONFIG_FSFREEZE
+#endif
+#endif
#ifndef _WIN32
#ifdef __FreeBSD__
--
2.34.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 4/5] qga: Add shutdown/halt/reboot support for FreeBSD
2022-09-22 13:19 [PATCH 0/5] qga: Add FreeBSD support Alexander Ivanov
` (2 preceding siblings ...)
2022-09-22 13:19 ` [PATCH 3/5] qga: Add UFS freeze/thaw support for FreeBSD Alexander Ivanov
@ 2022-09-22 13:20 ` Alexander Ivanov
2022-09-23 8:13 ` Marc-André Lureau
2022-09-22 13:20 ` [PATCH 5/5] qga: Add support for user password settinng in FreeeBSD Alexander Ivanov
4 siblings, 1 reply; 14+ messages in thread
From: Alexander Ivanov @ 2022-09-22 13:20 UTC (permalink / raw)
To: qemu-devel; +Cc: den, michael.roth, kkostiuk
Add appropriate shutdown command arguments in qmp_guest_shutdown()
for FreeBSD.
Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
---
qga/commands-posix.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/qga/commands-posix.c b/qga/commands-posix.c
index 3a1055d5c3..60cc673f25 100644
--- a/qga/commands-posix.c
+++ b/qga/commands-posix.c
@@ -242,6 +242,10 @@ void qmp_guest_shutdown(bool has_mode, const char *mode, Error **errp)
const char *powerdown_flag = "-i5";
const char *halt_flag = "-i0";
const char *reboot_flag = "-i6";
+#elifdef CONFIG_BSD
+ const char *powerdown_flag = "-p";
+ const char *halt_flag = "-h";
+ const char *reboot_flag = "-r";
#else
const char *powerdown_flag = "-P";
const char *halt_flag = "-H";
@@ -272,6 +276,9 @@ void qmp_guest_shutdown(bool has_mode, const char *mode, Error **errp)
#ifdef CONFIG_SOLARIS
execl("/sbin/shutdown", "shutdown", shutdown_flag, "-g0", "-y",
"hypervisor initiated shutdown", (char *)NULL);
+#elifdef CONFIG_BSD
+ execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
+ "hypervisor initiated shutdown", (char *)NULL);
#else
execl("/sbin/shutdown", "shutdown", "-h", shutdown_flag, "+0",
"hypervisor initiated shutdown", (char *)NULL);
--
2.34.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 5/5] qga: Add support for user password settinng in FreeeBSD
2022-09-22 13:19 [PATCH 0/5] qga: Add FreeBSD support Alexander Ivanov
` (3 preceding siblings ...)
2022-09-22 13:20 ` [PATCH 4/5] qga: Add shutdown/halt/reboot " Alexander Ivanov
@ 2022-09-22 13:20 ` Alexander Ivanov
2022-09-23 8:19 ` Marc-André Lureau
4 siblings, 1 reply; 14+ messages in thread
From: Alexander Ivanov @ 2022-09-22 13:20 UTC (permalink / raw)
To: qemu-devel; +Cc: den, michael.roth, kkostiuk
Move qmp_guest_set_user_password() to commands-posix.c under (__linux__ or
__FreeBSD) condition. Add command and arguments for password setting in
FreeBSD.
Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
---
qga/commands-bsd.c | 8 ---
qga/commands-linux.c | 105 --------------------------------------
qga/commands-posix.c | 117 ++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 116 insertions(+), 114 deletions(-)
diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
index 5d3f46804a..fa3933f2f4 100644
--- a/qga/commands-bsd.c
+++ b/qga/commands-bsd.c
@@ -190,14 +190,6 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
return -1;
}
-void qmp_guest_set_user_password(const char *username,
- const char *password,
- bool crypted,
- Error **errp)
-{
- error_setg(errp, QERR_UNSUPPORTED);
-}
-
GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
{
error_setg(errp, QERR_UNSUPPORTED);
diff --git a/qga/commands-linux.c b/qga/commands-linux.c
index 615e9a0027..1f25c80482 100644
--- a/qga/commands-linux.c
+++ b/qga/commands-linux.c
@@ -1629,111 +1629,6 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
return processed;
}
-void qmp_guest_set_user_password(const char *username,
- const char *password,
- bool crypted,
- Error **errp)
-{
- Error *local_err = NULL;
- char *passwd_path = NULL;
- pid_t pid;
- int status;
- int datafd[2] = { -1, -1 };
- char *rawpasswddata = NULL;
- size_t rawpasswdlen;
- char *chpasswddata = NULL;
- size_t chpasswdlen;
-
- rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp);
- if (!rawpasswddata) {
- return;
- }
- rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
- rawpasswddata[rawpasswdlen] = '\0';
-
- if (strchr(rawpasswddata, '\n')) {
- error_setg(errp, "forbidden characters in raw password");
- goto out;
- }
-
- if (strchr(username, '\n') ||
- strchr(username, ':')) {
- error_setg(errp, "forbidden characters in username");
- goto out;
- }
-
- chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata);
- chpasswdlen = strlen(chpasswddata);
-
- passwd_path = g_find_program_in_path("chpasswd");
-
- if (!passwd_path) {
- error_setg(errp, "cannot find 'passwd' program in PATH");
- goto out;
- }
-
- if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) {
- error_setg(errp, "cannot create pipe FDs");
- goto out;
- }
-
- pid = fork();
- if (pid == 0) {
- close(datafd[1]);
- /* child */
- setsid();
- dup2(datafd[0], 0);
- reopen_fd_to_null(1);
- reopen_fd_to_null(2);
-
- if (crypted) {
- execl(passwd_path, "chpasswd", "-e", NULL);
- } else {
- execl(passwd_path, "chpasswd", NULL);
- }
- _exit(EXIT_FAILURE);
- } else if (pid < 0) {
- error_setg_errno(errp, errno, "failed to create child process");
- goto out;
- }
- close(datafd[0]);
- datafd[0] = -1;
-
- if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) != chpasswdlen) {
- error_setg_errno(errp, errno, "cannot write new account password");
- goto out;
- }
- close(datafd[1]);
- datafd[1] = -1;
-
- ga_wait_child(pid, &status, &local_err);
- if (local_err) {
- error_propagate(errp, local_err);
- goto out;
- }
-
- if (!WIFEXITED(status)) {
- error_setg(errp, "child process has terminated abnormally");
- goto out;
- }
-
- if (WEXITSTATUS(status)) {
- error_setg(errp, "child process has failed to set user password");
- goto out;
- }
-
-out:
- g_free(chpasswddata);
- g_free(rawpasswddata);
- g_free(passwd_path);
- if (datafd[0] != -1) {
- close(datafd[0]);
- }
- if (datafd[1] != -1) {
- close(datafd[1]);
- }
-}
-
static void ga_read_sysfs_file(int dirfd, const char *pathname, char *buf,
int size, Error **errp)
{
diff --git a/qga/commands-posix.c b/qga/commands-posix.c
index 60cc673f25..e8fc7bd516 100644
--- a/qga/commands-posix.c
+++ b/qga/commands-posix.c
@@ -774,8 +774,123 @@ void qmp_guest_file_flush(int64_t handle, Error **errp)
}
}
-#if !(defined(__linux__) || defined(__FreeBSD__))
+#if defined(__linux__) || defined(__FreeBSD__)
+void qmp_guest_set_user_password(const char *username,
+ const char *password,
+ bool crypted,
+ Error **errp)
+{
+ Error *local_err = NULL;
+ char *passwd_path = NULL;
+ pid_t pid;
+ int status;
+ int datafd[2] = { -1, -1 };
+ char *rawpasswddata = NULL;
+ size_t rawpasswdlen;
+ char *chpasswddata = NULL;
+ size_t chpasswdlen;
+ rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp);
+ if (!rawpasswddata) {
+ return;
+ }
+ rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
+ rawpasswddata[rawpasswdlen] = '\0';
+
+ if (strchr(rawpasswddata, '\n')) {
+ error_setg(errp, "forbidden characters in raw password");
+ goto out;
+ }
+
+ if (strchr(username, '\n') ||
+ strchr(username, ':')) {
+ error_setg(errp, "forbidden characters in username");
+ goto out;
+ }
+
+#ifdef __FreeBSD__
+ chpasswddata = g_strdup(rawpasswddata);
+ passwd_path = g_find_program_in_path("pw");
+#else
+ chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata);
+ passwd_path = g_find_program_in_path("chpasswd");
+#endif
+
+ chpasswdlen = strlen(chpasswddata);
+
+ if (!passwd_path) {
+ error_setg(errp, "cannot find 'passwd' program in PATH");
+ goto out;
+ }
+
+ if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) {
+ error_setg(errp, "cannot create pipe FDs");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid == 0) {
+ close(datafd[1]);
+ /* child */
+ setsid();
+ dup2(datafd[0], 0);
+ reopen_fd_to_null(1);
+ reopen_fd_to_null(2);
+
+#ifdef __FreeBSD__
+ const char *h_arg;
+ h_arg = (crypted) ? "-H" : "-h";
+ execl(passwd_path, "pw", "usermod", "-n", username, h_arg, "0", NULL);
+#else
+ if (crypted) {
+ execl(passwd_path, "chpasswd", "-e", NULL);
+ } else {
+ execl(passwd_path, "chpasswd", NULL);
+ }
+#endif
+ _exit(EXIT_FAILURE);
+ } else if (pid < 0) {
+ error_setg_errno(errp, errno, "failed to create child process");
+ goto out;
+ }
+ close(datafd[0]);
+ datafd[0] = -1;
+
+ if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) != chpasswdlen) {
+ error_setg_errno(errp, errno, "cannot write new account password");
+ goto out;
+ }
+ close(datafd[1]);
+ datafd[1] = -1;
+
+ ga_wait_child(pid, &status, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ goto out;
+ }
+
+ if (!WIFEXITED(status)) {
+ error_setg(errp, "child process has terminated abnormally");
+ goto out;
+ }
+
+ if (WEXITSTATUS(status)) {
+ error_setg(errp, "child process has failed to set user password");
+ goto out;
+ }
+
+out:
+ g_free(chpasswddata);
+ g_free(rawpasswddata);
+ g_free(passwd_path);
+ if (datafd[0] != -1) {
+ close(datafd[0]);
+ }
+ if (datafd[1] != -1) {
+ close(datafd[1]);
+ }
+}
+#else
void qmp_guest_suspend_disk(Error **errp)
{
error_setg(errp, QERR_UNSUPPORTED);
--
2.34.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH 2/5] qga: Add initial FreeBSD support
2022-09-22 13:19 ` [PATCH 2/5] qga: Add initial FreeBSD support Alexander Ivanov
@ 2022-09-23 7:56 ` Marc-André Lureau
2022-09-23 9:32 ` Alexander Ivanov
0 siblings, 1 reply; 14+ messages in thread
From: Marc-André Lureau @ 2022-09-23 7:56 UTC (permalink / raw)
To: Alexander Ivanov; +Cc: qemu-devel, den, michael.roth, kkostiuk
[-- Attachment #1: Type: text/plain, Size: 7020 bytes --]
Hi
On Thu, Sep 22, 2022 at 4:28 PM Alexander Ivanov <
alexander.ivanov@virtuozzo.com> wrote:
> Add commands-bsd.c file with dumb functions, fix device path and make
> the code buildable in FreeBSD.
>
> Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
> ---
> meson.build | 2 +-
> qga/commands-bsd.c | 121 +++++++++++++++++++++++++++++++++++++++++++
> qga/commands-posix.c | 6 ++-
> qga/main.c | 11 +++-
> qga/meson.build | 3 ++
> 5 files changed, 140 insertions(+), 3 deletions(-)
> create mode 100644 qga/commands-bsd.c
>
> diff --git a/meson.build b/meson.build
> index c2adb7caf4..574cc1e91e 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -75,7 +75,7 @@ have_tools = get_option('tools') \
> .allowed()
> have_ga = get_option('guest_agent') \
> .disable_auto_if(not have_system and not have_tools) \
> - .require(targetos in ['sunos', 'linux', 'windows'],
> + .require(targetos in ['sunos', 'linux', 'windows', 'freebsd'],
> error_message: 'unsupported OS for QEMU guest agent') \
> .allowed()
> have_block = have_system or have_tools
> diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
> new file mode 100644
> index 0000000000..c1e3ed13e9
> --- /dev/null
> +++ b/qga/commands-bsd.c
> @@ -0,0 +1,121 @@
> +/*
> + * QEMU Guest Agent BSD-specific command implementations
> + *
> + * Copyright (c) Virtuozzo International GmbH.
> + *
> + * Authors:
> + * Alexander Ivanov <alexander.ivanov@virtuozzo.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 "qemu/osdep.h"
> +#include "qga-qapi-commands.h"
> +#include "qapi/qmp/qerror.h"
> +#include "qapi/error.h"
> +#include "qemu/queue.h"
> +#include "commands-common.h"
> +
> +#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
> +bool build_fs_mount_list(FsMountList *mounts, Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return false;
> +}
> +#endif
> +
> +#if defined(CONFIG_FSFREEZE)
> +int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints,
> + strList *mountpoints,
> + FsMountList mounts,
> + Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return 0;
> +}
> +
> +int qmp_guest_fsfreeze_do_thaw(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return 0;
> +}
> +#endif
> +
> +GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +GuestCpuStatsList *qmp_guest_get_cpustats(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +void qmp_guest_suspend_disk(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> +}
> +
> +void qmp_guest_suspend_ram(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> +}
> +
> +void qmp_guest_suspend_hybrid(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> +}
> +
> +GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error
> **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return -1;
> +}
> +
> +void qmp_guest_set_user_password(const char *username,
> + const char *password,
> + bool crypted,
> + Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> +}
> +
> +GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +GuestMemoryBlockResponseList *
> +qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> diff --git a/qga/commands-posix.c b/qga/commands-posix.c
> index 0bb8b9e2f3..3a1055d5c3 100644
> --- a/qga/commands-posix.c
> +++ b/qga/commands-posix.c
> @@ -31,6 +31,10 @@
> #include <utmpx.h>
> #endif
>
> +#ifdef __FreeBSD__
> +#undef HAVE_GETIFADDRS
> +#endif
>
Why? leave a comment in code please (or in commit message if it's solved
later in the series)
> +
> #ifdef HAVE_GETIFADDRS
> #include <arpa/inet.h>
> #include <sys/socket.h>
> @@ -763,7 +767,7 @@ void qmp_guest_file_flush(int64_t handle, Error **errp)
> }
> }
>
> -#if !defined(__linux__)
> +#if !(defined(__linux__) || defined(__FreeBSD__))
>
> void qmp_guest_suspend_disk(Error **errp)
> {
> diff --git a/qga/main.c b/qga/main.c
> index 5f1efa2333..22b3c0df11 100644
> --- a/qga/main.c
> +++ b/qga/main.c
> @@ -45,9 +45,14 @@
> #endif
>
> #ifndef _WIN32
> +#ifdef __FreeBSD__
> +#define QGA_VIRTIO_PATH_DEFAULT "/dev/vtcon/org.qemu.guest_agent.0"
> +#define QGA_SERIAL_PATH_DEFAULT "/dev/vtcon/org.qemu.guest_agent.0"
>
The same path for serial and virtio?
> +#else /* __FreeBSD__ */
> #define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent.0"
> -#define QGA_STATE_RELATIVE_DIR "run"
> #define QGA_SERIAL_PATH_DEFAULT "/dev/ttyS0"
> +#endif /* __FreeBSD__ */
> +#define QGA_STATE_RELATIVE_DIR "run"
> #else
> #define QGA_VIRTIO_PATH_DEFAULT "\\\\.\\Global\\org.qemu.guest_agent.0"
> #define QGA_STATE_RELATIVE_DIR "qemu-ga"
> @@ -1475,7 +1480,11 @@ int main(int argc, char **argv)
> }
>
> if (config->method == NULL) {
> +#ifdef CONFIG_BSD
> + config->method = g_strdup("isa-serial");
>
Why is isa serial the default ?
> +#else
> config->method = g_strdup("virtio-serial");
> +#endif
> }
>
> socket_activation = check_socket_activation();
> diff --git a/qga/meson.build b/qga/meson.build
> index 409f49a000..456ba4c29f 100644
> --- a/qga/meson.build
> +++ b/qga/meson.build
> @@ -75,6 +75,9 @@ qga_ss.add(when: 'CONFIG_POSIX', if_true: files(
> qga_ss.add(when: 'CONFIG_LINUX', if_true: files(
> 'commands-linux.c',
> ))
> +qga_ss.add(when: 'CONFIG_BSD', if_true: files(
> + 'commands-bsd.c',
> +))
> qga_ss.add(when: 'CONFIG_WIN32', if_true: files(
> 'channel-win32.c',
> 'commands-win32.c',
> --
> 2.34.1
>
>
>
otherwise, lgtm
--
Marc-André Lureau
[-- Attachment #2: Type: text/html, Size: 9118 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 1/5] qga: Move Linux-specific commands code to separate file
2022-09-22 13:19 ` [PATCH 1/5] qga: Move Linux-specific commands code to separate file Alexander Ivanov
@ 2022-09-23 7:59 ` Marc-André Lureau
2022-09-23 8:51 ` Alexander Ivanov
0 siblings, 1 reply; 14+ messages in thread
From: Marc-André Lureau @ 2022-09-23 7:59 UTC (permalink / raw)
To: Alexander Ivanov; +Cc: qemu-devel, den, michael.roth, kkostiuk
[-- Attachment #1: Type: text/plain, Size: 164108 bytes --]
Hi
On Thu, Sep 22, 2022 at 4:33 PM Alexander Ivanov <
alexander.ivanov@virtuozzo.com> wrote:
> In the next patches we are going to add FreeBSD support for QEMU Guest
> Agent. In the result, code in commands-posix.c will be too cumbersome.
> Move Linux-specific code to a separate file keeping common POSIX code in
> commands-posix.c.
>
> Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
>
lgtm, but moving 2000 lines in one patch is a bit hard to review.. Maybe
you can split this patch for the various moved commands? Although if enough
maintainers ack this patch, it's fine for me.
---
> qga/commands-common.h | 34 +
> qga/commands-linux.c | 2242 ++++++++++++++++++++++++++++++++++++
> qga/commands-posix.c | 2558 +++--------------------------------------
> qga/meson.build | 3 +
> 4 files changed, 2447 insertions(+), 2390 deletions(-)
> create mode 100644 qga/commands-linux.c
>
> diff --git a/qga/commands-common.h b/qga/commands-common.h
> index d0e4a9696f..aa0472ea4c 100644
> --- a/qga/commands-common.h
> +++ b/qga/commands-common.h
> @@ -29,4 +29,38 @@ GuestFileRead *guest_file_read_unsafe(GuestFileHandle
> *gfh,
> */
> char *qga_get_host_name(Error **errp);
>
> +void ga_wait_child(pid_t pid, int *status, Error **errp);
> +
> +#if defined(__linux__)
> +#include <linux/fs.h>
> +#ifdef FIFREEZE
> +#define CONFIG_FSFREEZE
> #endif
> +#ifdef FITRIM
> +#define CONFIG_FSTRIM
> +#endif
> +#endif /* __linux__*/
> +
> +#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
> +typedef struct FsMount {
> + char *dirname;
> + char *devtype;
> + unsigned int devmajor, devminor;
> + QTAILQ_ENTRY(FsMount) next;
> +} FsMount;
> +
> +typedef QTAILQ_HEAD(FsMountList, FsMount) FsMountList;
> +
> +bool build_fs_mount_list(FsMountList *mounts, Error **errp);
> +void free_fs_mount_list(FsMountList *mounts);
> +#endif
> +
> +#if defined(CONFIG_FSFREEZE)
> +int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints,
> + strList *mountpoints,
> + FsMountList mounts,
> + Error **errp);
> +int qmp_guest_fsfreeze_do_thaw(Error **errp);
> +#endif
> +
> +#endif /* QGA_COMMANDS_COMMON_H */
> diff --git a/qga/commands-linux.c b/qga/commands-linux.c
> new file mode 100644
> index 0000000000..615e9a0027
> --- /dev/null
> +++ b/qga/commands-linux.c
> @@ -0,0 +1,2242 @@
> +/*
> + * QEMU Guest Agent Linux-specific command implementations
> + *
> + * Copyright IBM Corp. 2011
> + *
> + * Authors:
> + * Michael Roth <mdroth@linux.vnet.ibm.com>
> + * Michal Privoznik <mprivozn@redhat.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 "qemu/osdep.h"
> +#include <sys/ioctl.h>
> +#include "guest-agent-core.h"
> +#include "qga-qapi-commands.h"
> +#include "qapi/qmp/qerror.h"
> +#include "qapi/error.h"
> +#include "qemu/queue.h"
> +#include "qemu/base64.h"
> +#include "commands-common.h"
> +#include "block/nvme.h"
> +#include "cutils.h"
> +
> +#include <mntent.h>
> +#include <sys/statvfs.h>
> +#include <linux/nvme_ioctl.h>
> +
> +#ifdef CONFIG_LIBUDEV
> +#include <libudev.h>
> +#endif
> +
> +#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
> +static int dev_major_minor(const char *devpath,
> + unsigned int *devmajor, unsigned int *devminor)
> +{
> + struct stat st;
> +
> + *devmajor = 0;
> + *devminor = 0;
> +
> + if (stat(devpath, &st) < 0) {
> + slog("failed to stat device file '%s': %s", devpath,
> strerror(errno));
> + return -1;
> + }
> + if (S_ISDIR(st.st_mode)) {
> + /* It is bind mount */
> + return -2;
> + }
> + if (S_ISBLK(st.st_mode)) {
> + *devmajor = major(st.st_rdev);
> + *devminor = minor(st.st_rdev);
> + return 0;
> + }
> + return -1;
> +}
> +
> +/*
> + * Walk the mount table and build a list of local file systems
> + */
> +static bool build_fs_mount_list_from_mtab(FsMountList *mounts, Error
> **errp)
> +{
> + struct mntent *ment;
> + FsMount *mount;
> + char const *mtab = "/proc/self/mounts";
> + FILE *fp;
> + unsigned int devmajor, devminor;
> +
> + fp = setmntent(mtab, "r");
> + if (!fp) {
> + error_setg(errp, "failed to open mtab file: '%s'", mtab);
> + return false;
> + }
> +
> + while ((ment = getmntent(fp))) {
> + /*
> + * An entry which device name doesn't start with a '/' is
> + * either a dummy file system or a network file system.
> + * Add special handling for smbfs and cifs as is done by
> + * coreutils as well.
> + */
> + if ((ment->mnt_fsname[0] != '/') ||
> + (strcmp(ment->mnt_type, "smbfs") == 0) ||
> + (strcmp(ment->mnt_type, "cifs") == 0)) {
> + continue;
> + }
> + if (dev_major_minor(ment->mnt_fsname, &devmajor, &devminor) ==
> -2) {
> + /* Skip bind mounts */
> + continue;
> + }
> +
> + mount = g_new0(FsMount, 1);
> + mount->dirname = g_strdup(ment->mnt_dir);
> + mount->devtype = g_strdup(ment->mnt_type);
> + mount->devmajor = devmajor;
> + mount->devminor = devminor;
> +
> + QTAILQ_INSERT_TAIL(mounts, mount, next);
> + }
> +
> + endmntent(fp);
> + return true;
> +}
> +
> +static void decode_mntname(char *name, int len)
> +{
> + int i, j = 0;
> + for (i = 0; i <= len; i++) {
> + if (name[i] != '\\') {
> + name[j++] = name[i];
> + } else if (name[i + 1] == '\\') {
> + name[j++] = '\\';
> + i++;
> + } else if (name[i + 1] >= '0' && name[i + 1] <= '3' &&
> + name[i + 2] >= '0' && name[i + 2] <= '7' &&
> + name[i + 3] >= '0' && name[i + 3] <= '7') {
> + name[j++] = (name[i + 1] - '0') * 64 +
> + (name[i + 2] - '0') * 8 +
> + (name[i + 3] - '0');
> + i += 3;
> + } else {
> + name[j++] = name[i];
> + }
> + }
> +}
> +
> +bool build_fs_mount_list(FsMountList *mounts, Error **errp)
> +{
> + FsMount *mount;
> + char const *mountinfo = "/proc/self/mountinfo";
> + FILE *fp;
> + char *line = NULL, *dash;
> + size_t n;
> + char check;
> + unsigned int devmajor, devminor;
> + int ret, dir_s, dir_e, type_s, type_e, dev_s, dev_e;
> +
> + fp = fopen(mountinfo, "r");
> + if (!fp) {
> + return build_fs_mount_list_from_mtab(mounts, errp);
> + }
> +
> + while (getline(&line, &n, fp) != -1) {
> + ret = sscanf(line, "%*u %*u %u:%u %*s %n%*s%n%c",
> + &devmajor, &devminor, &dir_s, &dir_e, &check);
> + if (ret < 3) {
> + continue;
> + }
> + dash = strstr(line + dir_e, " - ");
> + if (!dash) {
> + continue;
> + }
> + ret = sscanf(dash, " - %n%*s%n %n%*s%n%c",
> + &type_s, &type_e, &dev_s, &dev_e, &check);
> + if (ret < 1) {
> + continue;
> + }
> + line[dir_e] = 0;
> + dash[type_e] = 0;
> + dash[dev_e] = 0;
> + decode_mntname(line + dir_s, dir_e - dir_s);
> + decode_mntname(dash + dev_s, dev_e - dev_s);
> + if (devmajor == 0) {
> + /* btrfs reports major number = 0 */
> + if (strcmp("btrfs", dash + type_s) != 0 ||
> + dev_major_minor(dash + dev_s, &devmajor, &devminor) < 0) {
> + continue;
> + }
> + }
> +
> + mount = g_new0(FsMount, 1);
> + mount->dirname = g_strdup(line + dir_s);
> + mount->devtype = g_strdup(dash + type_s);
> + mount->devmajor = devmajor;
> + mount->devminor = devminor;
> +
> + QTAILQ_INSERT_TAIL(mounts, mount, next);
> + }
> + free(line);
> +
> + fclose(fp);
> + return true;
> +}
> +#endif
> +
> +#if defined(CONFIG_FSFREEZE)
> +
> +static char *get_pci_driver(char const *syspath, int pathlen, Error
> **errp)
> +{
> + char *path;
> + char *dpath;
> + char *driver = NULL;
> + char buf[PATH_MAX];
> + ssize_t len;
> +
> + path = g_strndup(syspath, pathlen);
> + dpath = g_strdup_printf("%s/driver", path);
> + len = readlink(dpath, buf, sizeof(buf) - 1);
> + if (len != -1) {
> + buf[len] = 0;
> + driver = g_path_get_basename(buf);
> + }
> + g_free(dpath);
> + g_free(path);
> + return driver;
> +}
> +
> +static int compare_uint(const void *_a, const void *_b)
> +{
> + unsigned int a = *(unsigned int *)_a;
> + unsigned int b = *(unsigned int *)_b;
> +
> + return a < b ? -1 : a > b ? 1 : 0;
> +}
> +
> +/* Walk the specified sysfs and build a sorted list of host or ata
> numbers */
> +static int build_hosts(char const *syspath, char const *host, bool ata,
> + unsigned int *hosts, int hosts_max, Error **errp)
> +{
> + char *path;
> + DIR *dir;
> + struct dirent *entry;
> + int i = 0;
> +
> + path = g_strndup(syspath, host - syspath);
> + dir = opendir(path);
> + if (!dir) {
> + error_setg_errno(errp, errno, "opendir(\"%s\")", path);
> + g_free(path);
> + return -1;
> + }
> +
> + while (i < hosts_max) {
> + entry = readdir(dir);
> + if (!entry) {
> + break;
> + }
> + if (ata && sscanf(entry->d_name, "ata%d", hosts + i) == 1) {
> + ++i;
> + } else if (!ata && sscanf(entry->d_name, "host%d", hosts + i) ==
> 1) {
> + ++i;
> + }
> + }
> +
> + qsort(hosts, i, sizeof(hosts[0]), compare_uint);
> +
> + g_free(path);
> + closedir(dir);
> + return i;
> +}
> +
> +/*
> + * Store disk device info for devices on the PCI bus.
> + * Returns true if information has been stored, or false for failure.
> + */
> +static bool build_guest_fsinfo_for_pci_dev(char const *syspath,
> + GuestDiskAddress *disk,
> + Error **errp)
> +{
> + unsigned int pci[4], host, hosts[8], tgt[3];
> + int i, nhosts = 0, pcilen;
> + GuestPCIAddress *pciaddr = disk->pci_controller;
> + bool has_ata = false, has_host = false, has_tgt = false;
> + char *p, *q, *driver = NULL;
> + bool ret = false;
> +
> + p = strstr(syspath, "/devices/pci");
> + if (!p || sscanf(p + 12, "%*x:%*x/%x:%x:%x.%x%n",
> + pci, pci + 1, pci + 2, pci + 3, &pcilen) < 4) {
> + g_debug("only pci device is supported: sysfs path '%s'", syspath);
> + return false;
> + }
> +
> + p += 12 + pcilen;
> + while (true) {
> + driver = get_pci_driver(syspath, p - syspath, errp);
> + if (driver && (g_str_equal(driver, "ata_piix") ||
> + g_str_equal(driver, "sym53c8xx") ||
> + g_str_equal(driver, "virtio-pci") ||
> + g_str_equal(driver, "ahci") ||
> + g_str_equal(driver, "nvme"))) {
> + break;
> + }
> +
> + g_free(driver);
> + if (sscanf(p, "/%x:%x:%x.%x%n",
> + pci, pci + 1, pci + 2, pci + 3, &pcilen) == 4) {
> + p += pcilen;
> + continue;
> + }
> +
> + g_debug("unsupported driver or sysfs path '%s'", syspath);
> + return false;
> + }
> +
> + p = strstr(syspath, "/target");
> + if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u",
> + tgt, tgt + 1, tgt + 2) == 3) {
> + has_tgt = true;
> + }
> +
> + p = strstr(syspath, "/ata");
> + if (p) {
> + q = p + 4;
> + has_ata = true;
> + } else {
> + p = strstr(syspath, "/host");
> + q = p + 5;
> + }
> + if (p && sscanf(q, "%u", &host) == 1) {
> + has_host = true;
> + nhosts = build_hosts(syspath, p, has_ata, hosts,
> + ARRAY_SIZE(hosts), errp);
> + if (nhosts < 0) {
> + goto cleanup;
> + }
> + }
> +
> + pciaddr->domain = pci[0];
> + pciaddr->bus = pci[1];
> + pciaddr->slot = pci[2];
> + pciaddr->function = pci[3];
> +
> + if (strcmp(driver, "ata_piix") == 0) {
> + /* a host per ide bus, target*:0:<unit>:0 */
> + if (!has_host || !has_tgt) {
> + g_debug("invalid sysfs path '%s' (driver '%s')", syspath,
> driver);
> + goto cleanup;
> + }
> + for (i = 0; i < nhosts; i++) {
> + if (host == hosts[i]) {
> + disk->bus_type = GUEST_DISK_BUS_TYPE_IDE;
> + disk->bus = i;
> + disk->unit = tgt[1];
> + break;
> + }
> + }
> + if (i >= nhosts) {
> + g_debug("no host for '%s' (driver '%s')", syspath, driver);
> + goto cleanup;
> + }
> + } else if (strcmp(driver, "sym53c8xx") == 0) {
> + /* scsi(LSI Logic): target*:0:<unit>:0 */
> + if (!has_tgt) {
> + g_debug("invalid sysfs path '%s' (driver '%s')", syspath,
> driver);
> + goto cleanup;
> + }
> + disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
> + disk->unit = tgt[1];
> + } else if (strcmp(driver, "virtio-pci") == 0) {
> + if (has_tgt) {
> + /* virtio-scsi: target*:0:0:<unit> */
> + disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
> + disk->unit = tgt[2];
> + } else {
> + /* virtio-blk: 1 disk per 1 device */
> + disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
> + }
> + } else if (strcmp(driver, "ahci") == 0) {
> + /* ahci: 1 host per 1 unit */
> + if (!has_host || !has_tgt) {
> + g_debug("invalid sysfs path '%s' (driver '%s')", syspath,
> driver);
> + goto cleanup;
> + }
> + for (i = 0; i < nhosts; i++) {
> + if (host == hosts[i]) {
> + disk->unit = i;
> + disk->bus_type = GUEST_DISK_BUS_TYPE_SATA;
> + break;
> + }
> + }
> + if (i >= nhosts) {
> + g_debug("no host for '%s' (driver '%s')", syspath, driver);
> + goto cleanup;
> + }
> + } else if (strcmp(driver, "nvme") == 0) {
> + disk->bus_type = GUEST_DISK_BUS_TYPE_NVME;
> + } else {
> + g_debug("unknown driver '%s' (sysfs path '%s')", driver, syspath);
> + goto cleanup;
> + }
> +
> + ret = true;
> +
> +cleanup:
> + g_free(driver);
> + return ret;
> +}
> +
> +/*
> + * Store disk device info for non-PCI virtio devices (for example s390x
> + * channel I/O devices). Returns true if information has been stored, or
> + * false for failure.
> + */
> +static bool build_guest_fsinfo_for_nonpci_virtio(char const *syspath,
> + GuestDiskAddress *disk,
> + Error **errp)
> +{
> + unsigned int tgt[3];
> + char *p;
> +
> + if (!strstr(syspath, "/virtio") || !strstr(syspath, "/block")) {
> + g_debug("Unsupported virtio device '%s'", syspath);
> + return false;
> + }
> +
> + p = strstr(syspath, "/target");
> + if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u",
> + &tgt[0], &tgt[1], &tgt[2]) == 3) {
> + /* virtio-scsi: target*:0:<target>:<unit> */
> + disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
> + disk->bus = tgt[0];
> + disk->target = tgt[1];
> + disk->unit = tgt[2];
> + } else {
> + /* virtio-blk: 1 disk per 1 device */
> + disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
> + }
> +
> + return true;
> +}
> +
> +/*
> + * Store disk device info for CCW devices (s390x channel I/O devices).
> + * Returns true if information has been stored, or false for failure.
> + */
> +static bool build_guest_fsinfo_for_ccw_dev(char const *syspath,
> + GuestDiskAddress *disk,
> + Error **errp)
> +{
> + unsigned int cssid, ssid, subchno, devno;
> + char *p;
> +
> + p = strstr(syspath, "/devices/css");
> + if (!p || sscanf(p + 12, "%*x/%x.%x.%x/%*x.%*x.%x/",
> + &cssid, &ssid, &subchno, &devno) < 4) {
> + g_debug("could not parse ccw device sysfs path: %s", syspath);
> + return false;
> + }
> +
> + disk->has_ccw_address = true;
> + disk->ccw_address = g_new0(GuestCCWAddress, 1);
> + disk->ccw_address->cssid = cssid;
> + disk->ccw_address->ssid = ssid;
> + disk->ccw_address->subchno = subchno;
> + disk->ccw_address->devno = devno;
> +
> + if (strstr(p, "/virtio")) {
> + build_guest_fsinfo_for_nonpci_virtio(syspath, disk, errp);
> + }
> +
> + return true;
> +}
> +
> +/* Store disk device info specified by @sysfs into @fs */
> +static void build_guest_fsinfo_for_real_device(char const *syspath,
> + GuestFilesystemInfo *fs,
> + Error **errp)
> +{
> + GuestDiskAddress *disk;
> + GuestPCIAddress *pciaddr;
> + bool has_hwinf;
> +#ifdef CONFIG_LIBUDEV
> + struct udev *udev = NULL;
> + struct udev_device *udevice = NULL;
> +#endif
> +
> + pciaddr = g_new0(GuestPCIAddress, 1);
> + pciaddr->domain = -1; /* -1 means field is
> invalid */
> + pciaddr->bus = -1;
> + pciaddr->slot = -1;
> + pciaddr->function = -1;
> +
> + disk = g_new0(GuestDiskAddress, 1);
> + disk->pci_controller = pciaddr;
> + disk->bus_type = GUEST_DISK_BUS_TYPE_UNKNOWN;
> +
> +#ifdef CONFIG_LIBUDEV
> + udev = udev_new();
> + udevice = udev_device_new_from_syspath(udev, syspath);
> + if (udev == NULL || udevice == NULL) {
> + g_debug("failed to query udev");
> + } else {
> + const char *devnode, *serial;
> + devnode = udev_device_get_devnode(udevice);
> + if (devnode != NULL) {
> + disk->dev = g_strdup(devnode);
> + disk->has_dev = true;
> + }
> + serial = udev_device_get_property_value(udevice, "ID_SERIAL");
> + if (serial != NULL && *serial != 0) {
> + disk->serial = g_strdup(serial);
> + disk->has_serial = true;
> + }
> + }
> +
> + udev_unref(udev);
> + udev_device_unref(udevice);
> +#endif
> +
> + if (strstr(syspath, "/devices/pci")) {
> + has_hwinf = build_guest_fsinfo_for_pci_dev(syspath, disk, errp);
> + } else if (strstr(syspath, "/devices/css")) {
> + has_hwinf = build_guest_fsinfo_for_ccw_dev(syspath, disk, errp);
> + } else if (strstr(syspath, "/virtio")) {
> + has_hwinf = build_guest_fsinfo_for_nonpci_virtio(syspath, disk,
> errp);
> + } else {
> + g_debug("Unsupported device type for '%s'", syspath);
> + has_hwinf = false;
> + }
> +
> + if (has_hwinf || disk->has_dev || disk->has_serial) {
> + QAPI_LIST_PREPEND(fs->disk, disk);
> + } else {
> + qapi_free_GuestDiskAddress(disk);
> + }
> +}
> +
> +static void build_guest_fsinfo_for_device(char const *devpath,
> + GuestFilesystemInfo *fs,
> + Error **errp);
> +
> +/* Store a list of slave devices of virtual volume specified by @syspath
> into
> + * @fs */
> +static void build_guest_fsinfo_for_virtual_device(char const *syspath,
> + GuestFilesystemInfo *fs,
> + Error **errp)
> +{
> + Error *err = NULL;
> + DIR *dir;
> + char *dirpath;
> + struct dirent *entry;
> +
> + dirpath = g_strdup_printf("%s/slaves", syspath);
> + dir = opendir(dirpath);
> + if (!dir) {
> + if (errno != ENOENT) {
> + error_setg_errno(errp, errno, "opendir(\"%s\")", dirpath);
> + }
> + g_free(dirpath);
> + return;
> + }
> +
> + for (;;) {
> + errno = 0;
> + entry = readdir(dir);
> + if (entry == NULL) {
> + if (errno) {
> + error_setg_errno(errp, errno, "readdir(\"%s\")", dirpath);
> + }
> + break;
> + }
> +
> + if (entry->d_type == DT_LNK) {
> + char *path;
> +
> + g_debug(" slave device '%s'", entry->d_name);
> + path = g_strdup_printf("%s/slaves/%s", syspath,
> entry->d_name);
> + build_guest_fsinfo_for_device(path, fs, &err);
> + g_free(path);
> +
> + if (err) {
> + error_propagate(errp, err);
> + break;
> + }
> + }
> + }
> +
> + g_free(dirpath);
> + closedir(dir);
> +}
> +
> +static bool is_disk_virtual(const char *devpath, Error **errp)
> +{
> + g_autofree char *syspath = realpath(devpath, NULL);
> +
> + if (!syspath) {
> + error_setg_errno(errp, errno, "realpath(\"%s\")", devpath);
> + return false;
> + }
> + return strstr(syspath, "/devices/virtual/block/") != NULL;
> +}
> +
> +/* Dispatch to functions for virtual/real device */
> +static void build_guest_fsinfo_for_device(char const *devpath,
> + GuestFilesystemInfo *fs,
> + Error **errp)
> +{
> + ERRP_GUARD();
> + g_autofree char *syspath = NULL;
> + bool is_virtual = false;
> +
> + syspath = realpath(devpath, NULL);
> + if (!syspath) {
> + if (errno != ENOENT) {
> + error_setg_errno(errp, errno, "realpath(\"%s\")", devpath);
> + return;
> + }
> +
> + /* ENOENT: This devpath may not exist because of container config
> */
> + if (!fs->name) {
> + fs->name = g_path_get_basename(devpath);
> + }
> + return;
> + }
> +
> + if (!fs->name) {
> + fs->name = g_path_get_basename(syspath);
> + }
> +
> + g_debug(" parse sysfs path '%s'", syspath);
> + is_virtual = is_disk_virtual(syspath, errp);
> + if (*errp != NULL) {
> + return;
> + }
> + if (is_virtual) {
> + build_guest_fsinfo_for_virtual_device(syspath, fs, errp);
> + } else {
> + build_guest_fsinfo_for_real_device(syspath, fs, errp);
> + }
> +}
> +
> +#ifdef CONFIG_LIBUDEV
> +
> +/*
> + * Wrapper around build_guest_fsinfo_for_device() for getting just
> + * the disk address.
> + */
> +static GuestDiskAddress *get_disk_address(const char *syspath, Error
> **errp)
> +{
> + g_autoptr(GuestFilesystemInfo) fs = NULL;
> +
> + fs = g_new0(GuestFilesystemInfo, 1);
> + build_guest_fsinfo_for_device(syspath, fs, errp);
> + if (fs->disk != NULL) {
> + return g_steal_pointer(&fs->disk->value);
> + }
> + return NULL;
> +}
> +
> +static char *get_alias_for_syspath(const char *syspath)
> +{
> + struct udev *udev = NULL;
> + struct udev_device *udevice = NULL;
> + char *ret = NULL;
> +
> + udev = udev_new();
> + if (udev == NULL) {
> + g_debug("failed to query udev");
> + goto out;
> + }
> + udevice = udev_device_new_from_syspath(udev, syspath);
> + if (udevice == NULL) {
> + g_debug("failed to query udev for path: %s", syspath);
> + goto out;
> + } else {
> + const char *alias = udev_device_get_property_value(
> + udevice, "DM_NAME");
> + /*
> + * NULL means there was an error and empty string means there is
> no
> + * alias. In case of no alias we return NULL instead of empty
> string.
> + */
> + if (alias == NULL) {
> + g_debug("failed to query udev for device alias for: %s",
> + syspath);
> + } else if (*alias != 0) {
> + ret = g_strdup(alias);
> + }
> + }
> +
> +out:
> + udev_unref(udev);
> + udev_device_unref(udevice);
> + return ret;
> +}
> +
> +static char *get_device_for_syspath(const char *syspath)
> +{
> + struct udev *udev = NULL;
> + struct udev_device *udevice = NULL;
> + char *ret = NULL;
> +
> + udev = udev_new();
> + if (udev == NULL) {
> + g_debug("failed to query udev");
> + goto out;
> + }
> + udevice = udev_device_new_from_syspath(udev, syspath);
> + if (udevice == NULL) {
> + g_debug("failed to query udev for path: %s", syspath);
> + goto out;
> + } else {
> + ret = g_strdup(udev_device_get_devnode(udevice));
> + }
> +
> +out:
> + udev_unref(udev);
> + udev_device_unref(udevice);
> + return ret;
> +}
> +
> +static void get_disk_deps(const char *disk_dir, GuestDiskInfo *disk)
> +{
> + g_autofree char *deps_dir = NULL;
> + const gchar *dep;
> + GDir *dp_deps = NULL;
> +
> + /* List dependent disks */
> + deps_dir = g_strdup_printf("%s/slaves", disk_dir);
> + g_debug(" listing entries in: %s", deps_dir);
> + dp_deps = g_dir_open(deps_dir, 0, NULL);
> + if (dp_deps == NULL) {
> + g_debug("failed to list entries in %s", deps_dir);
> + return;
> + }
> + disk->has_dependencies = true;
> + while ((dep = g_dir_read_name(dp_deps)) != NULL) {
> + g_autofree char *dep_dir = NULL;
> + char *dev_name;
> +
> + /* Add dependent disks */
> + dep_dir = g_strdup_printf("%s/%s", deps_dir, dep);
> + dev_name = get_device_for_syspath(dep_dir);
> + if (dev_name != NULL) {
> + g_debug(" adding dependent device: %s", dev_name);
> + QAPI_LIST_PREPEND(disk->dependencies, dev_name);
> + }
> + }
> + g_dir_close(dp_deps);
> +}
> +
> +/*
> + * Detect partitions subdirectory, name is "<disk_name><number>" or
> + * "<disk_name>p<number>"
> + *
> + * @disk_name -- last component of /sys path (e.g. sda)
> + * @disk_dir -- sys path of the disk (e.g. /sys/block/sda)
> + * @disk_dev -- device node of the disk (e.g. /dev/sda)
> + */
> +static GuestDiskInfoList *get_disk_partitions(
> + GuestDiskInfoList *list,
> + const char *disk_name, const char *disk_dir,
> + const char *disk_dev)
> +{
> + GuestDiskInfoList *ret = list;
> + struct dirent *de_disk;
> + DIR *dp_disk = NULL;
> + size_t len = strlen(disk_name);
> +
> + dp_disk = opendir(disk_dir);
> + while ((de_disk = readdir(dp_disk)) != NULL) {
> + g_autofree char *partition_dir = NULL;
> + char *dev_name;
> + GuestDiskInfo *partition;
> +
> + if (!(de_disk->d_type & DT_DIR)) {
> + continue;
> + }
> +
> + if (!(strncmp(disk_name, de_disk->d_name, len) == 0 &&
> + ((*(de_disk->d_name + len) == 'p' &&
> + isdigit(*(de_disk->d_name + len + 1))) ||
> + isdigit(*(de_disk->d_name + len))))) {
> + continue;
> + }
> +
> + partition_dir = g_strdup_printf("%s/%s",
> + disk_dir, de_disk->d_name);
> + dev_name = get_device_for_syspath(partition_dir);
> + if (dev_name == NULL) {
> + g_debug("Failed to get device name for syspath: %s",
> + disk_dir);
> + continue;
> + }
> + partition = g_new0(GuestDiskInfo, 1);
> + partition->name = dev_name;
> + partition->partition = true;
> + partition->has_dependencies = true;
> + /* Add parent disk as dependent for easier tracking of hierarchy
> */
> + QAPI_LIST_PREPEND(partition->dependencies, g_strdup(disk_dev));
> +
> + QAPI_LIST_PREPEND(ret, partition);
> + }
> + closedir(dp_disk);
> +
> + return ret;
> +}
> +
> +static void get_nvme_smart(GuestDiskInfo *disk)
> +{
> + int fd;
> + GuestNVMeSmart *smart;
> + NvmeSmartLog log = {0};
> + struct nvme_admin_cmd cmd = {
> + .opcode = NVME_ADM_CMD_GET_LOG_PAGE,
> + .nsid = NVME_NSID_BROADCAST,
> + .addr = (uintptr_t)&log,
> + .data_len = sizeof(log),
> + .cdw10 = NVME_LOG_SMART_INFO | (1 << 15) /* RAE bit */
> + | (((sizeof(log) >> 2) - 1) << 16)
> + };
> +
> + fd = qga_open_cloexec(disk->name, O_RDONLY, 0);
> + if (fd == -1) {
> + g_debug("Failed to open device: %s: %s", disk->name,
> g_strerror(errno));
> + return;
> + }
> +
> + if (ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd)) {
> + g_debug("Failed to get smart: %s: %s", disk->name,
> g_strerror(errno));
> + close(fd);
> + return;
> + }
> +
> + disk->has_smart = true;
> + disk->smart = g_new0(GuestDiskSmart, 1);
> + disk->smart->type = GUEST_DISK_BUS_TYPE_NVME;
> +
> + smart = &disk->smart->u.nvme;
> + smart->critical_warning = log.critical_warning;
> + smart->temperature = lduw_le_p(&log.temperature); /* unaligned field
> */
> + smart->available_spare = log.available_spare;
> + smart->available_spare_threshold = log.available_spare_threshold;
> + smart->percentage_used = log.percentage_used;
> + smart->data_units_read_lo = le64_to_cpu(log.data_units_read[0]);
> + smart->data_units_read_hi = le64_to_cpu(log.data_units_read[1]);
> + smart->data_units_written_lo = le64_to_cpu(log.data_units_written[0]);
> + smart->data_units_written_hi = le64_to_cpu(log.data_units_written[1]);
> + smart->host_read_commands_lo = le64_to_cpu(log.host_read_commands[0]);
> + smart->host_read_commands_hi = le64_to_cpu(log.host_read_commands[1]);
> + smart->host_write_commands_lo =
> le64_to_cpu(log.host_write_commands[0]);
> + smart->host_write_commands_hi =
> le64_to_cpu(log.host_write_commands[1]);
> + smart->controller_busy_time_lo =
> le64_to_cpu(log.controller_busy_time[0]);
> + smart->controller_busy_time_hi =
> le64_to_cpu(log.controller_busy_time[1]);
> + smart->power_cycles_lo = le64_to_cpu(log.power_cycles[0]);
> + smart->power_cycles_hi = le64_to_cpu(log.power_cycles[1]);
> + smart->power_on_hours_lo = le64_to_cpu(log.power_on_hours[0]);
> + smart->power_on_hours_hi = le64_to_cpu(log.power_on_hours[1]);
> + smart->unsafe_shutdowns_lo = le64_to_cpu(log.unsafe_shutdowns[0]);
> + smart->unsafe_shutdowns_hi = le64_to_cpu(log.unsafe_shutdowns[1]);
> + smart->media_errors_lo = le64_to_cpu(log.media_errors[0]);
> + smart->media_errors_hi = le64_to_cpu(log.media_errors[1]);
> + smart->number_of_error_log_entries_lo =
> + le64_to_cpu(log.number_of_error_log_entries[0]);
> + smart->number_of_error_log_entries_hi =
> + le64_to_cpu(log.number_of_error_log_entries[1]);
> +
> + close(fd);
> +}
> +
> +static void get_disk_smart(GuestDiskInfo *disk)
> +{
> + if (disk->has_address
> + && (disk->address->bus_type == GUEST_DISK_BUS_TYPE_NVME)) {
> + get_nvme_smart(disk);
> + }
> +}
> +
> +GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
> +{
> + GuestDiskInfoList *ret = NULL;
> + GuestDiskInfo *disk;
> + DIR *dp = NULL;
> + struct dirent *de = NULL;
> +
> + g_debug("listing /sys/block directory");
> + dp = opendir("/sys/block");
> + if (dp == NULL) {
> + error_setg_errno(errp, errno, "Can't open directory
> \"/sys/block\"");
> + return NULL;
> + }
> + while ((de = readdir(dp)) != NULL) {
> + g_autofree char *disk_dir = NULL, *line = NULL,
> + *size_path = NULL;
> + char *dev_name;
> + Error *local_err = NULL;
> + if (de->d_type != DT_LNK) {
> + g_debug(" skipping entry: %s", de->d_name);
> + continue;
> + }
> +
> + /* Check size and skip zero-sized disks */
> + g_debug(" checking disk size");
> + size_path = g_strdup_printf("/sys/block/%s/size", de->d_name);
> + if (!g_file_get_contents(size_path, &line, NULL, NULL)) {
> + g_debug(" failed to read disk size");
> + continue;
> + }
> + if (g_strcmp0(line, "0\n") == 0) {
> + g_debug(" skipping zero-sized disk");
> + continue;
> + }
> +
> + g_debug(" adding %s", de->d_name);
> + disk_dir = g_strdup_printf("/sys/block/%s", de->d_name);
> + dev_name = get_device_for_syspath(disk_dir);
> + if (dev_name == NULL) {
> + g_debug("Failed to get device name for syspath: %s",
> + disk_dir);
> + continue;
> + }
> + disk = g_new0(GuestDiskInfo, 1);
> + disk->name = dev_name;
> + disk->partition = false;
> + disk->alias = get_alias_for_syspath(disk_dir);
> + disk->has_alias = (disk->alias != NULL);
> + QAPI_LIST_PREPEND(ret, disk);
> +
> + /* Get address for non-virtual devices */
> + bool is_virtual = is_disk_virtual(disk_dir, &local_err);
> + if (local_err != NULL) {
> + g_debug(" failed to check disk path, ignoring error: %s",
> + error_get_pretty(local_err));
> + error_free(local_err);
> + local_err = NULL;
> + /* Don't try to get the address */
> + is_virtual = true;
> + }
> + if (!is_virtual) {
> + disk->address = get_disk_address(disk_dir, &local_err);
> + if (local_err != NULL) {
> + g_debug(" failed to get device info, ignoring error: %s",
> + error_get_pretty(local_err));
> + error_free(local_err);
> + local_err = NULL;
> + } else if (disk->address != NULL) {
> + disk->has_address = true;
> + }
> + }
> +
> + get_disk_deps(disk_dir, disk);
> + get_disk_smart(disk);
> + ret = get_disk_partitions(ret, de->d_name, disk_dir, dev_name);
> + }
> +
> + closedir(dp);
> +
> + return ret;
> +}
> +
> +#else
> +
> +GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +#endif
> +
> +/* Return a list of the disk device(s)' info which @mount lies on */
> +static GuestFilesystemInfo *build_guest_fsinfo(struct FsMount *mount,
> + Error **errp)
> +{
> + GuestFilesystemInfo *fs = g_malloc0(sizeof(*fs));
> + struct statvfs buf;
> + unsigned long used, nonroot_total, fr_size;
> + char *devpath = g_strdup_printf("/sys/dev/block/%u:%u",
> + mount->devmajor, mount->devminor);
> +
> + fs->mountpoint = g_strdup(mount->dirname);
> + fs->type = g_strdup(mount->devtype);
> + build_guest_fsinfo_for_device(devpath, fs, errp);
> +
> + if (statvfs(fs->mountpoint, &buf) == 0) {
> + fr_size = buf.f_frsize;
> + used = buf.f_blocks - buf.f_bfree;
> + nonroot_total = used + buf.f_bavail;
> + fs->used_bytes = used * fr_size;
> + fs->total_bytes = nonroot_total * fr_size;
> +
> + fs->has_total_bytes = true;
> + fs->has_used_bytes = true;
> + }
> +
> + g_free(devpath);
> +
> + return fs;
> +}
> +
> +GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
> +{
> + FsMountList mounts;
> + struct FsMount *mount;
> + GuestFilesystemInfoList *ret = NULL;
> + Error *local_err = NULL;
> +
> + QTAILQ_INIT(&mounts);
> + if (!build_fs_mount_list(&mounts, &local_err)) {
> + error_propagate(errp, local_err);
> + return NULL;
> + }
> +
> + QTAILQ_FOREACH(mount, &mounts, next) {
> + g_debug("Building guest fsinfo for '%s'", mount->dirname);
> +
> + QAPI_LIST_PREPEND(ret, build_guest_fsinfo(mount, &local_err));
> + if (local_err) {
> + error_propagate(errp, local_err);
> + qapi_free_GuestFilesystemInfoList(ret);
> + ret = NULL;
> + break;
> + }
> + }
> +
> + free_fs_mount_list(&mounts);
> + return ret;
> +}
> +
> +/*
> + * Walk list of mounted file systems in the guest, and freeze the ones
> which
> + * are real local file systems.
> + */
> +int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints,
> + strList *mountpoints,
> + FsMountList mounts,
> + Error **errp)
> +{
> + struct FsMount *mount;
> + strList *list;
> + int fd, ret, i = 0;
> +
> + QTAILQ_FOREACH_REVERSE(mount, &mounts, next) {
> + /* To issue fsfreeze in the reverse order of mounts, check if the
> + * mount is listed in the list here */
> + if (has_mountpoints) {
> + for (list = mountpoints; list; list = list->next) {
> + if (strcmp(list->value, mount->dirname) == 0) {
> + break;
> + }
> + }
> + if (!list) {
> + continue;
> + }
> + }
> +
> + fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
> + if (fd == -1) {
> + error_setg_errno(errp, errno, "failed to open %s",
> mount->dirname);
> + return -1;
> + }
> +
> + /* we try to cull filesystems we know won't work in advance, but
> other
> + * filesystems may not implement fsfreeze for less obvious
> reasons.
> + * these will report EOPNOTSUPP. we simply ignore these when
> tallying
> + * the number of frozen filesystems.
> + * if a filesystem is mounted more than once (aka bind mount) a
> + * consecutive attempt to freeze an already frozen filesystem will
> + * return EBUSY.
> + *
> + * any other error means a failure to freeze a filesystem we
> + * expect to be freezable, so return an error in those cases
> + * and return system to thawed state.
> + */
> + ret = ioctl(fd, FIFREEZE);
> + if (ret == -1) {
> + if (errno != EOPNOTSUPP && errno != EBUSY) {
> + error_setg_errno(errp, errno, "failed to freeze %s",
> + mount->dirname);
> + close(fd);
> + return -1;
> + }
> + } else {
> + i++;
> + }
> + close(fd);
> + }
> + return i;
> +}
> +
> +int qmp_guest_fsfreeze_do_thaw(Error **errp)
> +{
> + int ret;
> + FsMountList mounts;
> + FsMount *mount;
> + int fd, i = 0, logged;
> + Error *local_err = NULL;
> +
> + QTAILQ_INIT(&mounts);
> + if (!build_fs_mount_list(&mounts, &local_err)) {
> + error_propagate(errp, local_err);
> + return -1;
> + }
> +
> + QTAILQ_FOREACH(mount, &mounts, next) {
> + logged = false;
> + fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
> + if (fd == -1) {
> + continue;
> + }
> + /* we have no way of knowing whether a filesystem was actually
> unfrozen
> + * as a result of a successful call to FITHAW, only that if an
> error
> + * was returned the filesystem was *not* unfrozen by that
> particular
> + * call.
> + *
> + * since multiple preceding FIFREEZEs require multiple calls to
> FITHAW
> + * to unfreeze, continuing issuing FITHAW until an error is
> returned,
> + * in which case either the filesystem is in an unfreezable
> state, or,
> + * more likely, it was thawed previously (and remains so
> afterward).
> + *
> + * also, since the most recent successful call is the one that did
> + * the actual unfreeze, we can use this to provide an accurate
> count
> + * of the number of filesystems unfrozen by guest-fsfreeze-thaw,
> which
> + * may * be useful for determining whether a filesystem was
> unfrozen
> + * during the freeze/thaw phase by a process other than qemu-ga.
> + */
> + do {
> + ret = ioctl(fd, FITHAW);
> + if (ret == 0 && !logged) {
> + i++;
> + logged = true;
> + }
> + } while (ret == 0);
> + close(fd);
> + }
> +
> + free_fs_mount_list(&mounts);
> +
> + return i;
> +}
> +#endif /* CONFIG_FSFREEZE */
> +
> +#if defined(CONFIG_FSTRIM)
> +/*
> + * Walk list of mounted file systems in the guest, and trim them.
> + */
> +GuestFilesystemTrimResponse *
> +qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
> +{
> + GuestFilesystemTrimResponse *response;
> + GuestFilesystemTrimResult *result;
> + int ret = 0;
> + FsMountList mounts;
> + struct FsMount *mount;
> + int fd;
> + struct fstrim_range r;
> +
> + slog("guest-fstrim called");
> +
> + QTAILQ_INIT(&mounts);
> + if (!build_fs_mount_list(&mounts, errp)) {
> + return NULL;
> + }
> +
> + response = g_malloc0(sizeof(*response));
> +
> + QTAILQ_FOREACH(mount, &mounts, next) {
> + result = g_malloc0(sizeof(*result));
> + result->path = g_strdup(mount->dirname);
> +
> + QAPI_LIST_PREPEND(response->paths, result);
> +
> + fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
> + if (fd == -1) {
> + result->error = g_strdup_printf("failed to open: %s",
> + strerror(errno));
> + result->has_error = true;
> + continue;
> + }
> +
> + /* We try to cull filesystems we know won't work in advance, but
> other
> + * filesystems may not implement fstrim for less obvious reasons.
> + * These will report EOPNOTSUPP; while in some other cases ENOTTY
> + * will be reported (e.g. CD-ROMs).
> + * Any other error means an unexpected error.
> + */
> + r.start = 0;
> + r.len = -1;
> + r.minlen = has_minimum ? minimum : 0;
> + ret = ioctl(fd, FITRIM, &r);
> + if (ret == -1) {
> + result->has_error = true;
> + if (errno == ENOTTY || errno == EOPNOTSUPP) {
> + result->error = g_strdup("trim not supported");
> + } else {
> + result->error = g_strdup_printf("failed to trim: %s",
> + strerror(errno));
> + }
> + close(fd);
> + continue;
> + }
> +
> + result->has_minimum = true;
> + result->minimum = r.minlen;
> + result->has_trimmed = true;
> + result->trimmed = r.len;
> + close(fd);
> + }
> +
> + free_fs_mount_list(&mounts);
> + return response;
> +}
> +#endif /* CONFIG_FSTRIM */
> +
> +
> +#define LINUX_SYS_STATE_FILE "/sys/power/state"
> +#define SUSPEND_SUPPORTED 0
> +#define SUSPEND_NOT_SUPPORTED 1
> +
> +typedef enum {
> + SUSPEND_MODE_DISK = 0,
> + SUSPEND_MODE_RAM = 1,
> + SUSPEND_MODE_HYBRID = 2,
> +} SuspendMode;
> +
> +/*
> + * Executes a command in a child process using g_spawn_sync,
> + * returning an int >= 0 representing the exit status of the
> + * process.
> + *
> + * If the program wasn't found in path, returns -1.
> + *
> + * If a problem happened when creating the child process,
> + * returns -1 and errp is set.
> + */
> +static int run_process_child(const char *command[], Error **errp)
> +{
> + int exit_status, spawn_flag;
> + GError *g_err = NULL;
> + bool success;
> +
> + spawn_flag = G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL |
> + G_SPAWN_STDERR_TO_DEV_NULL;
> +
> + success = g_spawn_sync(NULL, (char **)command, NULL, spawn_flag,
> + NULL, NULL, NULL, NULL,
> + &exit_status, &g_err);
> +
> + if (success) {
> + return WEXITSTATUS(exit_status);
> + }
> +
> + if (g_err && (g_err->code != G_SPAWN_ERROR_NOENT)) {
> + error_setg(errp, "failed to create child process, error '%s'",
> + g_err->message);
> + }
> +
> + g_error_free(g_err);
> + return -1;
> +}
> +
> +static bool systemd_supports_mode(SuspendMode mode, Error **errp)
> +{
> + const char *systemctl_args[3] = {"systemd-hibernate",
> "systemd-suspend",
> + "systemd-hybrid-sleep"};
> + const char *cmd[4] = {"systemctl", "status", systemctl_args[mode],
> NULL};
> + int status;
> +
> + status = run_process_child(cmd, errp);
> +
> + /*
> + * systemctl status uses LSB return codes so we can expect
> + * status > 0 and be ok. To assert if the guest has support
> + * for the selected suspend mode, status should be < 4. 4 is
> + * the code for unknown service status, the return value when
> + * the service does not exist. A common value is status = 3
> + * (program is not running).
> + */
> + if (status > 0 && status < 4) {
> + return true;
> + }
> +
> + return false;
> +}
> +
> +static void systemd_suspend(SuspendMode mode, Error **errp)
> +{
> + Error *local_err = NULL;
> + const char *systemctl_args[3] = {"hibernate", "suspend",
> "hybrid-sleep"};
> + const char *cmd[3] = {"systemctl", systemctl_args[mode], NULL};
> + int status;
> +
> + status = run_process_child(cmd, &local_err);
> +
> + if (status == 0) {
> + return;
> + }
> +
> + if ((status == -1) && !local_err) {
> + error_setg(errp, "the helper program 'systemctl %s' was not
> found",
> + systemctl_args[mode]);
> + return;
> + }
> +
> + if (local_err) {
> + error_propagate(errp, local_err);
> + } else {
> + error_setg(errp, "the helper program 'systemctl %s' returned an "
> + "unexpected exit status code (%d)",
> + systemctl_args[mode], status);
> + }
> +}
> +
> +static bool pmutils_supports_mode(SuspendMode mode, Error **errp)
> +{
> + Error *local_err = NULL;
> + const char *pmutils_args[3] = {"--hibernate", "--suspend",
> + "--suspend-hybrid"};
> + const char *cmd[3] = {"pm-is-supported", pmutils_args[mode], NULL};
> + int status;
> +
> + status = run_process_child(cmd, &local_err);
> +
> + if (status == SUSPEND_SUPPORTED) {
> + return true;
> + }
> +
> + if ((status == -1) && !local_err) {
> + return false;
> + }
> +
> + if (local_err) {
> + error_propagate(errp, local_err);
> + } else {
> + error_setg(errp,
> + "the helper program '%s' returned an unexpected exit"
> + " status code (%d)", "pm-is-supported", status);
> + }
> +
> + return false;
> +}
> +
> +static void pmutils_suspend(SuspendMode mode, Error **errp)
> +{
> + Error *local_err = NULL;
> + const char *pmutils_binaries[3] = {"pm-hibernate", "pm-suspend",
> + "pm-suspend-hybrid"};
> + const char *cmd[2] = {pmutils_binaries[mode], NULL};
> + int status;
> +
> + status = run_process_child(cmd, &local_err);
> +
> + if (status == 0) {
> + return;
> + }
> +
> + if ((status == -1) && !local_err) {
> + error_setg(errp, "the helper program '%s' was not found",
> + pmutils_binaries[mode]);
> + return;
> + }
> +
> + if (local_err) {
> + error_propagate(errp, local_err);
> + } else {
> + error_setg(errp,
> + "the helper program '%s' returned an unexpected exit"
> + " status code (%d)", pmutils_binaries[mode], status);
> + }
> +}
> +
> +static bool linux_sys_state_supports_mode(SuspendMode mode, Error **errp)
> +{
> + const char *sysfile_strs[3] = {"disk", "mem", NULL};
> + const char *sysfile_str = sysfile_strs[mode];
> + char buf[32]; /* hopefully big enough */
> + int fd;
> + ssize_t ret;
> +
> + if (!sysfile_str) {
> + error_setg(errp, "unknown guest suspend mode");
> + return false;
> + }
> +
> + fd = open(LINUX_SYS_STATE_FILE, O_RDONLY);
> + if (fd < 0) {
> + return false;
> + }
> +
> + ret = read(fd, buf, sizeof(buf) - 1);
> + close(fd);
> + if (ret <= 0) {
> + return false;
> + }
> + buf[ret] = '\0';
> +
> + if (strstr(buf, sysfile_str)) {
> + return true;
> + }
> + return false;
> +}
> +
> +static void linux_sys_state_suspend(SuspendMode mode, Error **errp)
> +{
> + Error *local_err = NULL;
> + const char *sysfile_strs[3] = {"disk", "mem", NULL};
> + const char *sysfile_str = sysfile_strs[mode];
> + pid_t pid;
> + int status;
> +
> + if (!sysfile_str) {
> + error_setg(errp, "unknown guest suspend mode");
> + return;
> + }
> +
> + pid = fork();
> + if (!pid) {
> + /* child */
> + int fd;
> +
> + setsid();
> + reopen_fd_to_null(0);
> + reopen_fd_to_null(1);
> + reopen_fd_to_null(2);
> +
> + fd = open(LINUX_SYS_STATE_FILE, O_WRONLY);
> + if (fd < 0) {
> + _exit(EXIT_FAILURE);
> + }
> +
> + if (write(fd, sysfile_str, strlen(sysfile_str)) < 0) {
> + _exit(EXIT_FAILURE);
> + }
> +
> + _exit(EXIT_SUCCESS);
> + } else if (pid < 0) {
> + error_setg_errno(errp, errno, "failed to create child process");
> + return;
> + }
> +
> + ga_wait_child(pid, &status, &local_err);
> + if (local_err) {
> + error_propagate(errp, local_err);
> + return;
> + }
> +
> + if (WEXITSTATUS(status)) {
> + error_setg(errp, "child process has failed to suspend");
> + }
> +
> +}
> +
> +static void guest_suspend(SuspendMode mode, Error **errp)
> +{
> + Error *local_err = NULL;
> + bool mode_supported = false;
> +
> + if (systemd_supports_mode(mode, &local_err)) {
> + mode_supported = true;
> + systemd_suspend(mode, &local_err);
> + }
> +
> + if (!local_err) {
> + return;
> + }
> +
> + error_free(local_err);
> + local_err = NULL;
> +
> + if (pmutils_supports_mode(mode, &local_err)) {
> + mode_supported = true;
> + pmutils_suspend(mode, &local_err);
> + }
> +
> + if (!local_err) {
> + return;
> + }
> +
> + error_free(local_err);
> + local_err = NULL;
> +
> + if (linux_sys_state_supports_mode(mode, &local_err)) {
> + mode_supported = true;
> + linux_sys_state_suspend(mode, &local_err);
> + }
> +
> + if (!mode_supported) {
> + error_free(local_err);
> + error_setg(errp,
> + "the requested suspend mode is not supported by the
> guest");
> + } else {
> + error_propagate(errp, local_err);
> + }
> +}
> +
> +void qmp_guest_suspend_disk(Error **errp)
> +{
> + guest_suspend(SUSPEND_MODE_DISK, errp);
> +}
> +
> +void qmp_guest_suspend_ram(Error **errp)
> +{
> + guest_suspend(SUSPEND_MODE_RAM, errp);
> +}
> +
> +void qmp_guest_suspend_hybrid(Error **errp)
> +{
> + guest_suspend(SUSPEND_MODE_HYBRID, errp);
> +}
> +
> +/* 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,
> + char *dirpath, Error **errp)
> +{
> + int fd;
> + int res;
> + int dirfd;
> + static const char fn[] = "online";
> +
> + dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
> + if (dirfd == -1) {
> + error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
> + return;
> + }
> +
> + 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);
> +}
> +
> +GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
> +{
> + GuestLogicalProcessorList *head, **tail;
> + const char *cpu_dir = "/sys/devices/system/cpu";
> + const gchar *line;
> + g_autoptr(GDir) cpu_gdir = NULL;
> + Error *local_err = NULL;
> +
> + head = NULL;
> + tail = &head;
> + cpu_gdir = g_dir_open(cpu_dir, 0, NULL);
> +
> + if (cpu_gdir == NULL) {
> + error_setg_errno(errp, errno, "failed to list entries: %s",
> cpu_dir);
> + return NULL;
> + }
> +
> + while (local_err == NULL && (line = g_dir_read_name(cpu_gdir)) !=
> NULL) {
> + GuestLogicalProcessor *vcpu;
> + int64_t id;
> + if (sscanf(line, "cpu%" PRId64, &id)) {
> + g_autofree char *path =
> g_strdup_printf("/sys/devices/system/cpu/"
> + "cpu%" PRId64 "/",
> id);
> + vcpu = g_malloc0(sizeof *vcpu);
> + vcpu->logical_id = id;
> + vcpu->has_can_offline = true; /* lolspeak ftw */
> + transfer_vcpu(vcpu, true, path, &local_err);
> + QAPI_LIST_APPEND(tail, vcpu);
> + }
> + }
> +
> + 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) {
> + char *path = g_strdup_printf("/sys/devices/system/cpu/cpu%"
> PRId64 "/",
> + vcpus->value->logical_id);
> +
> + transfer_vcpu(vcpus->value, false, path, &local_err);
> + g_free(path);
> + 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;
> +}
> +
> +void qmp_guest_set_user_password(const char *username,
> + const char *password,
> + bool crypted,
> + Error **errp)
> +{
> + Error *local_err = NULL;
> + char *passwd_path = NULL;
> + pid_t pid;
> + int status;
> + int datafd[2] = { -1, -1 };
> + char *rawpasswddata = NULL;
> + size_t rawpasswdlen;
> + char *chpasswddata = NULL;
> + size_t chpasswdlen;
> +
> + rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen,
> errp);
> + if (!rawpasswddata) {
> + return;
> + }
> + rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
> + rawpasswddata[rawpasswdlen] = '\0';
> +
> + if (strchr(rawpasswddata, '\n')) {
> + error_setg(errp, "forbidden characters in raw password");
> + goto out;
> + }
> +
> + if (strchr(username, '\n') ||
> + strchr(username, ':')) {
> + error_setg(errp, "forbidden characters in username");
> + goto out;
> + }
> +
> + chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata);
> + chpasswdlen = strlen(chpasswddata);
> +
> + passwd_path = g_find_program_in_path("chpasswd");
> +
> + if (!passwd_path) {
> + error_setg(errp, "cannot find 'passwd' program in PATH");
> + goto out;
> + }
> +
> + if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) {
> + error_setg(errp, "cannot create pipe FDs");
> + goto out;
> + }
> +
> + pid = fork();
> + if (pid == 0) {
> + close(datafd[1]);
> + /* child */
> + setsid();
> + dup2(datafd[0], 0);
> + reopen_fd_to_null(1);
> + reopen_fd_to_null(2);
> +
> + if (crypted) {
> + execl(passwd_path, "chpasswd", "-e", NULL);
> + } else {
> + execl(passwd_path, "chpasswd", NULL);
> + }
> + _exit(EXIT_FAILURE);
> + } else if (pid < 0) {
> + error_setg_errno(errp, errno, "failed to create child process");
> + goto out;
> + }
> + close(datafd[0]);
> + datafd[0] = -1;
> +
> + if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) !=
> chpasswdlen) {
> + error_setg_errno(errp, errno, "cannot write new account
> password");
> + goto out;
> + }
> + close(datafd[1]);
> + datafd[1] = -1;
> +
> + ga_wait_child(pid, &status, &local_err);
> + if (local_err) {
> + error_propagate(errp, local_err);
> + goto out;
> + }
> +
> + if (!WIFEXITED(status)) {
> + error_setg(errp, "child process has terminated abnormally");
> + goto out;
> + }
> +
> + if (WEXITSTATUS(status)) {
> + error_setg(errp, "child process has failed to set user password");
> + goto out;
> + }
> +
> +out:
> + g_free(chpasswddata);
> + g_free(rawpasswddata);
> + g_free(passwd_path);
> + if (datafd[0] != -1) {
> + close(datafd[0]);
> + }
> + if (datafd[1] != -1) {
> + close(datafd[1]);
> + }
> +}
> +
> +static void ga_read_sysfs_file(int dirfd, const char *pathname, char *buf,
> + int size, Error **errp)
> +{
> + int fd;
> + int res;
> +
> + errno = 0;
> + fd = openat(dirfd, pathname, O_RDONLY);
> + if (fd == -1) {
> + error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname);
> + return;
> + }
> +
> + res = pread(fd, buf, size, 0);
> + if (res == -1) {
> + error_setg_errno(errp, errno, "pread sysfs file \"%s\"",
> pathname);
> + } else if (res == 0) {
> + error_setg(errp, "pread sysfs file \"%s\": unexpected EOF",
> pathname);
> + }
> + close(fd);
> +}
> +
> +static void ga_write_sysfs_file(int dirfd, const char *pathname,
> + const char *buf, int size, Error **errp)
> +{
> + int fd;
> +
> + errno = 0;
> + fd = openat(dirfd, pathname, O_WRONLY);
> + if (fd == -1) {
> + error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname);
> + return;
> + }
> +
> + if (pwrite(fd, buf, size, 0) == -1) {
> + error_setg_errno(errp, errno, "pwrite sysfs file \"%s\"",
> pathname);
> + }
> +
> + close(fd);
> +}
> +
> +/* Transfer online/offline status between @mem_blk and the guest system.
> + *
> + * On input either @errp or *@errp must be NULL.
> + *
> + * In system-to-@mem_blk direction, the following @mem_blk fields are
> accessed:
> + * - R: mem_blk->phys_index
> + * - W: mem_blk->online
> + * - W: mem_blk->can_offline
> + *
> + * In @mem_blk-to-system direction, the following @mem_blk fields are
> accessed:
> + * - R: mem_blk->phys_index
> + * - R: mem_blk->online
> + *- R: mem_blk->can_offline
> + * Written members remain unmodified on error.
> + */
> +static void transfer_memory_block(GuestMemoryBlock *mem_blk, bool
> sys2memblk,
> + GuestMemoryBlockResponse *result,
> + Error **errp)
> +{
> + char *dirpath;
> + int dirfd;
> + char *status;
> + Error *local_err = NULL;
> +
> + if (!sys2memblk) {
> + DIR *dp;
> +
> + if (!result) {
> + error_setg(errp, "Internal error, 'result' should not be
> NULL");
> + return;
> + }
> + errno = 0;
> + dp = opendir("/sys/devices/system/memory/");
> + /* if there is no 'memory' directory in sysfs,
> + * we think this VM does not support online/offline memory block,
> + * any other solution?
> + */
> + if (!dp) {
> + if (errno == ENOENT) {
> + result->response =
> +
> GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
> + }
> + goto out1;
> + }
> + closedir(dp);
> + }
> +
> + dirpath = g_strdup_printf("/sys/devices/system/memory/memory%" PRId64
> "/",
> + mem_blk->phys_index);
> + dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
> + if (dirfd == -1) {
> + if (sys2memblk) {
> + error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
> + } else {
> + if (errno == ENOENT) {
> + result->response =
> GUEST_MEMORY_BLOCK_RESPONSE_TYPE_NOT_FOUND;
> + } else {
> + result->response =
> + GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
> + }
> + }
> + g_free(dirpath);
> + goto out1;
> + }
> + g_free(dirpath);
> +
> + status = g_malloc0(10);
> + ga_read_sysfs_file(dirfd, "state", status, 10, &local_err);
> + if (local_err) {
> + /* treat with sysfs file that not exist in old kernel */
> + if (errno == ENOENT) {
> + error_free(local_err);
> + if (sys2memblk) {
> + mem_blk->online = true;
> + mem_blk->can_offline = false;
> + } else if (!mem_blk->online) {
> + result->response =
> +
> GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
> + }
> + } else {
> + if (sys2memblk) {
> + error_propagate(errp, local_err);
> + } else {
> + error_free(local_err);
> + result->response =
> + GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
> + }
> + }
> + goto out2;
> + }
> +
> + if (sys2memblk) {
> + char removable = '0';
> +
> + mem_blk->online = (strncmp(status, "online", 6) == 0);
> +
> + ga_read_sysfs_file(dirfd, "removable", &removable, 1, &local_err);
> + if (local_err) {
> + /* if no 'removable' file, it doesn't support offline mem blk
> */
> + if (errno == ENOENT) {
> + error_free(local_err);
> + mem_blk->can_offline = false;
> + } else {
> + error_propagate(errp, local_err);
> + }
> + } else {
> + mem_blk->can_offline = (removable != '0');
> + }
> + } else {
> + if (mem_blk->online != (strncmp(status, "online", 6) == 0)) {
> + const char *new_state = mem_blk->online ? "online" :
> "offline";
> +
> + ga_write_sysfs_file(dirfd, "state", new_state,
> strlen(new_state),
> + &local_err);
> + if (local_err) {
> + error_free(local_err);
> + result->response =
> + GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
> + goto out2;
> + }
> +
> + result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_SUCCESS;
> + result->has_error_code = false;
> + } /* otherwise pretend successful re-(on|off)-lining */
> + }
> + g_free(status);
> + close(dirfd);
> + return;
> +
> +out2:
> + g_free(status);
> + close(dirfd);
> +out1:
> + if (!sys2memblk) {
> + result->has_error_code = true;
> + result->error_code = errno;
> + }
> +}
> +
> +GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
> +{
> + GuestMemoryBlockList *head, **tail;
> + Error *local_err = NULL;
> + struct dirent *de;
> + DIR *dp;
> +
> + head = NULL;
> + tail = &head;
> +
> + dp = opendir("/sys/devices/system/memory/");
> + if (!dp) {
> + /* it's ok if this happens to be a system that doesn't expose
> + * memory blocks via sysfs, but otherwise we should report
> + * an error
> + */
> + if (errno != ENOENT) {
> + error_setg_errno(errp, errno, "Can't open directory"
> + "\"/sys/devices/system/memory/\"");
> + }
> + return NULL;
> + }
> +
> + /* Note: the phys_index of memory block may be discontinuous,
> + * this is because a memblk is the unit of the Sparse Memory design,
> which
> + * allows discontinuous memory ranges (ex. NUMA), so here we should
> + * traverse the memory block directory.
> + */
> + while ((de = readdir(dp)) != NULL) {
> + GuestMemoryBlock *mem_blk;
> +
> + if ((strncmp(de->d_name, "memory", 6) != 0) ||
> + !(de->d_type & DT_DIR)) {
> + continue;
> + }
> +
> + mem_blk = g_malloc0(sizeof *mem_blk);
> + /* The d_name is "memoryXXX", phys_index is block id, same as
> XXX */
> + mem_blk->phys_index = strtoul(&de->d_name[6], NULL, 10);
> + mem_blk->has_can_offline = true; /* lolspeak ftw */
> + transfer_memory_block(mem_blk, true, NULL, &local_err);
> + if (local_err) {
> + break;
> + }
> +
> + QAPI_LIST_APPEND(tail, mem_blk);
> + }
> +
> + closedir(dp);
> + if (local_err == NULL) {
> + /* there's no guest with zero memory blocks */
> + if (head == NULL) {
> + error_setg(errp, "guest reported zero memory blocks!");
> + }
> + return head;
> + }
> +
> + qapi_free_GuestMemoryBlockList(head);
> + error_propagate(errp, local_err);
> + return NULL;
> +}
> +
> +GuestMemoryBlockResponseList *
> +qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp)
> +{
> + GuestMemoryBlockResponseList *head, **tail;
> + Error *local_err = NULL;
> +
> + head = NULL;
> + tail = &head;
> +
> + while (mem_blks != NULL) {
> + GuestMemoryBlockResponse *result;
> + GuestMemoryBlock *current_mem_blk = mem_blks->value;
> +
> + result = g_malloc0(sizeof(*result));
> + result->phys_index = current_mem_blk->phys_index;
> + transfer_memory_block(current_mem_blk, false, result, &local_err);
> + if (local_err) { /* should never happen */
> + goto err;
> + }
> +
> + QAPI_LIST_APPEND(tail, result);
> + mem_blks = mem_blks->next;
> + }
> +
> + return head;
> +err:
> + qapi_free_GuestMemoryBlockResponseList(head);
> + error_propagate(errp, local_err);
> + return NULL;
> +}
> +
> +GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
> +{
> + Error *local_err = NULL;
> + char *dirpath;
> + int dirfd;
> + char *buf;
> + GuestMemoryBlockInfo *info;
> +
> + dirpath = g_strdup_printf("/sys/devices/system/memory/");
> + dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
> + if (dirfd == -1) {
> + error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
> + g_free(dirpath);
> + return NULL;
> + }
> + g_free(dirpath);
> +
> + buf = g_malloc0(20);
> + ga_read_sysfs_file(dirfd, "block_size_bytes", buf, 20, &local_err);
> + close(dirfd);
> + if (local_err) {
> + g_free(buf);
> + error_propagate(errp, local_err);
> + return NULL;
> + }
> +
> + info = g_new0(GuestMemoryBlockInfo, 1);
> + info->size = strtol(buf, NULL, 16); /* the unit is bytes */
> +
> + g_free(buf);
> +
> + return info;
> +}
> +
> +#define MAX_NAME_LEN 128
> +static GuestDiskStatsInfoList *guest_get_diskstats(Error **errp)
> +{
> +#ifdef CONFIG_LINUX
> + GuestDiskStatsInfoList *head = NULL, **tail = &head;
> + const char *diskstats = "/proc/diskstats";
> + FILE *fp;
> + size_t n;
> + char *line = NULL;
> +
> + fp = fopen(diskstats, "r");
> + if (fp == NULL) {
> + error_setg_errno(errp, errno, "open(\"%s\")", diskstats);
> + return NULL;
> + }
> +
> + while (getline(&line, &n, fp) != -1) {
> + g_autofree GuestDiskStatsInfo *diskstatinfo = NULL;
> + g_autofree GuestDiskStats *diskstat = NULL;
> + char dev_name[MAX_NAME_LEN];
> + unsigned int ios_pgr, tot_ticks, rq_ticks, wr_ticks, dc_ticks,
> fl_ticks;
> + unsigned long rd_ios, rd_merges_or_rd_sec, rd_ticks_or_wr_sec,
> wr_ios;
> + unsigned long wr_merges, rd_sec_or_wr_ios, wr_sec;
> + unsigned long dc_ios, dc_merges, dc_sec, fl_ios;
> + unsigned int major, minor;
> + int i;
> +
> + i = sscanf(line, "%u %u %s %lu %lu %lu"
> + "%lu %lu %lu %lu %u %u %u %u"
> + "%lu %lu %lu %u %lu %u",
> + &major, &minor, dev_name,
> + &rd_ios, &rd_merges_or_rd_sec, &rd_sec_or_wr_ios,
> + &rd_ticks_or_wr_sec, &wr_ios, &wr_merges, &wr_sec,
> + &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks,
> + &dc_ios, &dc_merges, &dc_sec, &dc_ticks,
> + &fl_ios, &fl_ticks);
> +
> + if (i < 7) {
> + continue;
> + }
> +
> + diskstatinfo = g_new0(GuestDiskStatsInfo, 1);
> + diskstatinfo->name = g_strdup(dev_name);
> + diskstatinfo->major = major;
> + diskstatinfo->minor = minor;
> +
> + diskstat = g_new0(GuestDiskStats, 1);
> + if (i == 7) {
> + diskstat->has_read_ios = true;
> + diskstat->read_ios = rd_ios;
> + diskstat->has_read_sectors = true;
> + diskstat->read_sectors = rd_merges_or_rd_sec;
> + diskstat->has_write_ios = true;
> + diskstat->write_ios = rd_sec_or_wr_ios;
> + diskstat->has_write_sectors = true;
> + diskstat->write_sectors = rd_ticks_or_wr_sec;
> + }
> + if (i >= 14) {
> + diskstat->has_read_ios = true;
> + diskstat->read_ios = rd_ios;
> + diskstat->has_read_sectors = true;
> + diskstat->read_sectors = rd_sec_or_wr_ios;
> + diskstat->has_read_merges = true;
> + diskstat->read_merges = rd_merges_or_rd_sec;
> + diskstat->has_read_ticks = true;
> + diskstat->read_ticks = rd_ticks_or_wr_sec;
> + diskstat->has_write_ios = true;
> + diskstat->write_ios = wr_ios;
> + diskstat->has_write_sectors = true;
> + diskstat->write_sectors = wr_sec;
> + diskstat->has_write_merges = true;
> + diskstat->write_merges = wr_merges;
> + diskstat->has_write_ticks = true;
> + diskstat->write_ticks = wr_ticks;
> + diskstat->has_ios_pgr = true;
> + diskstat->ios_pgr = ios_pgr;
> + diskstat->has_total_ticks = true;
> + diskstat->total_ticks = tot_ticks;
> + diskstat->has_weight_ticks = true;
> + diskstat->weight_ticks = rq_ticks;
> + }
> + if (i >= 18) {
> + diskstat->has_discard_ios = true;
> + diskstat->discard_ios = dc_ios;
> + diskstat->has_discard_merges = true;
> + diskstat->discard_merges = dc_merges;
> + diskstat->has_discard_sectors = true;
> + diskstat->discard_sectors = dc_sec;
> + diskstat->has_discard_ticks = true;
> + diskstat->discard_ticks = dc_ticks;
> + }
> + if (i >= 20) {
> + diskstat->has_flush_ios = true;
> + diskstat->flush_ios = fl_ios;
> + diskstat->has_flush_ticks = true;
> + diskstat->flush_ticks = fl_ticks;
> + }
> +
> + diskstatinfo->stats = g_steal_pointer(&diskstat);
> + QAPI_LIST_APPEND(tail, diskstatinfo);
> + diskstatinfo = NULL;
> + }
> + free(line);
> + fclose(fp);
> + return head;
> +#else
> + g_debug("disk stats reporting available only for Linux");
> + return NULL;
> +#endif
> +}
> +
> +GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp)
> +{
> + return guest_get_diskstats(errp);
> +}
> +
> +GuestCpuStatsList *qmp_guest_get_cpustats(Error **errp)
> +{
> + GuestCpuStatsList *head = NULL, **tail = &head;
> + const char *cpustats = "/proc/stat";
> + int clk_tck = sysconf(_SC_CLK_TCK);
> + FILE *fp;
> + size_t n;
> + char *line = NULL;
> +
> + fp = fopen(cpustats, "r");
> + if (fp == NULL) {
> + error_setg_errno(errp, errno, "open(\"%s\")", cpustats);
> + return NULL;
> + }
> +
> + while (getline(&line, &n, fp) != -1) {
> + GuestCpuStats *cpustat = NULL;
> + GuestLinuxCpuStats *linuxcpustat;
> + int i;
> + unsigned long user, system, idle, iowait, irq, softirq, steal,
> guest;
> + unsigned long nice, guest_nice;
> + char name[64];
> +
> + i = sscanf(line, "%s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
> + name, &user, &nice, &system, &idle, &iowait, &irq,
> &softirq,
> + &steal, &guest, &guest_nice);
> +
> + /* drop "cpu 1 2 3 ...", get "cpuX 1 2 3 ..." only */
> + if ((i == EOF) || strncmp(name, "cpu", 3) || (name[3] == '\0')) {
> + continue;
> + }
> +
> + if (i < 5) {
> + slog("Parsing cpu stat from %s failed, see \"man proc\"",
> cpustats);
> + break;
> + }
> +
> + cpustat = g_new0(GuestCpuStats, 1);
> + cpustat->type = GUEST_CPU_STATS_TYPE_LINUX;
> +
> + linuxcpustat = &cpustat->u.q_linux;
> + linuxcpustat->cpu = atoi(&name[3]);
> + linuxcpustat->user = user * 1000 / clk_tck;
> + linuxcpustat->nice = nice * 1000 / clk_tck;
> + linuxcpustat->system = system * 1000 / clk_tck;
> + linuxcpustat->idle = idle * 1000 / clk_tck;
> +
> + if (i > 5) {
> + linuxcpustat->has_iowait = true;
> + linuxcpustat->iowait = iowait * 1000 / clk_tck;
> + }
> +
> + if (i > 6) {
> + linuxcpustat->has_irq = true;
> + linuxcpustat->irq = irq * 1000 / clk_tck;
> + linuxcpustat->has_softirq = true;
> + linuxcpustat->softirq = softirq * 1000 / clk_tck;
> + }
> +
> + if (i > 8) {
> + linuxcpustat->has_steal = true;
> + linuxcpustat->steal = steal * 1000 / clk_tck;
> + }
> +
> + if (i > 9) {
> + linuxcpustat->has_guest = true;
> + linuxcpustat->guest = guest * 1000 / clk_tck;
> + }
> +
> + if (i > 10) {
> + linuxcpustat->has_guest = true;
> + linuxcpustat->guest = guest * 1000 / clk_tck;
> + linuxcpustat->has_guestnice = true;
> + linuxcpustat->guestnice = guest_nice * 1000 / clk_tck;
> + }
> +
> + QAPI_LIST_APPEND(tail, cpustat);
> + }
> +
> + free(line);
> + fclose(fp);
> + return head;
> +}
> diff --git a/qga/commands-posix.c b/qga/commands-posix.c
> index 954efed01b..0bb8b9e2f3 100644
> --- a/qga/commands-posix.c
> +++ b/qga/commands-posix.c
> @@ -15,7 +15,6 @@
> #include <sys/ioctl.h>
> #include <sys/utsname.h>
> #include <sys/wait.h>
> -#include <dirent.h>
> #include "guest-agent-core.h"
> #include "qga-qapi-commands.h"
> #include "qapi/error.h"
> @@ -26,31 +25,12 @@
> #include "qemu/base64.h"
> #include "qemu/cutils.h"
> #include "commands-common.h"
> -#include "block/nvme.h"
> #include "cutils.h"
>
> #ifdef HAVE_UTMPX
> #include <utmpx.h>
> #endif
>
> -#if defined(__linux__)
> -#include <mntent.h>
> -#include <linux/fs.h>
> -#include <sys/statvfs.h>
> -#include <linux/nvme_ioctl.h>
> -
> -#ifdef CONFIG_LIBUDEV
> -#include <libudev.h>
> -#endif
> -
> -#ifdef FIFREEZE
> -#define CONFIG_FSFREEZE
> -#endif
> -#ifdef FITRIM
> -#define CONFIG_FSTRIM
> -#endif
> -#endif
> -
> #ifdef HAVE_GETIFADDRS
> #include <arpa/inet.h>
> #include <sys/socket.h>
> @@ -62,7 +42,173 @@
> #endif
> #endif
>
> -static void ga_wait_child(pid_t pid, int *status, Error **errp)
> +#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
> +void free_fs_mount_list(FsMountList *mounts)
> +{
> + FsMount *mount, *temp;
> +
> + if (!mounts) {
> + return;
> + }
> +
> + QTAILQ_FOREACH_SAFE(mount, mounts, next, temp) {
> + QTAILQ_REMOVE(mounts, mount, next);
> + g_free(mount->dirname);
> + g_free(mount->devtype);
> + g_free(mount);
> + }
> +}
> +#endif
> +
> +#if defined(CONFIG_FSFREEZE)
> +typedef enum {
> + FSFREEZE_HOOK_THAW = 0,
> + FSFREEZE_HOOK_FREEZE,
> +} FsfreezeHookArg;
> +
> +static const char *fsfreeze_hook_arg_string[] = {
> + "thaw",
> + "freeze",
> +};
> +
> +static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **errp)
> +{
> + 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(errp, 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);
> +
> + execl(hook, hook, arg_str, NULL);
> + _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 (local_err) {
> + error_propagate(errp, local_err);
> + return;
> + }
> +
> + if (!WIFEXITED(status)) {
> + error_setg(errp, "fsfreeze hook has terminated abnormally");
> + return;
> + }
> +
> + status = WEXITSTATUS(status);
> + if (status) {
> + error_setg(errp, "fsfreeze hook has failed with status %d",
> status);
> + return;
> + }
> +}
> +
> +int64_t qmp_guest_fsfreeze_thaw(Error **errp)
> +{
> + int ret;
> +
> + ret = qmp_guest_fsfreeze_do_thaw(errp);
> + if (ret >= 0) {
> + ga_unset_frozen(ga_state);
> + execute_fsfreeze_hook(FSFREEZE_HOOK_THAW, errp);
> + } else {
> + ret = 0;
> + }
> +
> + return ret;
> +}
> +
> +int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints,
> + strList *mountpoints,
> + Error **errp)
> +{
> + int ret;
> + FsMountList mounts;
> + Error *local_err = NULL;
> +
> + slog("guest-fsfreeze called");
> +
> + execute_fsfreeze_hook(FSFREEZE_HOOK_FREEZE, &local_err);
> + if (local_err) {
> + error_propagate(errp, local_err);
> + return -1;
> + }
> +
> + QTAILQ_INIT(&mounts);
> + if (!build_fs_mount_list(&mounts, &local_err)) {
> + error_propagate(errp, local_err);
> + return -1;
> + }
> +
> + /* cannot risk guest agent blocking itself on a write in this state */
> + ga_set_frozen(ga_state);
> +
> + ret = qmp_guest_fsfreeze_do_freeze_list(has_mountpoints, mountpoints,
> + mounts, errp);
> +
> + free_fs_mount_list(&mounts);
> + /* We may not issue any FIFREEZE here.
> + * Just unset ga_state here and ready for the next call.
> + */
> + if (ret == 0) {
> + ga_unset_frozen(ga_state);
> + } else if (ret < 0) {
> + qmp_guest_fsfreeze_thaw(NULL);
> + }
> + return ret;
> +}
> +
> +/*
> + * Return status of freeze/thaw
> + */
> +GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
> +{
> + if (ga_is_frozen(ga_state)) {
> + return GUEST_FSFREEZE_STATUS_FROZEN;
> + }
> +
> + return GUEST_FSFREEZE_STATUS_THAWED;
> +}
> +
> +int64_t qmp_guest_fsfreeze_freeze(Error **errp)
> +{
> + return qmp_guest_fsfreeze_freeze_list(false, NULL, errp);
> +}
> +
> +static void guest_fsfreeze_cleanup(void)
> +{
> + Error *err = NULL;
> +
> + if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
> + qmp_guest_fsfreeze_thaw(&err);
> + if (err) {
> + slog("failed to clean up frozen filesystems: %s",
> + error_get_pretty(err));
> + error_free(err);
> + }
> + }
> +}
> +#endif /* CONFIG_FSFREEZE */
> +
> +void ga_wait_child(pid_t pid, int *status, Error **errp)
> {
> pid_t rpid;
>
> @@ -617,2375 +763,7 @@ void qmp_guest_file_flush(int64_t handle, Error
> **errp)
> }
> }
>
> -/* linux-specific implementations. avoid this if at all possible. */
> -#if defined(__linux__)
> -
> -#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
> -typedef struct FsMount {
> - char *dirname;
> - char *devtype;
> - unsigned int devmajor, devminor;
> - QTAILQ_ENTRY(FsMount) next;
> -} FsMount;
> -
> -typedef QTAILQ_HEAD(FsMountList, FsMount) FsMountList;
> -
> -static void free_fs_mount_list(FsMountList *mounts)
> -{
> - FsMount *mount, *temp;
> -
> - if (!mounts) {
> - return;
> - }
> -
> - QTAILQ_FOREACH_SAFE(mount, mounts, next, temp) {
> - QTAILQ_REMOVE(mounts, mount, next);
> - g_free(mount->dirname);
> - g_free(mount->devtype);
> - g_free(mount);
> - }
> -}
> -
> -static int dev_major_minor(const char *devpath,
> - unsigned int *devmajor, unsigned int *devminor)
> -{
> - struct stat st;
> -
> - *devmajor = 0;
> - *devminor = 0;
> -
> - if (stat(devpath, &st) < 0) {
> - slog("failed to stat device file '%s': %s", devpath,
> strerror(errno));
> - return -1;
> - }
> - if (S_ISDIR(st.st_mode)) {
> - /* It is bind mount */
> - return -2;
> - }
> - if (S_ISBLK(st.st_mode)) {
> - *devmajor = major(st.st_rdev);
> - *devminor = minor(st.st_rdev);
> - return 0;
> - }
> - return -1;
> -}
> -
> -/*
> - * Walk the mount table and build a list of local file systems
> - */
> -static bool build_fs_mount_list_from_mtab(FsMountList *mounts, Error
> **errp)
> -{
> - struct mntent *ment;
> - FsMount *mount;
> - char const *mtab = "/proc/self/mounts";
> - FILE *fp;
> - unsigned int devmajor, devminor;
> -
> - fp = setmntent(mtab, "r");
> - if (!fp) {
> - error_setg(errp, "failed to open mtab file: '%s'", mtab);
> - return false;
> - }
> -
> - while ((ment = getmntent(fp))) {
> - /*
> - * An entry which device name doesn't start with a '/' is
> - * either a dummy file system or a network file system.
> - * Add special handling for smbfs and cifs as is done by
> - * coreutils as well.
> - */
> - if ((ment->mnt_fsname[0] != '/') ||
> - (strcmp(ment->mnt_type, "smbfs") == 0) ||
> - (strcmp(ment->mnt_type, "cifs") == 0)) {
> - continue;
> - }
> - if (dev_major_minor(ment->mnt_fsname, &devmajor, &devminor) ==
> -2) {
> - /* Skip bind mounts */
> - continue;
> - }
> -
> - mount = g_new0(FsMount, 1);
> - mount->dirname = g_strdup(ment->mnt_dir);
> - mount->devtype = g_strdup(ment->mnt_type);
> - mount->devmajor = devmajor;
> - mount->devminor = devminor;
> -
> - QTAILQ_INSERT_TAIL(mounts, mount, next);
> - }
> -
> - endmntent(fp);
> - return true;
> -}
> -
> -static void decode_mntname(char *name, int len)
> -{
> - int i, j = 0;
> - for (i = 0; i <= len; i++) {
> - if (name[i] != '\\') {
> - name[j++] = name[i];
> - } else if (name[i + 1] == '\\') {
> - name[j++] = '\\';
> - i++;
> - } else if (name[i + 1] >= '0' && name[i + 1] <= '3' &&
> - name[i + 2] >= '0' && name[i + 2] <= '7' &&
> - name[i + 3] >= '0' && name[i + 3] <= '7') {
> - name[j++] = (name[i + 1] - '0') * 64 +
> - (name[i + 2] - '0') * 8 +
> - (name[i + 3] - '0');
> - i += 3;
> - } else {
> - name[j++] = name[i];
> - }
> - }
> -}
> -
> -static bool build_fs_mount_list(FsMountList *mounts, Error **errp)
> -{
> - FsMount *mount;
> - char const *mountinfo = "/proc/self/mountinfo";
> - FILE *fp;
> - char *line = NULL, *dash;
> - size_t n;
> - char check;
> - unsigned int devmajor, devminor;
> - int ret, dir_s, dir_e, type_s, type_e, dev_s, dev_e;
> -
> - fp = fopen(mountinfo, "r");
> - if (!fp) {
> - return build_fs_mount_list_from_mtab(mounts, errp);
> - }
> -
> - while (getline(&line, &n, fp) != -1) {
> - ret = sscanf(line, "%*u %*u %u:%u %*s %n%*s%n%c",
> - &devmajor, &devminor, &dir_s, &dir_e, &check);
> - if (ret < 3) {
> - continue;
> - }
> - dash = strstr(line + dir_e, " - ");
> - if (!dash) {
> - continue;
> - }
> - ret = sscanf(dash, " - %n%*s%n %n%*s%n%c",
> - &type_s, &type_e, &dev_s, &dev_e, &check);
> - if (ret < 1) {
> - continue;
> - }
> - line[dir_e] = 0;
> - dash[type_e] = 0;
> - dash[dev_e] = 0;
> - decode_mntname(line + dir_s, dir_e - dir_s);
> - decode_mntname(dash + dev_s, dev_e - dev_s);
> - if (devmajor == 0) {
> - /* btrfs reports major number = 0 */
> - if (strcmp("btrfs", dash + type_s) != 0 ||
> - dev_major_minor(dash + dev_s, &devmajor, &devminor) < 0) {
> - continue;
> - }
> - }
> -
> - mount = g_new0(FsMount, 1);
> - mount->dirname = g_strdup(line + dir_s);
> - mount->devtype = g_strdup(dash + type_s);
> - mount->devmajor = devmajor;
> - mount->devminor = devminor;
> -
> - QTAILQ_INSERT_TAIL(mounts, mount, next);
> - }
> - free(line);
> -
> - fclose(fp);
> - return true;
> -}
> -#endif
> -
> -#if defined(CONFIG_FSFREEZE)
> -
> -static char *get_pci_driver(char const *syspath, int pathlen, Error
> **errp)
> -{
> - char *path;
> - char *dpath;
> - char *driver = NULL;
> - char buf[PATH_MAX];
> - ssize_t len;
> -
> - path = g_strndup(syspath, pathlen);
> - dpath = g_strdup_printf("%s/driver", path);
> - len = readlink(dpath, buf, sizeof(buf) - 1);
> - if (len != -1) {
> - buf[len] = 0;
> - driver = g_path_get_basename(buf);
> - }
> - g_free(dpath);
> - g_free(path);
> - return driver;
> -}
> -
> -static int compare_uint(const void *_a, const void *_b)
> -{
> - unsigned int a = *(unsigned int *)_a;
> - unsigned int b = *(unsigned int *)_b;
> -
> - return a < b ? -1 : a > b ? 1 : 0;
> -}
> -
> -/* Walk the specified sysfs and build a sorted list of host or ata
> numbers */
> -static int build_hosts(char const *syspath, char const *host, bool ata,
> - unsigned int *hosts, int hosts_max, Error **errp)
> -{
> - char *path;
> - DIR *dir;
> - struct dirent *entry;
> - int i = 0;
> -
> - path = g_strndup(syspath, host - syspath);
> - dir = opendir(path);
> - if (!dir) {
> - error_setg_errno(errp, errno, "opendir(\"%s\")", path);
> - g_free(path);
> - return -1;
> - }
> -
> - while (i < hosts_max) {
> - entry = readdir(dir);
> - if (!entry) {
> - break;
> - }
> - if (ata && sscanf(entry->d_name, "ata%d", hosts + i) == 1) {
> - ++i;
> - } else if (!ata && sscanf(entry->d_name, "host%d", hosts + i) ==
> 1) {
> - ++i;
> - }
> - }
> -
> - qsort(hosts, i, sizeof(hosts[0]), compare_uint);
> -
> - g_free(path);
> - closedir(dir);
> - return i;
> -}
> -
> -/*
> - * Store disk device info for devices on the PCI bus.
> - * Returns true if information has been stored, or false for failure.
> - */
> -static bool build_guest_fsinfo_for_pci_dev(char const *syspath,
> - GuestDiskAddress *disk,
> - Error **errp)
> -{
> - unsigned int pci[4], host, hosts[8], tgt[3];
> - int i, nhosts = 0, pcilen;
> - GuestPCIAddress *pciaddr = disk->pci_controller;
> - bool has_ata = false, has_host = false, has_tgt = false;
> - char *p, *q, *driver = NULL;
> - bool ret = false;
> -
> - p = strstr(syspath, "/devices/pci");
> - if (!p || sscanf(p + 12, "%*x:%*x/%x:%x:%x.%x%n",
> - pci, pci + 1, pci + 2, pci + 3, &pcilen) < 4) {
> - g_debug("only pci device is supported: sysfs path '%s'", syspath);
> - return false;
> - }
> -
> - p += 12 + pcilen;
> - while (true) {
> - driver = get_pci_driver(syspath, p - syspath, errp);
> - if (driver && (g_str_equal(driver, "ata_piix") ||
> - g_str_equal(driver, "sym53c8xx") ||
> - g_str_equal(driver, "virtio-pci") ||
> - g_str_equal(driver, "ahci") ||
> - g_str_equal(driver, "nvme"))) {
> - break;
> - }
> -
> - g_free(driver);
> - if (sscanf(p, "/%x:%x:%x.%x%n",
> - pci, pci + 1, pci + 2, pci + 3, &pcilen) == 4) {
> - p += pcilen;
> - continue;
> - }
> -
> - g_debug("unsupported driver or sysfs path '%s'", syspath);
> - return false;
> - }
> -
> - p = strstr(syspath, "/target");
> - if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u",
> - tgt, tgt + 1, tgt + 2) == 3) {
> - has_tgt = true;
> - }
> -
> - p = strstr(syspath, "/ata");
> - if (p) {
> - q = p + 4;
> - has_ata = true;
> - } else {
> - p = strstr(syspath, "/host");
> - q = p + 5;
> - }
> - if (p && sscanf(q, "%u", &host) == 1) {
> - has_host = true;
> - nhosts = build_hosts(syspath, p, has_ata, hosts,
> - ARRAY_SIZE(hosts), errp);
> - if (nhosts < 0) {
> - goto cleanup;
> - }
> - }
> -
> - pciaddr->domain = pci[0];
> - pciaddr->bus = pci[1];
> - pciaddr->slot = pci[2];
> - pciaddr->function = pci[3];
> -
> - if (strcmp(driver, "ata_piix") == 0) {
> - /* a host per ide bus, target*:0:<unit>:0 */
> - if (!has_host || !has_tgt) {
> - g_debug("invalid sysfs path '%s' (driver '%s')", syspath,
> driver);
> - goto cleanup;
> - }
> - for (i = 0; i < nhosts; i++) {
> - if (host == hosts[i]) {
> - disk->bus_type = GUEST_DISK_BUS_TYPE_IDE;
> - disk->bus = i;
> - disk->unit = tgt[1];
> - break;
> - }
> - }
> - if (i >= nhosts) {
> - g_debug("no host for '%s' (driver '%s')", syspath, driver);
> - goto cleanup;
> - }
> - } else if (strcmp(driver, "sym53c8xx") == 0) {
> - /* scsi(LSI Logic): target*:0:<unit>:0 */
> - if (!has_tgt) {
> - g_debug("invalid sysfs path '%s' (driver '%s')", syspath,
> driver);
> - goto cleanup;
> - }
> - disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
> - disk->unit = tgt[1];
> - } else if (strcmp(driver, "virtio-pci") == 0) {
> - if (has_tgt) {
> - /* virtio-scsi: target*:0:0:<unit> */
> - disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
> - disk->unit = tgt[2];
> - } else {
> - /* virtio-blk: 1 disk per 1 device */
> - disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
> - }
> - } else if (strcmp(driver, "ahci") == 0) {
> - /* ahci: 1 host per 1 unit */
> - if (!has_host || !has_tgt) {
> - g_debug("invalid sysfs path '%s' (driver '%s')", syspath,
> driver);
> - goto cleanup;
> - }
> - for (i = 0; i < nhosts; i++) {
> - if (host == hosts[i]) {
> - disk->unit = i;
> - disk->bus_type = GUEST_DISK_BUS_TYPE_SATA;
> - break;
> - }
> - }
> - if (i >= nhosts) {
> - g_debug("no host for '%s' (driver '%s')", syspath, driver);
> - goto cleanup;
> - }
> - } else if (strcmp(driver, "nvme") == 0) {
> - disk->bus_type = GUEST_DISK_BUS_TYPE_NVME;
> - } else {
> - g_debug("unknown driver '%s' (sysfs path '%s')", driver, syspath);
> - goto cleanup;
> - }
> -
> - ret = true;
> -
> -cleanup:
> - g_free(driver);
> - return ret;
> -}
> -
> -/*
> - * Store disk device info for non-PCI virtio devices (for example s390x
> - * channel I/O devices). Returns true if information has been stored, or
> - * false for failure.
> - */
> -static bool build_guest_fsinfo_for_nonpci_virtio(char const *syspath,
> - GuestDiskAddress *disk,
> - Error **errp)
> -{
> - unsigned int tgt[3];
> - char *p;
> -
> - if (!strstr(syspath, "/virtio") || !strstr(syspath, "/block")) {
> - g_debug("Unsupported virtio device '%s'", syspath);
> - return false;
> - }
> -
> - p = strstr(syspath, "/target");
> - if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u",
> - &tgt[0], &tgt[1], &tgt[2]) == 3) {
> - /* virtio-scsi: target*:0:<target>:<unit> */
> - disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
> - disk->bus = tgt[0];
> - disk->target = tgt[1];
> - disk->unit = tgt[2];
> - } else {
> - /* virtio-blk: 1 disk per 1 device */
> - disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
> - }
> -
> - return true;
> -}
> -
> -/*
> - * Store disk device info for CCW devices (s390x channel I/O devices).
> - * Returns true if information has been stored, or false for failure.
> - */
> -static bool build_guest_fsinfo_for_ccw_dev(char const *syspath,
> - GuestDiskAddress *disk,
> - Error **errp)
> -{
> - unsigned int cssid, ssid, subchno, devno;
> - char *p;
> -
> - p = strstr(syspath, "/devices/css");
> - if (!p || sscanf(p + 12, "%*x/%x.%x.%x/%*x.%*x.%x/",
> - &cssid, &ssid, &subchno, &devno) < 4) {
> - g_debug("could not parse ccw device sysfs path: %s", syspath);
> - return false;
> - }
> -
> - disk->has_ccw_address = true;
> - disk->ccw_address = g_new0(GuestCCWAddress, 1);
> - disk->ccw_address->cssid = cssid;
> - disk->ccw_address->ssid = ssid;
> - disk->ccw_address->subchno = subchno;
> - disk->ccw_address->devno = devno;
> -
> - if (strstr(p, "/virtio")) {
> - build_guest_fsinfo_for_nonpci_virtio(syspath, disk, errp);
> - }
> -
> - return true;
> -}
> -
> -/* Store disk device info specified by @sysfs into @fs */
> -static void build_guest_fsinfo_for_real_device(char const *syspath,
> - GuestFilesystemInfo *fs,
> - Error **errp)
> -{
> - GuestDiskAddress *disk;
> - GuestPCIAddress *pciaddr;
> - bool has_hwinf;
> -#ifdef CONFIG_LIBUDEV
> - struct udev *udev = NULL;
> - struct udev_device *udevice = NULL;
> -#endif
> -
> - pciaddr = g_new0(GuestPCIAddress, 1);
> - pciaddr->domain = -1; /* -1 means field is
> invalid */
> - pciaddr->bus = -1;
> - pciaddr->slot = -1;
> - pciaddr->function = -1;
> -
> - disk = g_new0(GuestDiskAddress, 1);
> - disk->pci_controller = pciaddr;
> - disk->bus_type = GUEST_DISK_BUS_TYPE_UNKNOWN;
> -
> -#ifdef CONFIG_LIBUDEV
> - udev = udev_new();
> - udevice = udev_device_new_from_syspath(udev, syspath);
> - if (udev == NULL || udevice == NULL) {
> - g_debug("failed to query udev");
> - } else {
> - const char *devnode, *serial;
> - devnode = udev_device_get_devnode(udevice);
> - if (devnode != NULL) {
> - disk->dev = g_strdup(devnode);
> - disk->has_dev = true;
> - }
> - serial = udev_device_get_property_value(udevice, "ID_SERIAL");
> - if (serial != NULL && *serial != 0) {
> - disk->serial = g_strdup(serial);
> - disk->has_serial = true;
> - }
> - }
> -
> - udev_unref(udev);
> - udev_device_unref(udevice);
> -#endif
> -
> - if (strstr(syspath, "/devices/pci")) {
> - has_hwinf = build_guest_fsinfo_for_pci_dev(syspath, disk, errp);
> - } else if (strstr(syspath, "/devices/css")) {
> - has_hwinf = build_guest_fsinfo_for_ccw_dev(syspath, disk, errp);
> - } else if (strstr(syspath, "/virtio")) {
> - has_hwinf = build_guest_fsinfo_for_nonpci_virtio(syspath, disk,
> errp);
> - } else {
> - g_debug("Unsupported device type for '%s'", syspath);
> - has_hwinf = false;
> - }
> -
> - if (has_hwinf || disk->has_dev || disk->has_serial) {
> - QAPI_LIST_PREPEND(fs->disk, disk);
> - } else {
> - qapi_free_GuestDiskAddress(disk);
> - }
> -}
> -
> -static void build_guest_fsinfo_for_device(char const *devpath,
> - GuestFilesystemInfo *fs,
> - Error **errp);
> -
> -/* Store a list of slave devices of virtual volume specified by @syspath
> into
> - * @fs */
> -static void build_guest_fsinfo_for_virtual_device(char const *syspath,
> - GuestFilesystemInfo *fs,
> - Error **errp)
> -{
> - Error *err = NULL;
> - DIR *dir;
> - char *dirpath;
> - struct dirent *entry;
> -
> - dirpath = g_strdup_printf("%s/slaves", syspath);
> - dir = opendir(dirpath);
> - if (!dir) {
> - if (errno != ENOENT) {
> - error_setg_errno(errp, errno, "opendir(\"%s\")", dirpath);
> - }
> - g_free(dirpath);
> - return;
> - }
> -
> - for (;;) {
> - errno = 0;
> - entry = readdir(dir);
> - if (entry == NULL) {
> - if (errno) {
> - error_setg_errno(errp, errno, "readdir(\"%s\")", dirpath);
> - }
> - break;
> - }
> -
> - if (entry->d_type == DT_LNK) {
> - char *path;
> -
> - g_debug(" slave device '%s'", entry->d_name);
> - path = g_strdup_printf("%s/slaves/%s", syspath,
> entry->d_name);
> - build_guest_fsinfo_for_device(path, fs, &err);
> - g_free(path);
> -
> - if (err) {
> - error_propagate(errp, err);
> - break;
> - }
> - }
> - }
> -
> - g_free(dirpath);
> - closedir(dir);
> -}
> -
> -static bool is_disk_virtual(const char *devpath, Error **errp)
> -{
> - g_autofree char *syspath = realpath(devpath, NULL);
> -
> - if (!syspath) {
> - error_setg_errno(errp, errno, "realpath(\"%s\")", devpath);
> - return false;
> - }
> - return strstr(syspath, "/devices/virtual/block/") != NULL;
> -}
> -
> -/* Dispatch to functions for virtual/real device */
> -static void build_guest_fsinfo_for_device(char const *devpath,
> - GuestFilesystemInfo *fs,
> - Error **errp)
> -{
> - ERRP_GUARD();
> - g_autofree char *syspath = NULL;
> - bool is_virtual = false;
> -
> - syspath = realpath(devpath, NULL);
> - if (!syspath) {
> - if (errno != ENOENT) {
> - error_setg_errno(errp, errno, "realpath(\"%s\")", devpath);
> - return;
> - }
> -
> - /* ENOENT: This devpath may not exist because of container config
> */
> - if (!fs->name) {
> - fs->name = g_path_get_basename(devpath);
> - }
> - return;
> - }
> -
> - if (!fs->name) {
> - fs->name = g_path_get_basename(syspath);
> - }
> -
> - g_debug(" parse sysfs path '%s'", syspath);
> - is_virtual = is_disk_virtual(syspath, errp);
> - if (*errp != NULL) {
> - return;
> - }
> - if (is_virtual) {
> - build_guest_fsinfo_for_virtual_device(syspath, fs, errp);
> - } else {
> - build_guest_fsinfo_for_real_device(syspath, fs, errp);
> - }
> -}
> -
> -#ifdef CONFIG_LIBUDEV
> -
> -/*
> - * Wrapper around build_guest_fsinfo_for_device() for getting just
> - * the disk address.
> - */
> -static GuestDiskAddress *get_disk_address(const char *syspath, Error
> **errp)
> -{
> - g_autoptr(GuestFilesystemInfo) fs = NULL;
> -
> - fs = g_new0(GuestFilesystemInfo, 1);
> - build_guest_fsinfo_for_device(syspath, fs, errp);
> - if (fs->disk != NULL) {
> - return g_steal_pointer(&fs->disk->value);
> - }
> - return NULL;
> -}
> -
> -static char *get_alias_for_syspath(const char *syspath)
> -{
> - struct udev *udev = NULL;
> - struct udev_device *udevice = NULL;
> - char *ret = NULL;
> -
> - udev = udev_new();
> - if (udev == NULL) {
> - g_debug("failed to query udev");
> - goto out;
> - }
> - udevice = udev_device_new_from_syspath(udev, syspath);
> - if (udevice == NULL) {
> - g_debug("failed to query udev for path: %s", syspath);
> - goto out;
> - } else {
> - const char *alias = udev_device_get_property_value(
> - udevice, "DM_NAME");
> - /*
> - * NULL means there was an error and empty string means there is
> no
> - * alias. In case of no alias we return NULL instead of empty
> string.
> - */
> - if (alias == NULL) {
> - g_debug("failed to query udev for device alias for: %s",
> - syspath);
> - } else if (*alias != 0) {
> - ret = g_strdup(alias);
> - }
> - }
> -
> -out:
> - udev_unref(udev);
> - udev_device_unref(udevice);
> - return ret;
> -}
> -
> -static char *get_device_for_syspath(const char *syspath)
> -{
> - struct udev *udev = NULL;
> - struct udev_device *udevice = NULL;
> - char *ret = NULL;
> -
> - udev = udev_new();
> - if (udev == NULL) {
> - g_debug("failed to query udev");
> - goto out;
> - }
> - udevice = udev_device_new_from_syspath(udev, syspath);
> - if (udevice == NULL) {
> - g_debug("failed to query udev for path: %s", syspath);
> - goto out;
> - } else {
> - ret = g_strdup(udev_device_get_devnode(udevice));
> - }
> -
> -out:
> - udev_unref(udev);
> - udev_device_unref(udevice);
> - return ret;
> -}
> -
> -static void get_disk_deps(const char *disk_dir, GuestDiskInfo *disk)
> -{
> - g_autofree char *deps_dir = NULL;
> - const gchar *dep;
> - GDir *dp_deps = NULL;
> -
> - /* List dependent disks */
> - deps_dir = g_strdup_printf("%s/slaves", disk_dir);
> - g_debug(" listing entries in: %s", deps_dir);
> - dp_deps = g_dir_open(deps_dir, 0, NULL);
> - if (dp_deps == NULL) {
> - g_debug("failed to list entries in %s", deps_dir);
> - return;
> - }
> - disk->has_dependencies = true;
> - while ((dep = g_dir_read_name(dp_deps)) != NULL) {
> - g_autofree char *dep_dir = NULL;
> - char *dev_name;
> -
> - /* Add dependent disks */
> - dep_dir = g_strdup_printf("%s/%s", deps_dir, dep);
> - dev_name = get_device_for_syspath(dep_dir);
> - if (dev_name != NULL) {
> - g_debug(" adding dependent device: %s", dev_name);
> - QAPI_LIST_PREPEND(disk->dependencies, dev_name);
> - }
> - }
> - g_dir_close(dp_deps);
> -}
> -
> -/*
> - * Detect partitions subdirectory, name is "<disk_name><number>" or
> - * "<disk_name>p<number>"
> - *
> - * @disk_name -- last component of /sys path (e.g. sda)
> - * @disk_dir -- sys path of the disk (e.g. /sys/block/sda)
> - * @disk_dev -- device node of the disk (e.g. /dev/sda)
> - */
> -static GuestDiskInfoList *get_disk_partitions(
> - GuestDiskInfoList *list,
> - const char *disk_name, const char *disk_dir,
> - const char *disk_dev)
> -{
> - GuestDiskInfoList *ret = list;
> - struct dirent *de_disk;
> - DIR *dp_disk = NULL;
> - size_t len = strlen(disk_name);
> -
> - dp_disk = opendir(disk_dir);
> - while ((de_disk = readdir(dp_disk)) != NULL) {
> - g_autofree char *partition_dir = NULL;
> - char *dev_name;
> - GuestDiskInfo *partition;
> -
> - if (!(de_disk->d_type & DT_DIR)) {
> - continue;
> - }
> -
> - if (!(strncmp(disk_name, de_disk->d_name, len) == 0 &&
> - ((*(de_disk->d_name + len) == 'p' &&
> - isdigit(*(de_disk->d_name + len + 1))) ||
> - isdigit(*(de_disk->d_name + len))))) {
> - continue;
> - }
> -
> - partition_dir = g_strdup_printf("%s/%s",
> - disk_dir, de_disk->d_name);
> - dev_name = get_device_for_syspath(partition_dir);
> - if (dev_name == NULL) {
> - g_debug("Failed to get device name for syspath: %s",
> - disk_dir);
> - continue;
> - }
> - partition = g_new0(GuestDiskInfo, 1);
> - partition->name = dev_name;
> - partition->partition = true;
> - partition->has_dependencies = true;
> - /* Add parent disk as dependent for easier tracking of hierarchy
> */
> - QAPI_LIST_PREPEND(partition->dependencies, g_strdup(disk_dev));
> -
> - QAPI_LIST_PREPEND(ret, partition);
> - }
> - closedir(dp_disk);
> -
> - return ret;
> -}
> -
> -static void get_nvme_smart(GuestDiskInfo *disk)
> -{
> - int fd;
> - GuestNVMeSmart *smart;
> - NvmeSmartLog log = {0};
> - struct nvme_admin_cmd cmd = {
> - .opcode = NVME_ADM_CMD_GET_LOG_PAGE,
> - .nsid = NVME_NSID_BROADCAST,
> - .addr = (uintptr_t)&log,
> - .data_len = sizeof(log),
> - .cdw10 = NVME_LOG_SMART_INFO | (1 << 15) /* RAE bit */
> - | (((sizeof(log) >> 2) - 1) << 16)
> - };
> -
> - fd = qga_open_cloexec(disk->name, O_RDONLY, 0);
> - if (fd == -1) {
> - g_debug("Failed to open device: %s: %s", disk->name,
> g_strerror(errno));
> - return;
> - }
> -
> - if (ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd)) {
> - g_debug("Failed to get smart: %s: %s", disk->name,
> g_strerror(errno));
> - close(fd);
> - return;
> - }
> -
> - disk->has_smart = true;
> - disk->smart = g_new0(GuestDiskSmart, 1);
> - disk->smart->type = GUEST_DISK_BUS_TYPE_NVME;
> -
> - smart = &disk->smart->u.nvme;
> - smart->critical_warning = log.critical_warning;
> - smart->temperature = lduw_le_p(&log.temperature); /* unaligned field
> */
> - smart->available_spare = log.available_spare;
> - smart->available_spare_threshold = log.available_spare_threshold;
> - smart->percentage_used = log.percentage_used;
> - smart->data_units_read_lo = le64_to_cpu(log.data_units_read[0]);
> - smart->data_units_read_hi = le64_to_cpu(log.data_units_read[1]);
> - smart->data_units_written_lo = le64_to_cpu(log.data_units_written[0]);
> - smart->data_units_written_hi = le64_to_cpu(log.data_units_written[1]);
> - smart->host_read_commands_lo = le64_to_cpu(log.host_read_commands[0]);
> - smart->host_read_commands_hi = le64_to_cpu(log.host_read_commands[1]);
> - smart->host_write_commands_lo =
> le64_to_cpu(log.host_write_commands[0]);
> - smart->host_write_commands_hi =
> le64_to_cpu(log.host_write_commands[1]);
> - smart->controller_busy_time_lo =
> le64_to_cpu(log.controller_busy_time[0]);
> - smart->controller_busy_time_hi =
> le64_to_cpu(log.controller_busy_time[1]);
> - smart->power_cycles_lo = le64_to_cpu(log.power_cycles[0]);
> - smart->power_cycles_hi = le64_to_cpu(log.power_cycles[1]);
> - smart->power_on_hours_lo = le64_to_cpu(log.power_on_hours[0]);
> - smart->power_on_hours_hi = le64_to_cpu(log.power_on_hours[1]);
> - smart->unsafe_shutdowns_lo = le64_to_cpu(log.unsafe_shutdowns[0]);
> - smart->unsafe_shutdowns_hi = le64_to_cpu(log.unsafe_shutdowns[1]);
> - smart->media_errors_lo = le64_to_cpu(log.media_errors[0]);
> - smart->media_errors_hi = le64_to_cpu(log.media_errors[1]);
> - smart->number_of_error_log_entries_lo =
> - le64_to_cpu(log.number_of_error_log_entries[0]);
> - smart->number_of_error_log_entries_hi =
> - le64_to_cpu(log.number_of_error_log_entries[1]);
> -
> - close(fd);
> -}
> -
> -static void get_disk_smart(GuestDiskInfo *disk)
> -{
> - if (disk->has_address
> - && (disk->address->bus_type == GUEST_DISK_BUS_TYPE_NVME)) {
> - get_nvme_smart(disk);
> - }
> -}
> -
> -GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
> -{
> - GuestDiskInfoList *ret = NULL;
> - GuestDiskInfo *disk;
> - DIR *dp = NULL;
> - struct dirent *de = NULL;
> -
> - g_debug("listing /sys/block directory");
> - dp = opendir("/sys/block");
> - if (dp == NULL) {
> - error_setg_errno(errp, errno, "Can't open directory
> \"/sys/block\"");
> - return NULL;
> - }
> - while ((de = readdir(dp)) != NULL) {
> - g_autofree char *disk_dir = NULL, *line = NULL,
> - *size_path = NULL;
> - char *dev_name;
> - Error *local_err = NULL;
> - if (de->d_type != DT_LNK) {
> - g_debug(" skipping entry: %s", de->d_name);
> - continue;
> - }
> -
> - /* Check size and skip zero-sized disks */
> - g_debug(" checking disk size");
> - size_path = g_strdup_printf("/sys/block/%s/size", de->d_name);
> - if (!g_file_get_contents(size_path, &line, NULL, NULL)) {
> - g_debug(" failed to read disk size");
> - continue;
> - }
> - if (g_strcmp0(line, "0\n") == 0) {
> - g_debug(" skipping zero-sized disk");
> - continue;
> - }
> -
> - g_debug(" adding %s", de->d_name);
> - disk_dir = g_strdup_printf("/sys/block/%s", de->d_name);
> - dev_name = get_device_for_syspath(disk_dir);
> - if (dev_name == NULL) {
> - g_debug("Failed to get device name for syspath: %s",
> - disk_dir);
> - continue;
> - }
> - disk = g_new0(GuestDiskInfo, 1);
> - disk->name = dev_name;
> - disk->partition = false;
> - disk->alias = get_alias_for_syspath(disk_dir);
> - disk->has_alias = (disk->alias != NULL);
> - QAPI_LIST_PREPEND(ret, disk);
> -
> - /* Get address for non-virtual devices */
> - bool is_virtual = is_disk_virtual(disk_dir, &local_err);
> - if (local_err != NULL) {
> - g_debug(" failed to check disk path, ignoring error: %s",
> - error_get_pretty(local_err));
> - error_free(local_err);
> - local_err = NULL;
> - /* Don't try to get the address */
> - is_virtual = true;
> - }
> - if (!is_virtual) {
> - disk->address = get_disk_address(disk_dir, &local_err);
> - if (local_err != NULL) {
> - g_debug(" failed to get device info, ignoring error: %s",
> - error_get_pretty(local_err));
> - error_free(local_err);
> - local_err = NULL;
> - } else if (disk->address != NULL) {
> - disk->has_address = true;
> - }
> - }
> -
> - get_disk_deps(disk_dir, disk);
> - get_disk_smart(disk);
> - ret = get_disk_partitions(ret, de->d_name, disk_dir, dev_name);
> - }
> -
> - closedir(dp);
> -
> - return ret;
> -}
> -
> -#else
> -
> -GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
> -{
> - error_setg(errp, QERR_UNSUPPORTED);
> - return NULL;
> -}
> -
> -#endif
> -
> -/* Return a list of the disk device(s)' info which @mount lies on */
> -static GuestFilesystemInfo *build_guest_fsinfo(struct FsMount *mount,
> - Error **errp)
> -{
> - GuestFilesystemInfo *fs = g_malloc0(sizeof(*fs));
> - struct statvfs buf;
> - unsigned long used, nonroot_total, fr_size;
> - char *devpath = g_strdup_printf("/sys/dev/block/%u:%u",
> - mount->devmajor, mount->devminor);
> -
> - fs->mountpoint = g_strdup(mount->dirname);
> - fs->type = g_strdup(mount->devtype);
> - build_guest_fsinfo_for_device(devpath, fs, errp);
> -
> - if (statvfs(fs->mountpoint, &buf) == 0) {
> - fr_size = buf.f_frsize;
> - used = buf.f_blocks - buf.f_bfree;
> - nonroot_total = used + buf.f_bavail;
> - fs->used_bytes = used * fr_size;
> - fs->total_bytes = nonroot_total * fr_size;
> -
> - fs->has_total_bytes = true;
> - fs->has_used_bytes = true;
> - }
> -
> - g_free(devpath);
> -
> - return fs;
> -}
> -
> -GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
> -{
> - FsMountList mounts;
> - struct FsMount *mount;
> - GuestFilesystemInfoList *ret = NULL;
> - Error *local_err = NULL;
> -
> - QTAILQ_INIT(&mounts);
> - if (!build_fs_mount_list(&mounts, &local_err)) {
> - error_propagate(errp, local_err);
> - return NULL;
> - }
> -
> - QTAILQ_FOREACH(mount, &mounts, next) {
> - g_debug("Building guest fsinfo for '%s'", mount->dirname);
> -
> - QAPI_LIST_PREPEND(ret, build_guest_fsinfo(mount, &local_err));
> - if (local_err) {
> - error_propagate(errp, local_err);
> - qapi_free_GuestFilesystemInfoList(ret);
> - ret = NULL;
> - break;
> - }
> - }
> -
> - free_fs_mount_list(&mounts);
> - return ret;
> -}
> -
> -
> -typedef enum {
> - FSFREEZE_HOOK_THAW = 0,
> - FSFREEZE_HOOK_FREEZE,
> -} FsfreezeHookArg;
> -
> -static const char *fsfreeze_hook_arg_string[] = {
> - "thaw",
> - "freeze",
> -};
> -
> -static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **errp)
> -{
> - 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(errp, 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);
> -
> - execl(hook, hook, arg_str, NULL);
> - _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 (local_err) {
> - error_propagate(errp, local_err);
> - return;
> - }
> -
> - if (!WIFEXITED(status)) {
> - error_setg(errp, "fsfreeze hook has terminated abnormally");
> - return;
> - }
> -
> - status = WEXITSTATUS(status);
> - if (status) {
> - error_setg(errp, "fsfreeze hook has failed with status %d",
> status);
> - return;
> - }
> -}
> -
> -/*
> - * Return status of freeze/thaw
> - */
> -GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
> -{
> - if (ga_is_frozen(ga_state)) {
> - return GUEST_FSFREEZE_STATUS_FROZEN;
> - }
> -
> - return GUEST_FSFREEZE_STATUS_THAWED;
> -}
> -
> -int64_t qmp_guest_fsfreeze_freeze(Error **errp)
> -{
> - return qmp_guest_fsfreeze_freeze_list(false, NULL, errp);
> -}
> -
> -/*
> - * Walk list of mounted file systems in the guest, and freeze the ones
> which
> - * are real local file systems.
> - */
> -int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints,
> - strList *mountpoints,
> - Error **errp)
> -{
> - int ret = 0, i = 0;
> - strList *list;
> - FsMountList mounts;
> - struct FsMount *mount;
> - Error *local_err = NULL;
> - int fd;
> -
> - slog("guest-fsfreeze called");
> -
> - execute_fsfreeze_hook(FSFREEZE_HOOK_FREEZE, &local_err);
> - if (local_err) {
> - error_propagate(errp, local_err);
> - return -1;
> - }
> -
> - QTAILQ_INIT(&mounts);
> - if (!build_fs_mount_list(&mounts, &local_err)) {
> - error_propagate(errp, local_err);
> - return -1;
> - }
> -
> - /* cannot risk guest agent blocking itself on a write in this state */
> - ga_set_frozen(ga_state);
> -
> - QTAILQ_FOREACH_REVERSE(mount, &mounts, next) {
> - /* To issue fsfreeze in the reverse order of mounts, check if the
> - * mount is listed in the list here */
> - if (has_mountpoints) {
> - for (list = mountpoints; list; list = list->next) {
> - if (strcmp(list->value, mount->dirname) == 0) {
> - break;
> - }
> - }
> - if (!list) {
> - continue;
> - }
> - }
> -
> - fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
> - if (fd == -1) {
> - error_setg_errno(errp, errno, "failed to open %s",
> mount->dirname);
> - goto error;
> - }
> -
> - /* we try to cull filesystems we know won't work in advance, but
> other
> - * filesystems may not implement fsfreeze for less obvious
> reasons.
> - * these will report EOPNOTSUPP. we simply ignore these when
> tallying
> - * the number of frozen filesystems.
> - * if a filesystem is mounted more than once (aka bind mount) a
> - * consecutive attempt to freeze an already frozen filesystem will
> - * return EBUSY.
> - *
> - * any other error means a failure to freeze a filesystem we
> - * expect to be freezable, so return an error in those cases
> - * and return system to thawed state.
> - */
> - ret = ioctl(fd, FIFREEZE);
> - if (ret == -1) {
> - if (errno != EOPNOTSUPP && errno != EBUSY) {
> - error_setg_errno(errp, errno, "failed to freeze %s",
> - mount->dirname);
> - close(fd);
> - goto error;
> - }
> - } else {
> - i++;
> - }
> - close(fd);
> - }
> -
> - free_fs_mount_list(&mounts);
> - /* We may not issue any FIFREEZE here.
> - * Just unset ga_state here and ready for the next call.
> - */
> - if (i == 0) {
> - ga_unset_frozen(ga_state);
> - }
> - return i;
> -
> -error:
> - free_fs_mount_list(&mounts);
> - qmp_guest_fsfreeze_thaw(NULL);
> - return 0;
> -}
> -
> -/*
> - * Walk list of frozen file systems in the guest, and thaw them.
> - */
> -int64_t qmp_guest_fsfreeze_thaw(Error **errp)
> -{
> - int ret;
> - FsMountList mounts;
> - FsMount *mount;
> - int fd, i = 0, logged;
> - Error *local_err = NULL;
> -
> - QTAILQ_INIT(&mounts);
> - if (!build_fs_mount_list(&mounts, &local_err)) {
> - error_propagate(errp, local_err);
> - return 0;
> - }
> -
> - QTAILQ_FOREACH(mount, &mounts, next) {
> - logged = false;
> - fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
> - if (fd == -1) {
> - continue;
> - }
> - /* we have no way of knowing whether a filesystem was actually
> unfrozen
> - * as a result of a successful call to FITHAW, only that if an
> error
> - * was returned the filesystem was *not* unfrozen by that
> particular
> - * call.
> - *
> - * since multiple preceding FIFREEZEs require multiple calls to
> FITHAW
> - * to unfreeze, continuing issuing FITHAW until an error is
> returned,
> - * in which case either the filesystem is in an unfreezable
> state, or,
> - * more likely, it was thawed previously (and remains so
> afterward).
> - *
> - * also, since the most recent successful call is the one that did
> - * the actual unfreeze, we can use this to provide an accurate
> count
> - * of the number of filesystems unfrozen by guest-fsfreeze-thaw,
> which
> - * may * be useful for determining whether a filesystem was
> unfrozen
> - * during the freeze/thaw phase by a process other than qemu-ga.
> - */
> - do {
> - ret = ioctl(fd, FITHAW);
> - if (ret == 0 && !logged) {
> - i++;
> - logged = true;
> - }
> - } while (ret == 0);
> - close(fd);
> - }
> -
> - ga_unset_frozen(ga_state);
> - free_fs_mount_list(&mounts);
> -
> - execute_fsfreeze_hook(FSFREEZE_HOOK_THAW, errp);
> -
> - return i;
> -}
> -
> -static void guest_fsfreeze_cleanup(void)
> -{
> - Error *err = NULL;
> -
> - if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
> - qmp_guest_fsfreeze_thaw(&err);
> - if (err) {
> - slog("failed to clean up frozen filesystems: %s",
> - error_get_pretty(err));
> - error_free(err);
> - }
> - }
> -}
> -#endif /* CONFIG_FSFREEZE */
> -
> -#if defined(CONFIG_FSTRIM)
> -/*
> - * Walk list of mounted file systems in the guest, and trim them.
> - */
> -GuestFilesystemTrimResponse *
> -qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
> -{
> - GuestFilesystemTrimResponse *response;
> - GuestFilesystemTrimResult *result;
> - int ret = 0;
> - FsMountList mounts;
> - struct FsMount *mount;
> - int fd;
> - struct fstrim_range r;
> -
> - slog("guest-fstrim called");
> -
> - QTAILQ_INIT(&mounts);
> - if (!build_fs_mount_list(&mounts, errp)) {
> - return NULL;
> - }
> -
> - response = g_malloc0(sizeof(*response));
> -
> - QTAILQ_FOREACH(mount, &mounts, next) {
> - result = g_malloc0(sizeof(*result));
> - result->path = g_strdup(mount->dirname);
> -
> - QAPI_LIST_PREPEND(response->paths, result);
> -
> - fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
> - if (fd == -1) {
> - result->error = g_strdup_printf("failed to open: %s",
> - strerror(errno));
> - result->has_error = true;
> - continue;
> - }
> -
> - /* We try to cull filesystems we know won't work in advance, but
> other
> - * filesystems may not implement fstrim for less obvious reasons.
> - * These will report EOPNOTSUPP; while in some other cases ENOTTY
> - * will be reported (e.g. CD-ROMs).
> - * Any other error means an unexpected error.
> - */
> - r.start = 0;
> - r.len = -1;
> - r.minlen = has_minimum ? minimum : 0;
> - ret = ioctl(fd, FITRIM, &r);
> - if (ret == -1) {
> - result->has_error = true;
> - if (errno == ENOTTY || errno == EOPNOTSUPP) {
> - result->error = g_strdup("trim not supported");
> - } else {
> - result->error = g_strdup_printf("failed to trim: %s",
> - strerror(errno));
> - }
> - close(fd);
> - continue;
> - }
> -
> - result->has_minimum = true;
> - result->minimum = r.minlen;
> - result->has_trimmed = true;
> - result->trimmed = r.len;
> - close(fd);
> - }
> -
> - free_fs_mount_list(&mounts);
> - return response;
> -}
> -#endif /* CONFIG_FSTRIM */
> -
> -
> -#define LINUX_SYS_STATE_FILE "/sys/power/state"
> -#define SUSPEND_SUPPORTED 0
> -#define SUSPEND_NOT_SUPPORTED 1
> -
> -typedef enum {
> - SUSPEND_MODE_DISK = 0,
> - SUSPEND_MODE_RAM = 1,
> - SUSPEND_MODE_HYBRID = 2,
> -} SuspendMode;
> -
> -/*
> - * Executes a command in a child process using g_spawn_sync,
> - * returning an int >= 0 representing the exit status of the
> - * process.
> - *
> - * If the program wasn't found in path, returns -1.
> - *
> - * If a problem happened when creating the child process,
> - * returns -1 and errp is set.
> - */
> -static int run_process_child(const char *command[], Error **errp)
> -{
> - int exit_status, spawn_flag;
> - GError *g_err = NULL;
> - bool success;
> -
> - spawn_flag = G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL |
> - G_SPAWN_STDERR_TO_DEV_NULL;
> -
> - success = g_spawn_sync(NULL, (char **)command, NULL, spawn_flag,
> - NULL, NULL, NULL, NULL,
> - &exit_status, &g_err);
> -
> - if (success) {
> - return WEXITSTATUS(exit_status);
> - }
> -
> - if (g_err && (g_err->code != G_SPAWN_ERROR_NOENT)) {
> - error_setg(errp, "failed to create child process, error '%s'",
> - g_err->message);
> - }
> -
> - g_error_free(g_err);
> - return -1;
> -}
> -
> -static bool systemd_supports_mode(SuspendMode mode, Error **errp)
> -{
> - const char *systemctl_args[3] = {"systemd-hibernate",
> "systemd-suspend",
> - "systemd-hybrid-sleep"};
> - const char *cmd[4] = {"systemctl", "status", systemctl_args[mode],
> NULL};
> - int status;
> -
> - status = run_process_child(cmd, errp);
> -
> - /*
> - * systemctl status uses LSB return codes so we can expect
> - * status > 0 and be ok. To assert if the guest has support
> - * for the selected suspend mode, status should be < 4. 4 is
> - * the code for unknown service status, the return value when
> - * the service does not exist. A common value is status = 3
> - * (program is not running).
> - */
> - if (status > 0 && status < 4) {
> - return true;
> - }
> -
> - return false;
> -}
> -
> -static void systemd_suspend(SuspendMode mode, Error **errp)
> -{
> - Error *local_err = NULL;
> - const char *systemctl_args[3] = {"hibernate", "suspend",
> "hybrid-sleep"};
> - const char *cmd[3] = {"systemctl", systemctl_args[mode], NULL};
> - int status;
> -
> - status = run_process_child(cmd, &local_err);
> -
> - if (status == 0) {
> - return;
> - }
> -
> - if ((status == -1) && !local_err) {
> - error_setg(errp, "the helper program 'systemctl %s' was not
> found",
> - systemctl_args[mode]);
> - return;
> - }
> -
> - if (local_err) {
> - error_propagate(errp, local_err);
> - } else {
> - error_setg(errp, "the helper program 'systemctl %s' returned an "
> - "unexpected exit status code (%d)",
> - systemctl_args[mode], status);
> - }
> -}
> -
> -static bool pmutils_supports_mode(SuspendMode mode, Error **errp)
> -{
> - Error *local_err = NULL;
> - const char *pmutils_args[3] = {"--hibernate", "--suspend",
> - "--suspend-hybrid"};
> - const char *cmd[3] = {"pm-is-supported", pmutils_args[mode], NULL};
> - int status;
> -
> - status = run_process_child(cmd, &local_err);
> -
> - if (status == SUSPEND_SUPPORTED) {
> - return true;
> - }
> -
> - if ((status == -1) && !local_err) {
> - return false;
> - }
> -
> - if (local_err) {
> - error_propagate(errp, local_err);
> - } else {
> - error_setg(errp,
> - "the helper program '%s' returned an unexpected exit"
> - " status code (%d)", "pm-is-supported", status);
> - }
> -
> - return false;
> -}
> -
> -static void pmutils_suspend(SuspendMode mode, Error **errp)
> -{
> - Error *local_err = NULL;
> - const char *pmutils_binaries[3] = {"pm-hibernate", "pm-suspend",
> - "pm-suspend-hybrid"};
> - const char *cmd[2] = {pmutils_binaries[mode], NULL};
> - int status;
> -
> - status = run_process_child(cmd, &local_err);
> -
> - if (status == 0) {
> - return;
> - }
> -
> - if ((status == -1) && !local_err) {
> - error_setg(errp, "the helper program '%s' was not found",
> - pmutils_binaries[mode]);
> - return;
> - }
> -
> - if (local_err) {
> - error_propagate(errp, local_err);
> - } else {
> - error_setg(errp,
> - "the helper program '%s' returned an unexpected exit"
> - " status code (%d)", pmutils_binaries[mode], status);
> - }
> -}
> -
> -static bool linux_sys_state_supports_mode(SuspendMode mode, Error **errp)
> -{
> - const char *sysfile_strs[3] = {"disk", "mem", NULL};
> - const char *sysfile_str = sysfile_strs[mode];
> - char buf[32]; /* hopefully big enough */
> - int fd;
> - ssize_t ret;
> -
> - if (!sysfile_str) {
> - error_setg(errp, "unknown guest suspend mode");
> - return false;
> - }
> -
> - fd = open(LINUX_SYS_STATE_FILE, O_RDONLY);
> - if (fd < 0) {
> - return false;
> - }
> -
> - ret = read(fd, buf, sizeof(buf) - 1);
> - close(fd);
> - if (ret <= 0) {
> - return false;
> - }
> - buf[ret] = '\0';
> -
> - if (strstr(buf, sysfile_str)) {
> - return true;
> - }
> - return false;
> -}
> -
> -static void linux_sys_state_suspend(SuspendMode mode, Error **errp)
> -{
> - Error *local_err = NULL;
> - const char *sysfile_strs[3] = {"disk", "mem", NULL};
> - const char *sysfile_str = sysfile_strs[mode];
> - pid_t pid;
> - int status;
> -
> - if (!sysfile_str) {
> - error_setg(errp, "unknown guest suspend mode");
> - return;
> - }
> -
> - pid = fork();
> - if (!pid) {
> - /* child */
> - int fd;
> -
> - setsid();
> - reopen_fd_to_null(0);
> - reopen_fd_to_null(1);
> - reopen_fd_to_null(2);
> -
> - fd = open(LINUX_SYS_STATE_FILE, O_WRONLY);
> - if (fd < 0) {
> - _exit(EXIT_FAILURE);
> - }
> -
> - if (write(fd, sysfile_str, strlen(sysfile_str)) < 0) {
> - _exit(EXIT_FAILURE);
> - }
> -
> - _exit(EXIT_SUCCESS);
> - } else if (pid < 0) {
> - error_setg_errno(errp, errno, "failed to create child process");
> - return;
> - }
> -
> - ga_wait_child(pid, &status, &local_err);
> - if (local_err) {
> - error_propagate(errp, local_err);
> - return;
> - }
> -
> - if (WEXITSTATUS(status)) {
> - error_setg(errp, "child process has failed to suspend");
> - }
> -
> -}
> -
> -static void guest_suspend(SuspendMode mode, Error **errp)
> -{
> - Error *local_err = NULL;
> - bool mode_supported = false;
> -
> - if (systemd_supports_mode(mode, &local_err)) {
> - mode_supported = true;
> - systemd_suspend(mode, &local_err);
> - }
> -
> - if (!local_err) {
> - return;
> - }
> -
> - error_free(local_err);
> - local_err = NULL;
> -
> - if (pmutils_supports_mode(mode, &local_err)) {
> - mode_supported = true;
> - pmutils_suspend(mode, &local_err);
> - }
> -
> - if (!local_err) {
> - return;
> - }
> -
> - error_free(local_err);
> - local_err = NULL;
> -
> - if (linux_sys_state_supports_mode(mode, &local_err)) {
> - mode_supported = true;
> - linux_sys_state_suspend(mode, &local_err);
> - }
> -
> - if (!mode_supported) {
> - error_free(local_err);
> - error_setg(errp,
> - "the requested suspend mode is not supported by the
> guest");
> - } else {
> - error_propagate(errp, local_err);
> - }
> -}
> -
> -void qmp_guest_suspend_disk(Error **errp)
> -{
> - guest_suspend(SUSPEND_MODE_DISK, errp);
> -}
> -
> -void qmp_guest_suspend_ram(Error **errp)
> -{
> - guest_suspend(SUSPEND_MODE_RAM, errp);
> -}
> -
> -void qmp_guest_suspend_hybrid(Error **errp)
> -{
> - guest_suspend(SUSPEND_MODE_HYBRID, errp);
> -}
> -
> -/* 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,
> - char *dirpath, Error **errp)
> -{
> - int fd;
> - int res;
> - int dirfd;
> - static const char fn[] = "online";
> -
> - dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
> - if (dirfd == -1) {
> - error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
> - return;
> - }
> -
> - 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);
> -}
> -
> -GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
> -{
> - GuestLogicalProcessorList *head, **tail;
> - const char *cpu_dir = "/sys/devices/system/cpu";
> - const gchar *line;
> - g_autoptr(GDir) cpu_gdir = NULL;
> - Error *local_err = NULL;
> -
> - head = NULL;
> - tail = &head;
> - cpu_gdir = g_dir_open(cpu_dir, 0, NULL);
> -
> - if (cpu_gdir == NULL) {
> - error_setg_errno(errp, errno, "failed to list entries: %s",
> cpu_dir);
> - return NULL;
> - }
> -
> - while (local_err == NULL && (line = g_dir_read_name(cpu_gdir)) !=
> NULL) {
> - GuestLogicalProcessor *vcpu;
> - int64_t id;
> - if (sscanf(line, "cpu%" PRId64, &id)) {
> - g_autofree char *path =
> g_strdup_printf("/sys/devices/system/cpu/"
> - "cpu%" PRId64 "/",
> id);
> - vcpu = g_malloc0(sizeof *vcpu);
> - vcpu->logical_id = id;
> - vcpu->has_can_offline = true; /* lolspeak ftw */
> - transfer_vcpu(vcpu, true, path, &local_err);
> - QAPI_LIST_APPEND(tail, vcpu);
> - }
> - }
> -
> - 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) {
> - char *path = g_strdup_printf("/sys/devices/system/cpu/cpu%"
> PRId64 "/",
> - vcpus->value->logical_id);
> -
> - transfer_vcpu(vcpus->value, false, path, &local_err);
> - g_free(path);
> - 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;
> -}
> -
> -void qmp_guest_set_user_password(const char *username,
> - const char *password,
> - bool crypted,
> - Error **errp)
> -{
> - Error *local_err = NULL;
> - char *passwd_path = NULL;
> - pid_t pid;
> - int status;
> - int datafd[2] = { -1, -1 };
> - char *rawpasswddata = NULL;
> - size_t rawpasswdlen;
> - char *chpasswddata = NULL;
> - size_t chpasswdlen;
> -
> - rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen,
> errp);
> - if (!rawpasswddata) {
> - return;
> - }
> - rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
> - rawpasswddata[rawpasswdlen] = '\0';
> -
> - if (strchr(rawpasswddata, '\n')) {
> - error_setg(errp, "forbidden characters in raw password");
> - goto out;
> - }
> -
> - if (strchr(username, '\n') ||
> - strchr(username, ':')) {
> - error_setg(errp, "forbidden characters in username");
> - goto out;
> - }
> -
> - chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata);
> - chpasswdlen = strlen(chpasswddata);
> -
> - passwd_path = g_find_program_in_path("chpasswd");
> -
> - if (!passwd_path) {
> - error_setg(errp, "cannot find 'passwd' program in PATH");
> - goto out;
> - }
> -
> - if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) {
> - error_setg(errp, "cannot create pipe FDs");
> - goto out;
> - }
> -
> - pid = fork();
> - if (pid == 0) {
> - close(datafd[1]);
> - /* child */
> - setsid();
> - dup2(datafd[0], 0);
> - reopen_fd_to_null(1);
> - reopen_fd_to_null(2);
> -
> - if (crypted) {
> - execl(passwd_path, "chpasswd", "-e", NULL);
> - } else {
> - execl(passwd_path, "chpasswd", NULL);
> - }
> - _exit(EXIT_FAILURE);
> - } else if (pid < 0) {
> - error_setg_errno(errp, errno, "failed to create child process");
> - goto out;
> - }
> - close(datafd[0]);
> - datafd[0] = -1;
> -
> - if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) !=
> chpasswdlen) {
> - error_setg_errno(errp, errno, "cannot write new account
> password");
> - goto out;
> - }
> - close(datafd[1]);
> - datafd[1] = -1;
> -
> - ga_wait_child(pid, &status, &local_err);
> - if (local_err) {
> - error_propagate(errp, local_err);
> - goto out;
> - }
> -
> - if (!WIFEXITED(status)) {
> - error_setg(errp, "child process has terminated abnormally");
> - goto out;
> - }
> -
> - if (WEXITSTATUS(status)) {
> - error_setg(errp, "child process has failed to set user password");
> - goto out;
> - }
> -
> -out:
> - g_free(chpasswddata);
> - g_free(rawpasswddata);
> - g_free(passwd_path);
> - if (datafd[0] != -1) {
> - close(datafd[0]);
> - }
> - if (datafd[1] != -1) {
> - close(datafd[1]);
> - }
> -}
> -
> -static void ga_read_sysfs_file(int dirfd, const char *pathname, char *buf,
> - int size, Error **errp)
> -{
> - int fd;
> - int res;
> -
> - errno = 0;
> - fd = openat(dirfd, pathname, O_RDONLY);
> - if (fd == -1) {
> - error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname);
> - return;
> - }
> -
> - res = pread(fd, buf, size, 0);
> - if (res == -1) {
> - error_setg_errno(errp, errno, "pread sysfs file \"%s\"",
> pathname);
> - } else if (res == 0) {
> - error_setg(errp, "pread sysfs file \"%s\": unexpected EOF",
> pathname);
> - }
> - close(fd);
> -}
> -
> -static void ga_write_sysfs_file(int dirfd, const char *pathname,
> - const char *buf, int size, Error **errp)
> -{
> - int fd;
> -
> - errno = 0;
> - fd = openat(dirfd, pathname, O_WRONLY);
> - if (fd == -1) {
> - error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname);
> - return;
> - }
> -
> - if (pwrite(fd, buf, size, 0) == -1) {
> - error_setg_errno(errp, errno, "pwrite sysfs file \"%s\"",
> pathname);
> - }
> -
> - close(fd);
> -}
> -
> -/* Transfer online/offline status between @mem_blk and the guest system.
> - *
> - * On input either @errp or *@errp must be NULL.
> - *
> - * In system-to-@mem_blk direction, the following @mem_blk fields are
> accessed:
> - * - R: mem_blk->phys_index
> - * - W: mem_blk->online
> - * - W: mem_blk->can_offline
> - *
> - * In @mem_blk-to-system direction, the following @mem_blk fields are
> accessed:
> - * - R: mem_blk->phys_index
> - * - R: mem_blk->online
> - *- R: mem_blk->can_offline
> - * Written members remain unmodified on error.
> - */
> -static void transfer_memory_block(GuestMemoryBlock *mem_blk, bool
> sys2memblk,
> - GuestMemoryBlockResponse *result,
> - Error **errp)
> -{
> - char *dirpath;
> - int dirfd;
> - char *status;
> - Error *local_err = NULL;
> -
> - if (!sys2memblk) {
> - DIR *dp;
> -
> - if (!result) {
> - error_setg(errp, "Internal error, 'result' should not be
> NULL");
> - return;
> - }
> - errno = 0;
> - dp = opendir("/sys/devices/system/memory/");
> - /* if there is no 'memory' directory in sysfs,
> - * we think this VM does not support online/offline memory block,
> - * any other solution?
> - */
> - if (!dp) {
> - if (errno == ENOENT) {
> - result->response =
> -
> GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
> - }
> - goto out1;
> - }
> - closedir(dp);
> - }
> -
> - dirpath = g_strdup_printf("/sys/devices/system/memory/memory%" PRId64
> "/",
> - mem_blk->phys_index);
> - dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
> - if (dirfd == -1) {
> - if (sys2memblk) {
> - error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
> - } else {
> - if (errno == ENOENT) {
> - result->response =
> GUEST_MEMORY_BLOCK_RESPONSE_TYPE_NOT_FOUND;
> - } else {
> - result->response =
> - GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
> - }
> - }
> - g_free(dirpath);
> - goto out1;
> - }
> - g_free(dirpath);
> -
> - status = g_malloc0(10);
> - ga_read_sysfs_file(dirfd, "state", status, 10, &local_err);
> - if (local_err) {
> - /* treat with sysfs file that not exist in old kernel */
> - if (errno == ENOENT) {
> - error_free(local_err);
> - if (sys2memblk) {
> - mem_blk->online = true;
> - mem_blk->can_offline = false;
> - } else if (!mem_blk->online) {
> - result->response =
> -
> GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
> - }
> - } else {
> - if (sys2memblk) {
> - error_propagate(errp, local_err);
> - } else {
> - error_free(local_err);
> - result->response =
> - GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
> - }
> - }
> - goto out2;
> - }
> -
> - if (sys2memblk) {
> - char removable = '0';
> -
> - mem_blk->online = (strncmp(status, "online", 6) == 0);
> -
> - ga_read_sysfs_file(dirfd, "removable", &removable, 1, &local_err);
> - if (local_err) {
> - /* if no 'removable' file, it doesn't support offline mem blk
> */
> - if (errno == ENOENT) {
> - error_free(local_err);
> - mem_blk->can_offline = false;
> - } else {
> - error_propagate(errp, local_err);
> - }
> - } else {
> - mem_blk->can_offline = (removable != '0');
> - }
> - } else {
> - if (mem_blk->online != (strncmp(status, "online", 6) == 0)) {
> - const char *new_state = mem_blk->online ? "online" :
> "offline";
> -
> - ga_write_sysfs_file(dirfd, "state", new_state,
> strlen(new_state),
> - &local_err);
> - if (local_err) {
> - error_free(local_err);
> - result->response =
> - GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
> - goto out2;
> - }
> -
> - result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_SUCCESS;
> - result->has_error_code = false;
> - } /* otherwise pretend successful re-(on|off)-lining */
> - }
> - g_free(status);
> - close(dirfd);
> - return;
> -
> -out2:
> - g_free(status);
> - close(dirfd);
> -out1:
> - if (!sys2memblk) {
> - result->has_error_code = true;
> - result->error_code = errno;
> - }
> -}
> -
> -GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
> -{
> - GuestMemoryBlockList *head, **tail;
> - Error *local_err = NULL;
> - struct dirent *de;
> - DIR *dp;
> -
> - head = NULL;
> - tail = &head;
> -
> - dp = opendir("/sys/devices/system/memory/");
> - if (!dp) {
> - /* it's ok if this happens to be a system that doesn't expose
> - * memory blocks via sysfs, but otherwise we should report
> - * an error
> - */
> - if (errno != ENOENT) {
> - error_setg_errno(errp, errno, "Can't open directory"
> - "\"/sys/devices/system/memory/\"");
> - }
> - return NULL;
> - }
> -
> - /* Note: the phys_index of memory block may be discontinuous,
> - * this is because a memblk is the unit of the Sparse Memory design,
> which
> - * allows discontinuous memory ranges (ex. NUMA), so here we should
> - * traverse the memory block directory.
> - */
> - while ((de = readdir(dp)) != NULL) {
> - GuestMemoryBlock *mem_blk;
> -
> - if ((strncmp(de->d_name, "memory", 6) != 0) ||
> - !(de->d_type & DT_DIR)) {
> - continue;
> - }
> -
> - mem_blk = g_malloc0(sizeof *mem_blk);
> - /* The d_name is "memoryXXX", phys_index is block id, same as
> XXX */
> - mem_blk->phys_index = strtoul(&de->d_name[6], NULL, 10);
> - mem_blk->has_can_offline = true; /* lolspeak ftw */
> - transfer_memory_block(mem_blk, true, NULL, &local_err);
> - if (local_err) {
> - break;
> - }
> -
> - QAPI_LIST_APPEND(tail, mem_blk);
> - }
> -
> - closedir(dp);
> - if (local_err == NULL) {
> - /* there's no guest with zero memory blocks */
> - if (head == NULL) {
> - error_setg(errp, "guest reported zero memory blocks!");
> - }
> - return head;
> - }
> -
> - qapi_free_GuestMemoryBlockList(head);
> - error_propagate(errp, local_err);
> - return NULL;
> -}
> -
> -GuestMemoryBlockResponseList *
> -qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp)
> -{
> - GuestMemoryBlockResponseList *head, **tail;
> - Error *local_err = NULL;
> -
> - head = NULL;
> - tail = &head;
> -
> - while (mem_blks != NULL) {
> - GuestMemoryBlockResponse *result;
> - GuestMemoryBlock *current_mem_blk = mem_blks->value;
> -
> - result = g_malloc0(sizeof(*result));
> - result->phys_index = current_mem_blk->phys_index;
> - transfer_memory_block(current_mem_blk, false, result, &local_err);
> - if (local_err) { /* should never happen */
> - goto err;
> - }
> -
> - QAPI_LIST_APPEND(tail, result);
> - mem_blks = mem_blks->next;
> - }
> -
> - return head;
> -err:
> - qapi_free_GuestMemoryBlockResponseList(head);
> - error_propagate(errp, local_err);
> - return NULL;
> -}
> -
> -GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
> -{
> - Error *local_err = NULL;
> - char *dirpath;
> - int dirfd;
> - char *buf;
> - GuestMemoryBlockInfo *info;
> -
> - dirpath = g_strdup_printf("/sys/devices/system/memory/");
> - dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
> - if (dirfd == -1) {
> - error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
> - g_free(dirpath);
> - return NULL;
> - }
> - g_free(dirpath);
> -
> - buf = g_malloc0(20);
> - ga_read_sysfs_file(dirfd, "block_size_bytes", buf, 20, &local_err);
> - close(dirfd);
> - if (local_err) {
> - g_free(buf);
> - error_propagate(errp, local_err);
> - return NULL;
> - }
> -
> - info = g_new0(GuestMemoryBlockInfo, 1);
> - info->size = strtol(buf, NULL, 16); /* the unit is bytes */
> -
> - g_free(buf);
> -
> - return info;
> -}
> -
> -#define MAX_NAME_LEN 128
> -static GuestDiskStatsInfoList *guest_get_diskstats(Error **errp)
> -{
> -#ifdef CONFIG_LINUX
> - GuestDiskStatsInfoList *head = NULL, **tail = &head;
> - const char *diskstats = "/proc/diskstats";
> - FILE *fp;
> - size_t n;
> - char *line = NULL;
> -
> - fp = fopen(diskstats, "r");
> - if (fp == NULL) {
> - error_setg_errno(errp, errno, "open(\"%s\")", diskstats);
> - return NULL;
> - }
> -
> - while (getline(&line, &n, fp) != -1) {
> - g_autofree GuestDiskStatsInfo *diskstatinfo = NULL;
> - g_autofree GuestDiskStats *diskstat = NULL;
> - char dev_name[MAX_NAME_LEN];
> - unsigned int ios_pgr, tot_ticks, rq_ticks, wr_ticks, dc_ticks,
> fl_ticks;
> - unsigned long rd_ios, rd_merges_or_rd_sec, rd_ticks_or_wr_sec,
> wr_ios;
> - unsigned long wr_merges, rd_sec_or_wr_ios, wr_sec;
> - unsigned long dc_ios, dc_merges, dc_sec, fl_ios;
> - unsigned int major, minor;
> - int i;
> -
> - i = sscanf(line, "%u %u %s %lu %lu %lu"
> - "%lu %lu %lu %lu %u %u %u %u"
> - "%lu %lu %lu %u %lu %u",
> - &major, &minor, dev_name,
> - &rd_ios, &rd_merges_or_rd_sec, &rd_sec_or_wr_ios,
> - &rd_ticks_or_wr_sec, &wr_ios, &wr_merges, &wr_sec,
> - &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks,
> - &dc_ios, &dc_merges, &dc_sec, &dc_ticks,
> - &fl_ios, &fl_ticks);
> -
> - if (i < 7) {
> - continue;
> - }
> -
> - diskstatinfo = g_new0(GuestDiskStatsInfo, 1);
> - diskstatinfo->name = g_strdup(dev_name);
> - diskstatinfo->major = major;
> - diskstatinfo->minor = minor;
> -
> - diskstat = g_new0(GuestDiskStats, 1);
> - if (i == 7) {
> - diskstat->has_read_ios = true;
> - diskstat->read_ios = rd_ios;
> - diskstat->has_read_sectors = true;
> - diskstat->read_sectors = rd_merges_or_rd_sec;
> - diskstat->has_write_ios = true;
> - diskstat->write_ios = rd_sec_or_wr_ios;
> - diskstat->has_write_sectors = true;
> - diskstat->write_sectors = rd_ticks_or_wr_sec;
> - }
> - if (i >= 14) {
> - diskstat->has_read_ios = true;
> - diskstat->read_ios = rd_ios;
> - diskstat->has_read_sectors = true;
> - diskstat->read_sectors = rd_sec_or_wr_ios;
> - diskstat->has_read_merges = true;
> - diskstat->read_merges = rd_merges_or_rd_sec;
> - diskstat->has_read_ticks = true;
> - diskstat->read_ticks = rd_ticks_or_wr_sec;
> - diskstat->has_write_ios = true;
> - diskstat->write_ios = wr_ios;
> - diskstat->has_write_sectors = true;
> - diskstat->write_sectors = wr_sec;
> - diskstat->has_write_merges = true;
> - diskstat->write_merges = wr_merges;
> - diskstat->has_write_ticks = true;
> - diskstat->write_ticks = wr_ticks;
> - diskstat->has_ios_pgr = true;
> - diskstat->ios_pgr = ios_pgr;
> - diskstat->has_total_ticks = true;
> - diskstat->total_ticks = tot_ticks;
> - diskstat->has_weight_ticks = true;
> - diskstat->weight_ticks = rq_ticks;
> - }
> - if (i >= 18) {
> - diskstat->has_discard_ios = true;
> - diskstat->discard_ios = dc_ios;
> - diskstat->has_discard_merges = true;
> - diskstat->discard_merges = dc_merges;
> - diskstat->has_discard_sectors = true;
> - diskstat->discard_sectors = dc_sec;
> - diskstat->has_discard_ticks = true;
> - diskstat->discard_ticks = dc_ticks;
> - }
> - if (i >= 20) {
> - diskstat->has_flush_ios = true;
> - diskstat->flush_ios = fl_ios;
> - diskstat->has_flush_ticks = true;
> - diskstat->flush_ticks = fl_ticks;
> - }
> -
> - diskstatinfo->stats = g_steal_pointer(&diskstat);
> - QAPI_LIST_APPEND(tail, diskstatinfo);
> - diskstatinfo = NULL;
> - }
> - free(line);
> - fclose(fp);
> - return head;
> -#else
> - g_debug("disk stats reporting available only for Linux");
> - return NULL;
> -#endif
> -}
> -
> -GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp)
> -{
> - return guest_get_diskstats(errp);
> -}
> -
> -GuestCpuStatsList *qmp_guest_get_cpustats(Error **errp)
> -{
> - GuestCpuStatsList *head = NULL, **tail = &head;
> - const char *cpustats = "/proc/stat";
> - int clk_tck = sysconf(_SC_CLK_TCK);
> - FILE *fp;
> - size_t n;
> - char *line = NULL;
> -
> - fp = fopen(cpustats, "r");
> - if (fp == NULL) {
> - error_setg_errno(errp, errno, "open(\"%s\")", cpustats);
> - return NULL;
> - }
> -
> - while (getline(&line, &n, fp) != -1) {
> - GuestCpuStats *cpustat = NULL;
> - GuestLinuxCpuStats *linuxcpustat;
> - int i;
> - unsigned long user, system, idle, iowait, irq, softirq, steal,
> guest;
> - unsigned long nice, guest_nice;
> - char name[64];
> -
> - i = sscanf(line, "%s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
> - name, &user, &nice, &system, &idle, &iowait, &irq,
> &softirq,
> - &steal, &guest, &guest_nice);
> -
> - /* drop "cpu 1 2 3 ...", get "cpuX 1 2 3 ..." only */
> - if ((i == EOF) || strncmp(name, "cpu", 3) || (name[3] == '\0')) {
> - continue;
> - }
> -
> - if (i < 5) {
> - slog("Parsing cpu stat from %s failed, see \"man proc\"",
> cpustats);
> - break;
> - }
> -
> - cpustat = g_new0(GuestCpuStats, 1);
> - cpustat->type = GUEST_CPU_STATS_TYPE_LINUX;
> -
> - linuxcpustat = &cpustat->u.q_linux;
> - linuxcpustat->cpu = atoi(&name[3]);
> - linuxcpustat->user = user * 1000 / clk_tck;
> - linuxcpustat->nice = nice * 1000 / clk_tck;
> - linuxcpustat->system = system * 1000 / clk_tck;
> - linuxcpustat->idle = idle * 1000 / clk_tck;
> -
> - if (i > 5) {
> - linuxcpustat->has_iowait = true;
> - linuxcpustat->iowait = iowait * 1000 / clk_tck;
> - }
> -
> - if (i > 6) {
> - linuxcpustat->has_irq = true;
> - linuxcpustat->irq = irq * 1000 / clk_tck;
> - linuxcpustat->has_softirq = true;
> - linuxcpustat->softirq = softirq * 1000 / clk_tck;
> - }
> -
> - if (i > 8) {
> - linuxcpustat->has_steal = true;
> - linuxcpustat->steal = steal * 1000 / clk_tck;
> - }
> -
> - if (i > 9) {
> - linuxcpustat->has_guest = true;
> - linuxcpustat->guest = guest * 1000 / clk_tck;
> - }
> -
> - if (i > 10) {
> - linuxcpustat->has_guest = true;
> - linuxcpustat->guest = guest * 1000 / clk_tck;
> - linuxcpustat->has_guestnice = true;
> - linuxcpustat->guestnice = guest_nice * 1000 / clk_tck;
> - }
> -
> - QAPI_LIST_APPEND(tail, cpustat);
> - }
> -
> - free(line);
> - fclose(fp);
> - return head;
> -}
> -
> -#else /* defined(__linux__) */
> +#if !defined(__linux__)
>
> void qmp_guest_suspend_disk(Error **errp)
> {
> diff --git a/qga/meson.build b/qga/meson.build
> index 65c1e93846..409f49a000 100644
> --- a/qga/meson.build
> +++ b/qga/meson.build
> @@ -72,6 +72,9 @@ qga_ss.add(when: 'CONFIG_POSIX', if_true: files(
> 'commands-posix.c',
> 'commands-posix-ssh.c',
> ))
> +qga_ss.add(when: 'CONFIG_LINUX', if_true: files(
> + 'commands-linux.c',
> +))
> qga_ss.add(when: 'CONFIG_WIN32', if_true: files(
> 'channel-win32.c',
> 'commands-win32.c',
> --
> 2.34.1
>
>
>
--
Marc-André Lureau
[-- Attachment #2: Type: text/html, Size: 198831 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 3/5] qga: Add UFS freeze/thaw support for FreeBSD
2022-09-22 13:19 ` [PATCH 3/5] qga: Add UFS freeze/thaw support for FreeBSD Alexander Ivanov
@ 2022-09-23 8:09 ` Marc-André Lureau
0 siblings, 0 replies; 14+ messages in thread
From: Marc-André Lureau @ 2022-09-23 8:09 UTC (permalink / raw)
To: Alexander Ivanov; +Cc: qemu-devel, den, michael.roth, kkostiuk
[-- Attachment #1: Type: text/plain, Size: 5994 bytes --]
Hi
On Thu, Sep 22, 2022 at 4:15 PM Alexander Ivanov <
alexander.ivanov@virtuozzo.com> wrote:
> UFS supports FS freezing through ioctl UFSSUSPEND on /dev/ufssuspend.
> Freezed FS can be thawed by closing /dev/ufssuspend file descriptior.
>
> Use getmntinfo to get a list of mounted FS.
>
> Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
> ---
> qga/commands-bsd.c | 109 +++++++++++++++++++++++++++++++++++++++---
> qga/commands-common.h | 11 +++++
> qga/main.c | 6 +++
> 3 files changed, 120 insertions(+), 6 deletions(-)
>
> diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
> index c1e3ed13e9..5d3f46804a 100644
> --- a/qga/commands-bsd.c
> +++ b/qga/commands-bsd.c
> @@ -17,28 +17,125 @@
> #include "qemu/queue.h"
> #include "commands-common.h"
>
> +#include <sys/ioctl.h>
> +#include <sys/param.h>
> +#include <sys/ucred.h>
> +#include <sys/mount.h>
> +#include <paths.h>
> +
> #if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
> bool build_fs_mount_list(FsMountList *mounts, Error **errp)
> {
> - error_setg(errp, QERR_UNSUPPORTED);
> - return false;
> + FsMount *mount;
> + struct statfs *mntbuf, *mntp;
> + struct stat statbuf;
> + int i, count, ret;
> +
> + count = getmntinfo(&mntbuf, MNT_NOWAIT);
> + if (count == 0) {
> + error_setg_errno(errp, errno, "getmntinfo failed");
> + return false;
> + }
> +
> + for (i = 0; i < count; i++) {
> + mntp = &mntbuf[i];
> + ret = stat(mntp->f_mntonname, &statbuf);
> + if (ret != 0) {
>
I am not sure we can simply ignore an error here. At least, there should be
a warning logged, no?
> + continue;
> + }
> +
> + mount = g_new0(FsMount, 1);
> +
> + mount->dirname = g_strdup(mntp->f_mntonname);
> + mount->devtype = g_strdup(mntp->f_fstypename);
> + mount->devmajor = major(mount->dev);
> + mount->devminor = minor(mount->dev);
> + mount->fsid = mntp->f_fsid;
> + mount->dev = statbuf.st_dev;
> +
> + QTAILQ_INSERT_TAIL(mounts, mount, next);
> + }
> + return true;
> }
> #endif
>
> #if defined(CONFIG_FSFREEZE)
> +static int ufssuspend_fd = -1;
> +static int ufssuspend_cnt;
> +
> int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints,
> strList *mountpoints,
> FsMountList mounts,
> Error **errp)
> {
> - error_setg(errp, QERR_UNSUPPORTED);
> - return 0;
> + int ret;
> + strList *list;
> + struct FsMount *mount;
> +
> + if (ufssuspend_fd != -1) {
> + error_setg(errp, "filesystems have already frozen");
> + return -1;
> + }
> +
> + ufssuspend_cnt = 0;
> + ufssuspend_fd = qemu_open(_PATH_UFSSUSPEND, O_RDWR, errp);
> + if (ufssuspend_fd == -1) {
> + return -1;
> + }
> +
> + QTAILQ_FOREACH_REVERSE(mount, &mounts, next) {
> + /*
> + * To issue fsfreeze in the reverse order of mounts, check if the
> + * mount is listed in the list here
> + */
> + if (has_mountpoints) {
> + for (list = mountpoints; list; list = list->next) {
> + if (strcmp(list->value, mount->dirname) == 0) {
>
nit: I prefer g_str_equal()
> + break;
> + }
> + }
> + if (!list) {
> + continue;
> + }
> + }
> +
> + /* Only UFS supports suspend */
> + if (strcmp(mount->devtype, "ufs") != 0) {
>
!g_str_equal()
> + continue;
> + }
> +
> + ret = ioctl(ufssuspend_fd, UFSSUSPEND, &mount->fsid);
> + if (ret == -1) {
> + /*
> + * ioctl returns EBUSY for all the FS except the first one
> + * that was suspended
> + */
> + if (errno == EBUSY) {
> + continue;
> + }
> + error_setg_errno(errp, errno, "failed to freeze %s",
> + mount->dirname);
> + goto error;
> + }
> + ufssuspend_cnt++;
> + }
> + return ufssuspend_cnt;
> +error:
> + close(ufssuspend_fd);
> + ufssuspend_fd = -1;
> + return -1;
> +
> }
>
> int qmp_guest_fsfreeze_do_thaw(Error **errp)
> {
> - error_setg(errp, QERR_UNSUPPORTED);
> - return 0;
> + int ret = ufssuspend_cnt;
> + ufssuspend_cnt = 0;
> + if (ufssuspend_fd != -1) {
> + close(ufssuspend_fd);
> + ufssuspend_fd = -1;
> + }
>
Maybe leave a comment that UFSRESUME isn't necessary?
> + return ret;
> }
> #endif
>
> diff --git a/qga/commands-common.h b/qga/commands-common.h
> index aa0472ea4c..c3be6db3a9 100644
> --- a/qga/commands-common.h
> +++ b/qga/commands-common.h
> @@ -41,11 +41,22 @@ void ga_wait_child(pid_t pid, int *status, Error
> **errp);
> #endif
> #endif /* __linux__*/
>
> +#ifdef __FreeBSD__
> +#include <ufs/ffs/fs.h>
> +#ifdef UFSSUSPEND
> +#define CONFIG_FSFREEZE
> +#endif
> +#endif /* __FreeBSD__ */
> +
> #if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
> typedef struct FsMount {
> char *dirname;
> char *devtype;
> unsigned int devmajor, devminor;
> +#if defined(__FreeBSD__)
> + dev_t dev;
> + fsid_t fsid;
> +#endif
> QTAILQ_ENTRY(FsMount) next;
> } FsMount;
>
> diff --git a/qga/main.c b/qga/main.c
> index 22b3c0df11..ab420051fb 100644
> --- a/qga/main.c
> +++ b/qga/main.c
> @@ -43,6 +43,12 @@
> #define CONFIG_FSFREEZE
> #endif
> #endif
> +#ifdef __FreeBSD__
> +#include <ufs/ffs/fs.h>
> +#ifdef UFSSUSPEND
> +#define CONFIG_FSFREEZE
> +#endif
> +#endif
>
> #ifndef _WIN32
> #ifdef __FreeBSD__
> --
> 2.34.1
>
>
>
--
Marc-André Lureau
[-- Attachment #2: Type: text/html, Size: 8162 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 4/5] qga: Add shutdown/halt/reboot support for FreeBSD
2022-09-22 13:20 ` [PATCH 4/5] qga: Add shutdown/halt/reboot " Alexander Ivanov
@ 2022-09-23 8:13 ` Marc-André Lureau
0 siblings, 0 replies; 14+ messages in thread
From: Marc-André Lureau @ 2022-09-23 8:13 UTC (permalink / raw)
To: Alexander Ivanov; +Cc: qemu-devel, den, michael.roth, kkostiuk
[-- Attachment #1: Type: text/plain, Size: 1728 bytes --]
Hi
On Thu, Sep 22, 2022 at 3:51 PM Alexander Ivanov <
alexander.ivanov@virtuozzo.com> wrote:
> Add appropriate shutdown command arguments in qmp_guest_shutdown()
> for FreeBSD.
>
> Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
> ---
> qga/commands-posix.c | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/qga/commands-posix.c b/qga/commands-posix.c
> index 3a1055d5c3..60cc673f25 100644
> --- a/qga/commands-posix.c
> +++ b/qga/commands-posix.c
> @@ -242,6 +242,10 @@ void qmp_guest_shutdown(bool has_mode, const char
> *mode, Error **errp)
> const char *powerdown_flag = "-i5";
> const char *halt_flag = "-i0";
> const char *reboot_flag = "-i6";
> +#elifdef CONFIG_BSD
>
It might be the first time I see a patch with "#elifdef" :) this is C2X, so
please use "#elif defined(..)" instead
> + const char *powerdown_flag = "-p";
> + const char *halt_flag = "-h";
> + const char *reboot_flag = "-r";
> #else
> const char *powerdown_flag = "-P";
> const char *halt_flag = "-H";
> @@ -272,6 +276,9 @@ void qmp_guest_shutdown(bool has_mode, const char
> *mode, Error **errp)
> #ifdef CONFIG_SOLARIS
> execl("/sbin/shutdown", "shutdown", shutdown_flag, "-g0", "-y",
> "hypervisor initiated shutdown", (char *)NULL);
> +#elifdef CONFIG_BSD
> + execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
> + "hypervisor initiated shutdown", (char *)NULL);
> #else
> execl("/sbin/shutdown", "shutdown", "-h", shutdown_flag, "+0",
> "hypervisor initiated shutdown", (char *)NULL);
> --
> 2.34.1
>
>
>
lgtm otherwise
--
Marc-André Lureau
[-- Attachment #2: Type: text/html, Size: 2764 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 5/5] qga: Add support for user password settinng in FreeeBSD
2022-09-22 13:20 ` [PATCH 5/5] qga: Add support for user password settinng in FreeeBSD Alexander Ivanov
@ 2022-09-23 8:19 ` Marc-André Lureau
2022-09-23 13:43 ` Marc-André Lureau
0 siblings, 1 reply; 14+ messages in thread
From: Marc-André Lureau @ 2022-09-23 8:19 UTC (permalink / raw)
To: Alexander Ivanov; +Cc: qemu-devel, den, michael.roth, kkostiuk
[-- Attachment #1: Type: text/plain, Size: 8996 bytes --]
Hi
On Thu, Sep 22, 2022 at 4:25 PM Alexander Ivanov <
alexander.ivanov@virtuozzo.com> wrote:
> Move qmp_guest_set_user_password() to commands-posix.c under (__linux__ or
> __FreeBSD) condition. Add command and arguments for password setting in
> FreeBSD.
>
> Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
>
If you avoid the code move, the patch is much smaller and easier to review
and lgtm.
> ---
> qga/commands-bsd.c | 8 ---
> qga/commands-linux.c | 105 --------------------------------------
> qga/commands-posix.c | 117 ++++++++++++++++++++++++++++++++++++++++++-
> 3 files changed, 116 insertions(+), 114 deletions(-)
>
> diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
> index 5d3f46804a..fa3933f2f4 100644
> --- a/qga/commands-bsd.c
> +++ b/qga/commands-bsd.c
> @@ -190,14 +190,6 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList
> *vcpus, Error **errp)
> return -1;
> }
>
> -void qmp_guest_set_user_password(const char *username,
> - const char *password,
> - bool crypted,
> - Error **errp)
> -{
> - error_setg(errp, QERR_UNSUPPORTED);
> -}
> -
> GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
> {
> error_setg(errp, QERR_UNSUPPORTED);
> diff --git a/qga/commands-linux.c b/qga/commands-linux.c
> index 615e9a0027..1f25c80482 100644
> --- a/qga/commands-linux.c
> +++ b/qga/commands-linux.c
> @@ -1629,111 +1629,6 @@ int64_t
> qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
> return processed;
> }
>
> -void qmp_guest_set_user_password(const char *username,
> - const char *password,
> - bool crypted,
> - Error **errp)
> -{
> - Error *local_err = NULL;
> - char *passwd_path = NULL;
> - pid_t pid;
> - int status;
> - int datafd[2] = { -1, -1 };
> - char *rawpasswddata = NULL;
> - size_t rawpasswdlen;
> - char *chpasswddata = NULL;
> - size_t chpasswdlen;
> -
> - rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen,
> errp);
> - if (!rawpasswddata) {
> - return;
> - }
> - rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
> - rawpasswddata[rawpasswdlen] = '\0';
> -
> - if (strchr(rawpasswddata, '\n')) {
> - error_setg(errp, "forbidden characters in raw password");
> - goto out;
> - }
> -
> - if (strchr(username, '\n') ||
> - strchr(username, ':')) {
> - error_setg(errp, "forbidden characters in username");
> - goto out;
> - }
> -
> - chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata);
> - chpasswdlen = strlen(chpasswddata);
> -
> - passwd_path = g_find_program_in_path("chpasswd");
> -
> - if (!passwd_path) {
> - error_setg(errp, "cannot find 'passwd' program in PATH");
> - goto out;
> - }
> -
> - if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) {
> - error_setg(errp, "cannot create pipe FDs");
> - goto out;
> - }
> -
> - pid = fork();
> - if (pid == 0) {
> - close(datafd[1]);
> - /* child */
> - setsid();
> - dup2(datafd[0], 0);
> - reopen_fd_to_null(1);
> - reopen_fd_to_null(2);
> -
> - if (crypted) {
> - execl(passwd_path, "chpasswd", "-e", NULL);
> - } else {
> - execl(passwd_path, "chpasswd", NULL);
> - }
> - _exit(EXIT_FAILURE);
> - } else if (pid < 0) {
> - error_setg_errno(errp, errno, "failed to create child process");
> - goto out;
> - }
> - close(datafd[0]);
> - datafd[0] = -1;
> -
> - if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) !=
> chpasswdlen) {
> - error_setg_errno(errp, errno, "cannot write new account
> password");
> - goto out;
> - }
> - close(datafd[1]);
> - datafd[1] = -1;
> -
> - ga_wait_child(pid, &status, &local_err);
> - if (local_err) {
> - error_propagate(errp, local_err);
> - goto out;
> - }
> -
> - if (!WIFEXITED(status)) {
> - error_setg(errp, "child process has terminated abnormally");
> - goto out;
> - }
> -
> - if (WEXITSTATUS(status)) {
> - error_setg(errp, "child process has failed to set user password");
> - goto out;
> - }
> -
> -out:
> - g_free(chpasswddata);
> - g_free(rawpasswddata);
> - g_free(passwd_path);
> - if (datafd[0] != -1) {
> - close(datafd[0]);
> - }
> - if (datafd[1] != -1) {
> - close(datafd[1]);
> - }
> -}
> -
> static void ga_read_sysfs_file(int dirfd, const char *pathname, char *buf,
> int size, Error **errp)
> {
> diff --git a/qga/commands-posix.c b/qga/commands-posix.c
> index 60cc673f25..e8fc7bd516 100644
> --- a/qga/commands-posix.c
> +++ b/qga/commands-posix.c
> @@ -774,8 +774,123 @@ void qmp_guest_file_flush(int64_t handle, Error
> **errp)
> }
> }
>
> -#if !(defined(__linux__) || defined(__FreeBSD__))
> +#if defined(__linux__) || defined(__FreeBSD__)
> +void qmp_guest_set_user_password(const char *username,
> + const char *password,
> + bool crypted,
> + Error **errp)
> +{
> + Error *local_err = NULL;
> + char *passwd_path = NULL;
> + pid_t pid;
> + int status;
> + int datafd[2] = { -1, -1 };
> + char *rawpasswddata = NULL;
> + size_t rawpasswdlen;
> + char *chpasswddata = NULL;
> + size_t chpasswdlen;
>
> + rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen,
> errp);
> + if (!rawpasswddata) {
> + return;
> + }
> + rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
> + rawpasswddata[rawpasswdlen] = '\0';
> +
> + if (strchr(rawpasswddata, '\n')) {
> + error_setg(errp, "forbidden characters in raw password");
> + goto out;
> + }
> +
> + if (strchr(username, '\n') ||
> + strchr(username, ':')) {
> + error_setg(errp, "forbidden characters in username");
> + goto out;
> + }
> +
> +#ifdef __FreeBSD__
> + chpasswddata = g_strdup(rawpasswddata);
> + passwd_path = g_find_program_in_path("pw");
> +#else
> + chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata);
> + passwd_path = g_find_program_in_path("chpasswd");
> +#endif
> +
> + chpasswdlen = strlen(chpasswddata);
> +
> + if (!passwd_path) {
> + error_setg(errp, "cannot find 'passwd' program in PATH");
> + goto out;
> + }
> +
> + if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) {
> + error_setg(errp, "cannot create pipe FDs");
> + goto out;
> + }
> +
> + pid = fork();
> + if (pid == 0) {
> + close(datafd[1]);
> + /* child */
> + setsid();
> + dup2(datafd[0], 0);
> + reopen_fd_to_null(1);
> + reopen_fd_to_null(2);
> +
> +#ifdef __FreeBSD__
> + const char *h_arg;
> + h_arg = (crypted) ? "-H" : "-h";
> + execl(passwd_path, "pw", "usermod", "-n", username, h_arg, "0",
> NULL);
> +#else
> + if (crypted) {
> + execl(passwd_path, "chpasswd", "-e", NULL);
> + } else {
> + execl(passwd_path, "chpasswd", NULL);
> + }
> +#endif
> + _exit(EXIT_FAILURE);
> + } else if (pid < 0) {
> + error_setg_errno(errp, errno, "failed to create child process");
> + goto out;
> + }
> + close(datafd[0]);
> + datafd[0] = -1;
> +
> + if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) !=
> chpasswdlen) {
> + error_setg_errno(errp, errno, "cannot write new account
> password");
> + goto out;
> + }
> + close(datafd[1]);
> + datafd[1] = -1;
> +
> + ga_wait_child(pid, &status, &local_err);
> + if (local_err) {
> + error_propagate(errp, local_err);
> + goto out;
> + }
> +
> + if (!WIFEXITED(status)) {
> + error_setg(errp, "child process has terminated abnormally");
> + goto out;
> + }
> +
> + if (WEXITSTATUS(status)) {
> + error_setg(errp, "child process has failed to set user password");
> + goto out;
> + }
> +
> +out:
> + g_free(chpasswddata);
> + g_free(rawpasswddata);
> + g_free(passwd_path);
> + if (datafd[0] != -1) {
> + close(datafd[0]);
> + }
> + if (datafd[1] != -1) {
> + close(datafd[1]);
> + }
> +}
> +#else
> void qmp_guest_suspend_disk(Error **errp)
> {
> error_setg(errp, QERR_UNSUPPORTED);
> --
> 2.34.1
>
>
>
--
Marc-André Lureau
[-- Attachment #2: Type: text/html, Size: 11280 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 1/5] qga: Move Linux-specific commands code to separate file
2022-09-23 7:59 ` Marc-André Lureau
@ 2022-09-23 8:51 ` Alexander Ivanov
0 siblings, 0 replies; 14+ messages in thread
From: Alexander Ivanov @ 2022-09-23 8:51 UTC (permalink / raw)
To: Marc-André Lureau; +Cc: qemu-devel, den, michael.roth, kkostiuk
Hi,
thank you for your review.
On 23.09.2022 09:59, Marc-André Lureau wrote:
> Hi
>
> On Thu, Sep 22, 2022 at 4:33 PM Alexander Ivanov
> <alexander.ivanov@virtuozzo.com> wrote:
>
> In the next patches we are going to add FreeBSD support for QEMU Guest
> Agent. In the result, code in commands-posix.c will be too cumbersome.
> Move Linux-specific code to a separate file keeping common POSIX
> code in
> commands-posix.c.
>
> Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
>
>
> lgtm, but moving 2000 lines in one patch is a bit hard to review..
> Maybe you can split this patch for the various moved commands?
> Although if enough maintainers ack this patch, it's fine for me.
OK, I will split the patch.
>
> ---
> qga/commands-common.h | 34 +
> qga/commands-linux.c | 2242 ++++++++++++++++++++++++++++++++++++
> qga/commands-posix.c | 2558
> +++--------------------------------------
> qga/meson.build | 3 +
> 4 files changed, 2447 insertions(+), 2390 deletions(-)
> create mode 100644 qga/commands-linux.c
>
> diff --git a/qga/commands-common.h b/qga/commands-common.h
> index d0e4a9696f..aa0472ea4c 100644
> --- a/qga/commands-common.h
> +++ b/qga/commands-common.h
> @@ -29,4 +29,38 @@ GuestFileRead
> *guest_file_read_unsafe(GuestFileHandle *gfh,
> */
> char *qga_get_host_name(Error **errp);
>
> +void ga_wait_child(pid_t pid, int *status, Error **errp);
> +
> +#if defined(__linux__)
> +#include <linux/fs.h>
> +#ifdef FIFREEZE
> +#define CONFIG_FSFREEZE
> #endif
> +#ifdef FITRIM
> +#define CONFIG_FSTRIM
> +#endif
> +#endif /* __linux__*/
> +
> +#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
> +typedef struct FsMount {
> + char *dirname;
> + char *devtype;
> + unsigned int devmajor, devminor;
> + QTAILQ_ENTRY(FsMount) next;
> +} FsMount;
> +
> +typedef QTAILQ_HEAD(FsMountList, FsMount) FsMountList;
> +
> +bool build_fs_mount_list(FsMountList *mounts, Error **errp);
> +void free_fs_mount_list(FsMountList *mounts);
> +#endif
> +
> +#if defined(CONFIG_FSFREEZE)
> +int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints,
> + strList *mountpoints,
> + FsMountList mounts,
> + Error **errp);
> +int qmp_guest_fsfreeze_do_thaw(Error **errp);
> +#endif
> +
> +#endif /* QGA_COMMANDS_COMMON_H */
> diff --git a/qga/commands-linux.c b/qga/commands-linux.c
> new file mode 100644
> index 0000000000..615e9a0027
> --- /dev/null
> +++ b/qga/commands-linux.c
> @@ -0,0 +1,2242 @@
> +/*
> + * QEMU Guest Agent Linux-specific command implementations
> + *
> + * Copyright IBM Corp. 2011
> + *
> + * Authors:
> + * Michael Roth <mdroth@linux.vnet.ibm.com>
> + * Michal Privoznik <mprivozn@redhat.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 "qemu/osdep.h"
> +#include <sys/ioctl.h>
> +#include "guest-agent-core.h"
> +#include "qga-qapi-commands.h"
> +#include "qapi/qmp/qerror.h"
> +#include "qapi/error.h"
> +#include "qemu/queue.h"
> +#include "qemu/base64.h"
> +#include "commands-common.h"
> +#include "block/nvme.h"
> +#include "cutils.h"
> +
> +#include <mntent.h>
> +#include <sys/statvfs.h>
> +#include <linux/nvme_ioctl.h>
> +
> +#ifdef CONFIG_LIBUDEV
> +#include <libudev.h>
> +#endif
> +
> +#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
> +static int dev_major_minor(const char *devpath,
> + unsigned int *devmajor, unsigned int
> *devminor)
> +{
> + struct stat st;
> +
> + *devmajor = 0;
> + *devminor = 0;
> +
> + if (stat(devpath, &st) < 0) {
> + slog("failed to stat device file '%s': %s", devpath,
> strerror(errno));
> + return -1;
> + }
> + if (S_ISDIR(st.st_mode)) {
> + /* It is bind mount */
> + return -2;
> + }
> + if (S_ISBLK(st.st_mode)) {
> + *devmajor = major(st.st_rdev);
> + *devminor = minor(st.st_rdev);
> + return 0;
> + }
> + return -1;
> +}
> +
> +/*
> + * Walk the mount table and build a list of local file systems
> + */
> +static bool build_fs_mount_list_from_mtab(FsMountList *mounts,
> Error **errp)
> +{
> + struct mntent *ment;
> + FsMount *mount;
> + char const *mtab = "/proc/self/mounts";
> + FILE *fp;
> + unsigned int devmajor, devminor;
> +
> + fp = setmntent(mtab, "r");
> + if (!fp) {
> + error_setg(errp, "failed to open mtab file: '%s'", mtab);
> + return false;
> + }
> +
> + while ((ment = getmntent(fp))) {
> + /*
> + * An entry which device name doesn't start with a '/' is
> + * either a dummy file system or a network file system.
> + * Add special handling for smbfs and cifs as is done by
> + * coreutils as well.
> + */
> + if ((ment->mnt_fsname[0] != '/') ||
> + (strcmp(ment->mnt_type, "smbfs") == 0) ||
> + (strcmp(ment->mnt_type, "cifs") == 0)) {
> + continue;
> + }
> + if (dev_major_minor(ment->mnt_fsname, &devmajor,
> &devminor) == -2) {
> + /* Skip bind mounts */
> + continue;
> + }
> +
> + mount = g_new0(FsMount, 1);
> + mount->dirname = g_strdup(ment->mnt_dir);
> + mount->devtype = g_strdup(ment->mnt_type);
> + mount->devmajor = devmajor;
> + mount->devminor = devminor;
> +
> + QTAILQ_INSERT_TAIL(mounts, mount, next);
> + }
> +
> + endmntent(fp);
> + return true;
> +}
> +
> +static void decode_mntname(char *name, int len)
> +{
> + int i, j = 0;
> + for (i = 0; i <= len; i++) {
> + if (name[i] != '\\') {
> + name[j++] = name[i];
> + } else if (name[i + 1] == '\\') {
> + name[j++] = '\\';
> + i++;
> + } else if (name[i + 1] >= '0' && name[i + 1] <= '3' &&
> + name[i + 2] >= '0' && name[i + 2] <= '7' &&
> + name[i + 3] >= '0' && name[i + 3] <= '7') {
> + name[j++] = (name[i + 1] - '0') * 64 +
> + (name[i + 2] - '0') * 8 +
> + (name[i + 3] - '0');
> + i += 3;
> + } else {
> + name[j++] = name[i];
> + }
> + }
> +}
> +
> +bool build_fs_mount_list(FsMountList *mounts, Error **errp)
> +{
> + FsMount *mount;
> + char const *mountinfo = "/proc/self/mountinfo";
> + FILE *fp;
> + char *line = NULL, *dash;
> + size_t n;
> + char check;
> + unsigned int devmajor, devminor;
> + int ret, dir_s, dir_e, type_s, type_e, dev_s, dev_e;
> +
> + fp = fopen(mountinfo, "r");
> + if (!fp) {
> + return build_fs_mount_list_from_mtab(mounts, errp);
> + }
> +
> + while (getline(&line, &n, fp) != -1) {
> + ret = sscanf(line, "%*u %*u %u:%u %*s %n%*s%n%c",
> + &devmajor, &devminor, &dir_s, &dir_e, &check);
> + if (ret < 3) {
> + continue;
> + }
> + dash = strstr(line + dir_e, " - ");
> + if (!dash) {
> + continue;
> + }
> + ret = sscanf(dash, " - %n%*s%n %n%*s%n%c",
> + &type_s, &type_e, &dev_s, &dev_e, &check);
> + if (ret < 1) {
> + continue;
> + }
> + line[dir_e] = 0;
> + dash[type_e] = 0;
> + dash[dev_e] = 0;
> + decode_mntname(line + dir_s, dir_e - dir_s);
> + decode_mntname(dash + dev_s, dev_e - dev_s);
> + if (devmajor == 0) {
> + /* btrfs reports major number = 0 */
> + if (strcmp("btrfs", dash + type_s) != 0 ||
> + dev_major_minor(dash + dev_s, &devmajor,
> &devminor) < 0) {
> + continue;
> + }
> + }
> +
> + mount = g_new0(FsMount, 1);
> + mount->dirname = g_strdup(line + dir_s);
> + mount->devtype = g_strdup(dash + type_s);
> + mount->devmajor = devmajor;
> + mount->devminor = devminor;
> +
> + QTAILQ_INSERT_TAIL(mounts, mount, next);
> + }
> + free(line);
> +
> + fclose(fp);
> + return true;
> +}
> +#endif
> +
> +#if defined(CONFIG_FSFREEZE)
> +
> +static char *get_pci_driver(char const *syspath, int pathlen,
> Error **errp)
> +{
> + char *path;
> + char *dpath;
> + char *driver = NULL;
> + char buf[PATH_MAX];
> + ssize_t len;
> +
> + path = g_strndup(syspath, pathlen);
> + dpath = g_strdup_printf("%s/driver", path);
> + len = readlink(dpath, buf, sizeof(buf) - 1);
> + if (len != -1) {
> + buf[len] = 0;
> + driver = g_path_get_basename(buf);
> + }
> + g_free(dpath);
> + g_free(path);
> + return driver;
> +}
> +
> +static int compare_uint(const void *_a, const void *_b)
> +{
> + unsigned int a = *(unsigned int *)_a;
> + unsigned int b = *(unsigned int *)_b;
> +
> + return a < b ? -1 : a > b ? 1 : 0;
> +}
> +
> +/* Walk the specified sysfs and build a sorted list of host or
> ata numbers */
> +static int build_hosts(char const *syspath, char const *host,
> bool ata,
> + unsigned int *hosts, int hosts_max, Error
> **errp)
> +{
> + char *path;
> + DIR *dir;
> + struct dirent *entry;
> + int i = 0;
> +
> + path = g_strndup(syspath, host - syspath);
> + dir = opendir(path);
> + if (!dir) {
> + error_setg_errno(errp, errno, "opendir(\"%s\")", path);
> + g_free(path);
> + return -1;
> + }
> +
> + while (i < hosts_max) {
> + entry = readdir(dir);
> + if (!entry) {
> + break;
> + }
> + if (ata && sscanf(entry->d_name, "ata%d", hosts + i) == 1) {
> + ++i;
> + } else if (!ata && sscanf(entry->d_name, "host%d", hosts
> + i) == 1) {
> + ++i;
> + }
> + }
> +
> + qsort(hosts, i, sizeof(hosts[0]), compare_uint);
> +
> + g_free(path);
> + closedir(dir);
> + return i;
> +}
> +
> +/*
> + * Store disk device info for devices on the PCI bus.
> + * Returns true if information has been stored, or false for failure.
> + */
> +static bool build_guest_fsinfo_for_pci_dev(char const *syspath,
> + GuestDiskAddress *disk,
> + Error **errp)
> +{
> + unsigned int pci[4], host, hosts[8], tgt[3];
> + int i, nhosts = 0, pcilen;
> + GuestPCIAddress *pciaddr = disk->pci_controller;
> + bool has_ata = false, has_host = false, has_tgt = false;
> + char *p, *q, *driver = NULL;
> + bool ret = false;
> +
> + p = strstr(syspath, "/devices/pci");
> + if (!p || sscanf(p + 12, "%*x:%*x/%x:%x:%x.%x%n",
> + pci, pci + 1, pci + 2, pci + 3, &pcilen) < 4) {
> + g_debug("only pci device is supported: sysfs path '%s'",
> syspath);
> + return false;
> + }
> +
> + p += 12 + pcilen;
> + while (true) {
> + driver = get_pci_driver(syspath, p - syspath, errp);
> + if (driver && (g_str_equal(driver, "ata_piix") ||
> + g_str_equal(driver, "sym53c8xx") ||
> + g_str_equal(driver, "virtio-pci") ||
> + g_str_equal(driver, "ahci") ||
> + g_str_equal(driver, "nvme"))) {
> + break;
> + }
> +
> + g_free(driver);
> + if (sscanf(p, "/%x:%x:%x.%x%n",
> + pci, pci + 1, pci + 2, pci + 3,
> &pcilen) == 4) {
> + p += pcilen;
> + continue;
> + }
> +
> + g_debug("unsupported driver or sysfs path '%s'", syspath);
> + return false;
> + }
> +
> + p = strstr(syspath, "/target");
> + if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u",
> + tgt, tgt + 1, tgt + 2) == 3) {
> + has_tgt = true;
> + }
> +
> + p = strstr(syspath, "/ata");
> + if (p) {
> + q = p + 4;
> + has_ata = true;
> + } else {
> + p = strstr(syspath, "/host");
> + q = p + 5;
> + }
> + if (p && sscanf(q, "%u", &host) == 1) {
> + has_host = true;
> + nhosts = build_hosts(syspath, p, has_ata, hosts,
> + ARRAY_SIZE(hosts), errp);
> + if (nhosts < 0) {
> + goto cleanup;
> + }
> + }
> +
> + pciaddr->domain = pci[0];
> + pciaddr->bus = pci[1];
> + pciaddr->slot = pci[2];
> + pciaddr->function = pci[3];
> +
> + if (strcmp(driver, "ata_piix") == 0) {
> + /* a host per ide bus, target*:0:<unit>:0 */
> + if (!has_host || !has_tgt) {
> + g_debug("invalid sysfs path '%s' (driver '%s')",
> syspath, driver);
> + goto cleanup;
> + }
> + for (i = 0; i < nhosts; i++) {
> + if (host == hosts[i]) {
> + disk->bus_type = GUEST_DISK_BUS_TYPE_IDE;
> + disk->bus = i;
> + disk->unit = tgt[1];
> + break;
> + }
> + }
> + if (i >= nhosts) {
> + g_debug("no host for '%s' (driver '%s')", syspath,
> driver);
> + goto cleanup;
> + }
> + } else if (strcmp(driver, "sym53c8xx") == 0) {
> + /* scsi(LSI Logic): target*:0:<unit>:0 */
> + if (!has_tgt) {
> + g_debug("invalid sysfs path '%s' (driver '%s')",
> syspath, driver);
> + goto cleanup;
> + }
> + disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
> + disk->unit = tgt[1];
> + } else if (strcmp(driver, "virtio-pci") == 0) {
> + if (has_tgt) {
> + /* virtio-scsi: target*:0:0:<unit> */
> + disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
> + disk->unit = tgt[2];
> + } else {
> + /* virtio-blk: 1 disk per 1 device */
> + disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
> + }
> + } else if (strcmp(driver, "ahci") == 0) {
> + /* ahci: 1 host per 1 unit */
> + if (!has_host || !has_tgt) {
> + g_debug("invalid sysfs path '%s' (driver '%s')",
> syspath, driver);
> + goto cleanup;
> + }
> + for (i = 0; i < nhosts; i++) {
> + if (host == hosts[i]) {
> + disk->unit = i;
> + disk->bus_type = GUEST_DISK_BUS_TYPE_SATA;
> + break;
> + }
> + }
> + if (i >= nhosts) {
> + g_debug("no host for '%s' (driver '%s')", syspath,
> driver);
> + goto cleanup;
> + }
> + } else if (strcmp(driver, "nvme") == 0) {
> + disk->bus_type = GUEST_DISK_BUS_TYPE_NVME;
> + } else {
> + g_debug("unknown driver '%s' (sysfs path '%s')", driver,
> syspath);
> + goto cleanup;
> + }
> +
> + ret = true;
> +
> +cleanup:
> + g_free(driver);
> + return ret;
> +}
> +
> +/*
> + * Store disk device info for non-PCI virtio devices (for example
> s390x
> + * channel I/O devices). Returns true if information has been
> stored, or
> + * false for failure.
> + */
> +static bool build_guest_fsinfo_for_nonpci_virtio(char const *syspath,
> + GuestDiskAddress *disk,
> + Error **errp)
> +{
> + unsigned int tgt[3];
> + char *p;
> +
> + if (!strstr(syspath, "/virtio") || !strstr(syspath, "/block")) {
> + g_debug("Unsupported virtio device '%s'", syspath);
> + return false;
> + }
> +
> + p = strstr(syspath, "/target");
> + if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u",
> + &tgt[0], &tgt[1], &tgt[2]) == 3) {
> + /* virtio-scsi: target*:0:<target>:<unit> */
> + disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
> + disk->bus = tgt[0];
> + disk->target = tgt[1];
> + disk->unit = tgt[2];
> + } else {
> + /* virtio-blk: 1 disk per 1 device */
> + disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
> + }
> +
> + return true;
> +}
> +
> +/*
> + * Store disk device info for CCW devices (s390x channel I/O
> devices).
> + * Returns true if information has been stored, or false for failure.
> + */
> +static bool build_guest_fsinfo_for_ccw_dev(char const *syspath,
> + GuestDiskAddress *disk,
> + Error **errp)
> +{
> + unsigned int cssid, ssid, subchno, devno;
> + char *p;
> +
> + p = strstr(syspath, "/devices/css");
> + if (!p || sscanf(p + 12, "%*x/%x.%x.%x/%*x.%*x.%x/",
> + &cssid, &ssid, &subchno, &devno) < 4) {
> + g_debug("could not parse ccw device sysfs path: %s",
> syspath);
> + return false;
> + }
> +
> + disk->has_ccw_address = true;
> + disk->ccw_address = g_new0(GuestCCWAddress, 1);
> + disk->ccw_address->cssid = cssid;
> + disk->ccw_address->ssid = ssid;
> + disk->ccw_address->subchno = subchno;
> + disk->ccw_address->devno = devno;
> +
> + if (strstr(p, "/virtio")) {
> + build_guest_fsinfo_for_nonpci_virtio(syspath, disk, errp);
> + }
> +
> + return true;
> +}
> +
> +/* Store disk device info specified by @sysfs into @fs */
> +static void build_guest_fsinfo_for_real_device(char const *syspath,
> + GuestFilesystemInfo *fs,
> + Error **errp)
> +{
> + GuestDiskAddress *disk;
> + GuestPCIAddress *pciaddr;
> + bool has_hwinf;
> +#ifdef CONFIG_LIBUDEV
> + struct udev *udev = NULL;
> + struct udev_device *udevice = NULL;
> +#endif
> +
> + pciaddr = g_new0(GuestPCIAddress, 1);
> + pciaddr->domain = -1; /* -1 means field
> is invalid */
> + pciaddr->bus = -1;
> + pciaddr->slot = -1;
> + pciaddr->function = -1;
> +
> + disk = g_new0(GuestDiskAddress, 1);
> + disk->pci_controller = pciaddr;
> + disk->bus_type = GUEST_DISK_BUS_TYPE_UNKNOWN;
> +
> +#ifdef CONFIG_LIBUDEV
> + udev = udev_new();
> + udevice = udev_device_new_from_syspath(udev, syspath);
> + if (udev == NULL || udevice == NULL) {
> + g_debug("failed to query udev");
> + } else {
> + const char *devnode, *serial;
> + devnode = udev_device_get_devnode(udevice);
> + if (devnode != NULL) {
> + disk->dev = g_strdup(devnode);
> + disk->has_dev = true;
> + }
> + serial = udev_device_get_property_value(udevice,
> "ID_SERIAL");
> + if (serial != NULL && *serial != 0) {
> + disk->serial = g_strdup(serial);
> + disk->has_serial = true;
> + }
> + }
> +
> + udev_unref(udev);
> + udev_device_unref(udevice);
> +#endif
> +
> + if (strstr(syspath, "/devices/pci")) {
> + has_hwinf = build_guest_fsinfo_for_pci_dev(syspath, disk,
> errp);
> + } else if (strstr(syspath, "/devices/css")) {
> + has_hwinf = build_guest_fsinfo_for_ccw_dev(syspath, disk,
> errp);
> + } else if (strstr(syspath, "/virtio")) {
> + has_hwinf = build_guest_fsinfo_for_nonpci_virtio(syspath,
> disk, errp);
> + } else {
> + g_debug("Unsupported device type for '%s'", syspath);
> + has_hwinf = false;
> + }
> +
> + if (has_hwinf || disk->has_dev || disk->has_serial) {
> + QAPI_LIST_PREPEND(fs->disk, disk);
> + } else {
> + qapi_free_GuestDiskAddress(disk);
> + }
> +}
> +
> +static void build_guest_fsinfo_for_device(char const *devpath,
> + GuestFilesystemInfo *fs,
> + Error **errp);
> +
> +/* Store a list of slave devices of virtual volume specified by
> @syspath into
> + * @fs */
> +static void build_guest_fsinfo_for_virtual_device(char const
> *syspath,
> + GuestFilesystemInfo *fs,
> + Error **errp)
> +{
> + Error *err = NULL;
> + DIR *dir;
> + char *dirpath;
> + struct dirent *entry;
> +
> + dirpath = g_strdup_printf("%s/slaves", syspath);
> + dir = opendir(dirpath);
> + if (!dir) {
> + if (errno != ENOENT) {
> + error_setg_errno(errp, errno, "opendir(\"%s\")",
> dirpath);
> + }
> + g_free(dirpath);
> + return;
> + }
> +
> + for (;;) {
> + errno = 0;
> + entry = readdir(dir);
> + if (entry == NULL) {
> + if (errno) {
> + error_setg_errno(errp, errno, "readdir(\"%s\")",
> dirpath);
> + }
> + break;
> + }
> +
> + if (entry->d_type == DT_LNK) {
> + char *path;
> +
> + g_debug(" slave device '%s'", entry->d_name);
> + path = g_strdup_printf("%s/slaves/%s", syspath,
> entry->d_name);
> + build_guest_fsinfo_for_device(path, fs, &err);
> + g_free(path);
> +
> + if (err) {
> + error_propagate(errp, err);
> + break;
> + }
> + }
> + }
> +
> + g_free(dirpath);
> + closedir(dir);
> +}
> +
> +static bool is_disk_virtual(const char *devpath, Error **errp)
> +{
> + g_autofree char *syspath = realpath(devpath, NULL);
> +
> + if (!syspath) {
> + error_setg_errno(errp, errno, "realpath(\"%s\")", devpath);
> + return false;
> + }
> + return strstr(syspath, "/devices/virtual/block/") != NULL;
> +}
> +
> +/* Dispatch to functions for virtual/real device */
> +static void build_guest_fsinfo_for_device(char const *devpath,
> + GuestFilesystemInfo *fs,
> + Error **errp)
> +{
> + ERRP_GUARD();
> + g_autofree char *syspath = NULL;
> + bool is_virtual = false;
> +
> + syspath = realpath(devpath, NULL);
> + if (!syspath) {
> + if (errno != ENOENT) {
> + error_setg_errno(errp, errno, "realpath(\"%s\")",
> devpath);
> + return;
> + }
> +
> + /* ENOENT: This devpath may not exist because of
> container config */
> + if (!fs->name) {
> + fs->name = g_path_get_basename(devpath);
> + }
> + return;
> + }
> +
> + if (!fs->name) {
> + fs->name = g_path_get_basename(syspath);
> + }
> +
> + g_debug(" parse sysfs path '%s'", syspath);
> + is_virtual = is_disk_virtual(syspath, errp);
> + if (*errp != NULL) {
> + return;
> + }
> + if (is_virtual) {
> + build_guest_fsinfo_for_virtual_device(syspath, fs, errp);
> + } else {
> + build_guest_fsinfo_for_real_device(syspath, fs, errp);
> + }
> +}
> +
> +#ifdef CONFIG_LIBUDEV
> +
> +/*
> + * Wrapper around build_guest_fsinfo_for_device() for getting just
> + * the disk address.
> + */
> +static GuestDiskAddress *get_disk_address(const char *syspath,
> Error **errp)
> +{
> + g_autoptr(GuestFilesystemInfo) fs = NULL;
> +
> + fs = g_new0(GuestFilesystemInfo, 1);
> + build_guest_fsinfo_for_device(syspath, fs, errp);
> + if (fs->disk != NULL) {
> + return g_steal_pointer(&fs->disk->value);
> + }
> + return NULL;
> +}
> +
> +static char *get_alias_for_syspath(const char *syspath)
> +{
> + struct udev *udev = NULL;
> + struct udev_device *udevice = NULL;
> + char *ret = NULL;
> +
> + udev = udev_new();
> + if (udev == NULL) {
> + g_debug("failed to query udev");
> + goto out;
> + }
> + udevice = udev_device_new_from_syspath(udev, syspath);
> + if (udevice == NULL) {
> + g_debug("failed to query udev for path: %s", syspath);
> + goto out;
> + } else {
> + const char *alias = udev_device_get_property_value(
> + udevice, "DM_NAME");
> + /*
> + * NULL means there was an error and empty string means
> there is no
> + * alias. In case of no alias we return NULL instead of
> empty string.
> + */
> + if (alias == NULL) {
> + g_debug("failed to query udev for device alias for: %s",
> + syspath);
> + } else if (*alias != 0) {
> + ret = g_strdup(alias);
> + }
> + }
> +
> +out:
> + udev_unref(udev);
> + udev_device_unref(udevice);
> + return ret;
> +}
> +
> +static char *get_device_for_syspath(const char *syspath)
> +{
> + struct udev *udev = NULL;
> + struct udev_device *udevice = NULL;
> + char *ret = NULL;
> +
> + udev = udev_new();
> + if (udev == NULL) {
> + g_debug("failed to query udev");
> + goto out;
> + }
> + udevice = udev_device_new_from_syspath(udev, syspath);
> + if (udevice == NULL) {
> + g_debug("failed to query udev for path: %s", syspath);
> + goto out;
> + } else {
> + ret = g_strdup(udev_device_get_devnode(udevice));
> + }
> +
> +out:
> + udev_unref(udev);
> + udev_device_unref(udevice);
> + return ret;
> +}
> +
> +static void get_disk_deps(const char *disk_dir, GuestDiskInfo *disk)
> +{
> + g_autofree char *deps_dir = NULL;
> + const gchar *dep;
> + GDir *dp_deps = NULL;
> +
> + /* List dependent disks */
> + deps_dir = g_strdup_printf("%s/slaves", disk_dir);
> + g_debug(" listing entries in: %s", deps_dir);
> + dp_deps = g_dir_open(deps_dir, 0, NULL);
> + if (dp_deps == NULL) {
> + g_debug("failed to list entries in %s", deps_dir);
> + return;
> + }
> + disk->has_dependencies = true;
> + while ((dep = g_dir_read_name(dp_deps)) != NULL) {
> + g_autofree char *dep_dir = NULL;
> + char *dev_name;
> +
> + /* Add dependent disks */
> + dep_dir = g_strdup_printf("%s/%s", deps_dir, dep);
> + dev_name = get_device_for_syspath(dep_dir);
> + if (dev_name != NULL) {
> + g_debug(" adding dependent device: %s", dev_name);
> + QAPI_LIST_PREPEND(disk->dependencies, dev_name);
> + }
> + }
> + g_dir_close(dp_deps);
> +}
> +
> +/*
> + * Detect partitions subdirectory, name is "<disk_name><number>" or
> + * "<disk_name>p<number>"
> + *
> + * @disk_name -- last component of /sys path (e.g. sda)
> + * @disk_dir -- sys path of the disk (e.g. /sys/block/sda)
> + * @disk_dev -- device node of the disk (e.g. /dev/sda)
> + */
> +static GuestDiskInfoList *get_disk_partitions(
> + GuestDiskInfoList *list,
> + const char *disk_name, const char *disk_dir,
> + const char *disk_dev)
> +{
> + GuestDiskInfoList *ret = list;
> + struct dirent *de_disk;
> + DIR *dp_disk = NULL;
> + size_t len = strlen(disk_name);
> +
> + dp_disk = opendir(disk_dir);
> + while ((de_disk = readdir(dp_disk)) != NULL) {
> + g_autofree char *partition_dir = NULL;
> + char *dev_name;
> + GuestDiskInfo *partition;
> +
> + if (!(de_disk->d_type & DT_DIR)) {
> + continue;
> + }
> +
> + if (!(strncmp(disk_name, de_disk->d_name, len) == 0 &&
> + ((*(de_disk->d_name + len) == 'p' &&
> + isdigit(*(de_disk->d_name + len + 1))) ||
> + isdigit(*(de_disk->d_name + len))))) {
> + continue;
> + }
> +
> + partition_dir = g_strdup_printf("%s/%s",
> + disk_dir, de_disk->d_name);
> + dev_name = get_device_for_syspath(partition_dir);
> + if (dev_name == NULL) {
> + g_debug("Failed to get device name for syspath: %s",
> + disk_dir);
> + continue;
> + }
> + partition = g_new0(GuestDiskInfo, 1);
> + partition->name = dev_name;
> + partition->partition = true;
> + partition->has_dependencies = true;
> + /* Add parent disk as dependent for easier tracking of
> hierarchy */
> + QAPI_LIST_PREPEND(partition->dependencies,
> g_strdup(disk_dev));
> +
> + QAPI_LIST_PREPEND(ret, partition);
> + }
> + closedir(dp_disk);
> +
> + return ret;
> +}
> +
> +static void get_nvme_smart(GuestDiskInfo *disk)
> +{
> + int fd;
> + GuestNVMeSmart *smart;
> + NvmeSmartLog log = {0};
> + struct nvme_admin_cmd cmd = {
> + .opcode = NVME_ADM_CMD_GET_LOG_PAGE,
> + .nsid = NVME_NSID_BROADCAST,
> + .addr = (uintptr_t)&log,
> + .data_len = sizeof(log),
> + .cdw10 = NVME_LOG_SMART_INFO | (1 << 15) /* RAE bit */
> + | (((sizeof(log) >> 2) - 1) << 16)
> + };
> +
> + fd = qga_open_cloexec(disk->name, O_RDONLY, 0);
> + if (fd == -1) {
> + g_debug("Failed to open device: %s: %s", disk->name,
> g_strerror(errno));
> + return;
> + }
> +
> + if (ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd)) {
> + g_debug("Failed to get smart: %s: %s", disk->name,
> g_strerror(errno));
> + close(fd);
> + return;
> + }
> +
> + disk->has_smart = true;
> + disk->smart = g_new0(GuestDiskSmart, 1);
> + disk->smart->type = GUEST_DISK_BUS_TYPE_NVME;
> +
> + smart = &disk->smart->u.nvme;
> + smart->critical_warning = log.critical_warning;
> + smart->temperature = lduw_le_p(&log.temperature); /*
> unaligned field */
> + smart->available_spare = log.available_spare;
> + smart->available_spare_threshold = log.available_spare_threshold;
> + smart->percentage_used = log.percentage_used;
> + smart->data_units_read_lo = le64_to_cpu(log.data_units_read[0]);
> + smart->data_units_read_hi = le64_to_cpu(log.data_units_read[1]);
> + smart->data_units_written_lo =
> le64_to_cpu(log.data_units_written[0]);
> + smart->data_units_written_hi =
> le64_to_cpu(log.data_units_written[1]);
> + smart->host_read_commands_lo =
> le64_to_cpu(log.host_read_commands[0]);
> + smart->host_read_commands_hi =
> le64_to_cpu(log.host_read_commands[1]);
> + smart->host_write_commands_lo =
> le64_to_cpu(log.host_write_commands[0]);
> + smart->host_write_commands_hi =
> le64_to_cpu(log.host_write_commands[1]);
> + smart->controller_busy_time_lo =
> le64_to_cpu(log.controller_busy_time[0]);
> + smart->controller_busy_time_hi =
> le64_to_cpu(log.controller_busy_time[1]);
> + smart->power_cycles_lo = le64_to_cpu(log.power_cycles[0]);
> + smart->power_cycles_hi = le64_to_cpu(log.power_cycles[1]);
> + smart->power_on_hours_lo = le64_to_cpu(log.power_on_hours[0]);
> + smart->power_on_hours_hi = le64_to_cpu(log.power_on_hours[1]);
> + smart->unsafe_shutdowns_lo =
> le64_to_cpu(log.unsafe_shutdowns[0]);
> + smart->unsafe_shutdowns_hi =
> le64_to_cpu(log.unsafe_shutdowns[1]);
> + smart->media_errors_lo = le64_to_cpu(log.media_errors[0]);
> + smart->media_errors_hi = le64_to_cpu(log.media_errors[1]);
> + smart->number_of_error_log_entries_lo =
> + le64_to_cpu(log.number_of_error_log_entries[0]);
> + smart->number_of_error_log_entries_hi =
> + le64_to_cpu(log.number_of_error_log_entries[1]);
> +
> + close(fd);
> +}
> +
> +static void get_disk_smart(GuestDiskInfo *disk)
> +{
> + if (disk->has_address
> + && (disk->address->bus_type == GUEST_DISK_BUS_TYPE_NVME)) {
> + get_nvme_smart(disk);
> + }
> +}
> +
> +GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
> +{
> + GuestDiskInfoList *ret = NULL;
> + GuestDiskInfo *disk;
> + DIR *dp = NULL;
> + struct dirent *de = NULL;
> +
> + g_debug("listing /sys/block directory");
> + dp = opendir("/sys/block");
> + if (dp == NULL) {
> + error_setg_errno(errp, errno, "Can't open directory
> \"/sys/block\"");
> + return NULL;
> + }
> + while ((de = readdir(dp)) != NULL) {
> + g_autofree char *disk_dir = NULL, *line = NULL,
> + *size_path = NULL;
> + char *dev_name;
> + Error *local_err = NULL;
> + if (de->d_type != DT_LNK) {
> + g_debug(" skipping entry: %s", de->d_name);
> + continue;
> + }
> +
> + /* Check size and skip zero-sized disks */
> + g_debug(" checking disk size");
> + size_path = g_strdup_printf("/sys/block/%s/size",
> de->d_name);
> + if (!g_file_get_contents(size_path, &line, NULL, NULL)) {
> + g_debug(" failed to read disk size");
> + continue;
> + }
> + if (g_strcmp0(line, "0\n") == 0) {
> + g_debug(" skipping zero-sized disk");
> + continue;
> + }
> +
> + g_debug(" adding %s", de->d_name);
> + disk_dir = g_strdup_printf("/sys/block/%s", de->d_name);
> + dev_name = get_device_for_syspath(disk_dir);
> + if (dev_name == NULL) {
> + g_debug("Failed to get device name for syspath: %s",
> + disk_dir);
> + continue;
> + }
> + disk = g_new0(GuestDiskInfo, 1);
> + disk->name = dev_name;
> + disk->partition = false;
> + disk->alias = get_alias_for_syspath(disk_dir);
> + disk->has_alias = (disk->alias != NULL);
> + QAPI_LIST_PREPEND(ret, disk);
> +
> + /* Get address for non-virtual devices */
> + bool is_virtual = is_disk_virtual(disk_dir, &local_err);
> + if (local_err != NULL) {
> + g_debug(" failed to check disk path, ignoring error:
> %s",
> + error_get_pretty(local_err));
> + error_free(local_err);
> + local_err = NULL;
> + /* Don't try to get the address */
> + is_virtual = true;
> + }
> + if (!is_virtual) {
> + disk->address = get_disk_address(disk_dir, &local_err);
> + if (local_err != NULL) {
> + g_debug(" failed to get device info, ignoring
> error: %s",
> + error_get_pretty(local_err));
> + error_free(local_err);
> + local_err = NULL;
> + } else if (disk->address != NULL) {
> + disk->has_address = true;
> + }
> + }
> +
> + get_disk_deps(disk_dir, disk);
> + get_disk_smart(disk);
> + ret = get_disk_partitions(ret, de->d_name, disk_dir,
> dev_name);
> + }
> +
> + closedir(dp);
> +
> + return ret;
> +}
> +
> +#else
> +
> +GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +#endif
> +
> +/* Return a list of the disk device(s)' info which @mount lies on */
> +static GuestFilesystemInfo *build_guest_fsinfo(struct FsMount *mount,
> + Error **errp)
> +{
> + GuestFilesystemInfo *fs = g_malloc0(sizeof(*fs));
> + struct statvfs buf;
> + unsigned long used, nonroot_total, fr_size;
> + char *devpath = g_strdup_printf("/sys/dev/block/%u:%u",
> + mount->devmajor,
> mount->devminor);
> +
> + fs->mountpoint = g_strdup(mount->dirname);
> + fs->type = g_strdup(mount->devtype);
> + build_guest_fsinfo_for_device(devpath, fs, errp);
> +
> + if (statvfs(fs->mountpoint, &buf) == 0) {
> + fr_size = buf.f_frsize;
> + used = buf.f_blocks - buf.f_bfree;
> + nonroot_total = used + buf.f_bavail;
> + fs->used_bytes = used * fr_size;
> + fs->total_bytes = nonroot_total * fr_size;
> +
> + fs->has_total_bytes = true;
> + fs->has_used_bytes = true;
> + }
> +
> + g_free(devpath);
> +
> + return fs;
> +}
> +
> +GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
> +{
> + FsMountList mounts;
> + struct FsMount *mount;
> + GuestFilesystemInfoList *ret = NULL;
> + Error *local_err = NULL;
> +
> + QTAILQ_INIT(&mounts);
> + if (!build_fs_mount_list(&mounts, &local_err)) {
> + error_propagate(errp, local_err);
> + return NULL;
> + }
> +
> + QTAILQ_FOREACH(mount, &mounts, next) {
> + g_debug("Building guest fsinfo for '%s'", mount->dirname);
> +
> + QAPI_LIST_PREPEND(ret, build_guest_fsinfo(mount,
> &local_err));
> + if (local_err) {
> + error_propagate(errp, local_err);
> + qapi_free_GuestFilesystemInfoList(ret);
> + ret = NULL;
> + break;
> + }
> + }
> +
> + free_fs_mount_list(&mounts);
> + return ret;
> +}
> +
> +/*
> + * Walk list of mounted file systems in the guest, and freeze the
> ones which
> + * are real local file systems.
> + */
> +int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints,
> + strList *mountpoints,
> + FsMountList mounts,
> + Error **errp)
> +{
> + struct FsMount *mount;
> + strList *list;
> + int fd, ret, i = 0;
> +
> + QTAILQ_FOREACH_REVERSE(mount, &mounts, next) {
> + /* To issue fsfreeze in the reverse order of mounts,
> check if the
> + * mount is listed in the list here */
> + if (has_mountpoints) {
> + for (list = mountpoints; list; list = list->next) {
> + if (strcmp(list->value, mount->dirname) == 0) {
> + break;
> + }
> + }
> + if (!list) {
> + continue;
> + }
> + }
> +
> + fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
> + if (fd == -1) {
> + error_setg_errno(errp, errno, "failed to open %s",
> mount->dirname);
> + return -1;
> + }
> +
> + /* we try to cull filesystems we know won't work in
> advance, but other
> + * filesystems may not implement fsfreeze for less
> obvious reasons.
> + * these will report EOPNOTSUPP. we simply ignore these
> when tallying
> + * the number of frozen filesystems.
> + * if a filesystem is mounted more than once (aka bind
> mount) a
> + * consecutive attempt to freeze an already frozen
> filesystem will
> + * return EBUSY.
> + *
> + * any other error means a failure to freeze a filesystem we
> + * expect to be freezable, so return an error in those cases
> + * and return system to thawed state.
> + */
> + ret = ioctl(fd, FIFREEZE);
> + if (ret == -1) {
> + if (errno != EOPNOTSUPP && errno != EBUSY) {
> + error_setg_errno(errp, errno, "failed to freeze %s",
> + mount->dirname);
> + close(fd);
> + return -1;
> + }
> + } else {
> + i++;
> + }
> + close(fd);
> + }
> + return i;
> +}
> +
> +int qmp_guest_fsfreeze_do_thaw(Error **errp)
> +{
> + int ret;
> + FsMountList mounts;
> + FsMount *mount;
> + int fd, i = 0, logged;
> + Error *local_err = NULL;
> +
> + QTAILQ_INIT(&mounts);
> + if (!build_fs_mount_list(&mounts, &local_err)) {
> + error_propagate(errp, local_err);
> + return -1;
> + }
> +
> + QTAILQ_FOREACH(mount, &mounts, next) {
> + logged = false;
> + fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
> + if (fd == -1) {
> + continue;
> + }
> + /* we have no way of knowing whether a filesystem was
> actually unfrozen
> + * as a result of a successful call to FITHAW, only that
> if an error
> + * was returned the filesystem was *not* unfrozen by that
> particular
> + * call.
> + *
> + * since multiple preceding FIFREEZEs require multiple
> calls to FITHAW
> + * to unfreeze, continuing issuing FITHAW until an error
> is returned,
> + * in which case either the filesystem is in an
> unfreezable state, or,
> + * more likely, it was thawed previously (and remains so
> afterward).
> + *
> + * also, since the most recent successful call is the one
> that did
> + * the actual unfreeze, we can use this to provide an
> accurate count
> + * of the number of filesystems unfrozen by
> guest-fsfreeze-thaw, which
> + * may * be useful for determining whether a filesystem
> was unfrozen
> + * during the freeze/thaw phase by a process other than
> qemu-ga.
> + */
> + do {
> + ret = ioctl(fd, FITHAW);
> + if (ret == 0 && !logged) {
> + i++;
> + logged = true;
> + }
> + } while (ret == 0);
> + close(fd);
> + }
> +
> + free_fs_mount_list(&mounts);
> +
> + return i;
> +}
> +#endif /* CONFIG_FSFREEZE */
> +
> +#if defined(CONFIG_FSTRIM)
> +/*
> + * Walk list of mounted file systems in the guest, and trim them.
> + */
> +GuestFilesystemTrimResponse *
> +qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
> +{
> + GuestFilesystemTrimResponse *response;
> + GuestFilesystemTrimResult *result;
> + int ret = 0;
> + FsMountList mounts;
> + struct FsMount *mount;
> + int fd;
> + struct fstrim_range r;
> +
> + slog("guest-fstrim called");
> +
> + QTAILQ_INIT(&mounts);
> + if (!build_fs_mount_list(&mounts, errp)) {
> + return NULL;
> + }
> +
> + response = g_malloc0(sizeof(*response));
> +
> + QTAILQ_FOREACH(mount, &mounts, next) {
> + result = g_malloc0(sizeof(*result));
> + result->path = g_strdup(mount->dirname);
> +
> + QAPI_LIST_PREPEND(response->paths, result);
> +
> + fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
> + if (fd == -1) {
> + result->error = g_strdup_printf("failed to open: %s",
> + strerror(errno));
> + result->has_error = true;
> + continue;
> + }
> +
> + /* We try to cull filesystems we know won't work in
> advance, but other
> + * filesystems may not implement fstrim for less obvious
> reasons.
> + * These will report EOPNOTSUPP; while in some other
> cases ENOTTY
> + * will be reported (e.g. CD-ROMs).
> + * Any other error means an unexpected error.
> + */
> + r.start = 0;
> + r.len = -1;
> + r.minlen = has_minimum ? minimum : 0;
> + ret = ioctl(fd, FITRIM, &r);
> + if (ret == -1) {
> + result->has_error = true;
> + if (errno == ENOTTY || errno == EOPNOTSUPP) {
> + result->error = g_strdup("trim not supported");
> + } else {
> + result->error = g_strdup_printf("failed to trim: %s",
> + strerror(errno));
> + }
> + close(fd);
> + continue;
> + }
> +
> + result->has_minimum = true;
> + result->minimum = r.minlen;
> + result->has_trimmed = true;
> + result->trimmed = r.len;
> + close(fd);
> + }
> +
> + free_fs_mount_list(&mounts);
> + return response;
> +}
> +#endif /* CONFIG_FSTRIM */
> +
> +
> +#define LINUX_SYS_STATE_FILE "/sys/power/state"
> +#define SUSPEND_SUPPORTED 0
> +#define SUSPEND_NOT_SUPPORTED 1
> +
> +typedef enum {
> + SUSPEND_MODE_DISK = 0,
> + SUSPEND_MODE_RAM = 1,
> + SUSPEND_MODE_HYBRID = 2,
> +} SuspendMode;
> +
> +/*
> + * Executes a command in a child process using g_spawn_sync,
> + * returning an int >= 0 representing the exit status of the
> + * process.
> + *
> + * If the program wasn't found in path, returns -1.
> + *
> + * If a problem happened when creating the child process,
> + * returns -1 and errp is set.
> + */
> +static int run_process_child(const char *command[], Error **errp)
> +{
> + int exit_status, spawn_flag;
> + GError *g_err = NULL;
> + bool success;
> +
> + spawn_flag = G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL |
> + G_SPAWN_STDERR_TO_DEV_NULL;
> +
> + success = g_spawn_sync(NULL, (char **)command, NULL, spawn_flag,
> + NULL, NULL, NULL, NULL,
> + &exit_status, &g_err);
> +
> + if (success) {
> + return WEXITSTATUS(exit_status);
> + }
> +
> + if (g_err && (g_err->code != G_SPAWN_ERROR_NOENT)) {
> + error_setg(errp, "failed to create child process, error
> '%s'",
> + g_err->message);
> + }
> +
> + g_error_free(g_err);
> + return -1;
> +}
> +
> +static bool systemd_supports_mode(SuspendMode mode, Error **errp)
> +{
> + const char *systemctl_args[3] = {"systemd-hibernate",
> "systemd-suspend",
> + "systemd-hybrid-sleep"};
> + const char *cmd[4] = {"systemctl", "status",
> systemctl_args[mode], NULL};
> + int status;
> +
> + status = run_process_child(cmd, errp);
> +
> + /*
> + * systemctl status uses LSB return codes so we can expect
> + * status > 0 and be ok. To assert if the guest has support
> + * for the selected suspend mode, status should be < 4. 4 is
> + * the code for unknown service status, the return value when
> + * the service does not exist. A common value is status = 3
> + * (program is not running).
> + */
> + if (status > 0 && status < 4) {
> + return true;
> + }
> +
> + return false;
> +}
> +
> +static void systemd_suspend(SuspendMode mode, Error **errp)
> +{
> + Error *local_err = NULL;
> + const char *systemctl_args[3] = {"hibernate", "suspend",
> "hybrid-sleep"};
> + const char *cmd[3] = {"systemctl", systemctl_args[mode], NULL};
> + int status;
> +
> + status = run_process_child(cmd, &local_err);
> +
> + if (status == 0) {
> + return;
> + }
> +
> + if ((status == -1) && !local_err) {
> + error_setg(errp, "the helper program 'systemctl %s' was
> not found",
> + systemctl_args[mode]);
> + return;
> + }
> +
> + if (local_err) {
> + error_propagate(errp, local_err);
> + } else {
> + error_setg(errp, "the helper program 'systemctl %s'
> returned an "
> + "unexpected exit status code (%d)",
> + systemctl_args[mode], status);
> + }
> +}
> +
> +static bool pmutils_supports_mode(SuspendMode mode, Error **errp)
> +{
> + Error *local_err = NULL;
> + const char *pmutils_args[3] = {"--hibernate", "--suspend",
> + "--suspend-hybrid"};
> + const char *cmd[3] = {"pm-is-supported", pmutils_args[mode],
> NULL};
> + int status;
> +
> + status = run_process_child(cmd, &local_err);
> +
> + if (status == SUSPEND_SUPPORTED) {
> + return true;
> + }
> +
> + if ((status == -1) && !local_err) {
> + return false;
> + }
> +
> + if (local_err) {
> + error_propagate(errp, local_err);
> + } else {
> + error_setg(errp,
> + "the helper program '%s' returned an
> unexpected exit"
> + " status code (%d)", "pm-is-supported", status);
> + }
> +
> + return false;
> +}
> +
> +static void pmutils_suspend(SuspendMode mode, Error **errp)
> +{
> + Error *local_err = NULL;
> + const char *pmutils_binaries[3] = {"pm-hibernate", "pm-suspend",
> + "pm-suspend-hybrid"};
> + const char *cmd[2] = {pmutils_binaries[mode], NULL};
> + int status;
> +
> + status = run_process_child(cmd, &local_err);
> +
> + if (status == 0) {
> + return;
> + }
> +
> + if ((status == -1) && !local_err) {
> + error_setg(errp, "the helper program '%s' was not found",
> + pmutils_binaries[mode]);
> + return;
> + }
> +
> + if (local_err) {
> + error_propagate(errp, local_err);
> + } else {
> + error_setg(errp,
> + "the helper program '%s' returned an
> unexpected exit"
> + " status code (%d)", pmutils_binaries[mode],
> status);
> + }
> +}
> +
> +static bool linux_sys_state_supports_mode(SuspendMode mode, Error
> **errp)
> +{
> + const char *sysfile_strs[3] = {"disk", "mem", NULL};
> + const char *sysfile_str = sysfile_strs[mode];
> + char buf[32]; /* hopefully big enough */
> + int fd;
> + ssize_t ret;
> +
> + if (!sysfile_str) {
> + error_setg(errp, "unknown guest suspend mode");
> + return false;
> + }
> +
> + fd = open(LINUX_SYS_STATE_FILE, O_RDONLY);
> + if (fd < 0) {
> + return false;
> + }
> +
> + ret = read(fd, buf, sizeof(buf) - 1);
> + close(fd);
> + if (ret <= 0) {
> + return false;
> + }
> + buf[ret] = '\0';
> +
> + if (strstr(buf, sysfile_str)) {
> + return true;
> + }
> + return false;
> +}
> +
> +static void linux_sys_state_suspend(SuspendMode mode, Error **errp)
> +{
> + Error *local_err = NULL;
> + const char *sysfile_strs[3] = {"disk", "mem", NULL};
> + const char *sysfile_str = sysfile_strs[mode];
> + pid_t pid;
> + int status;
> +
> + if (!sysfile_str) {
> + error_setg(errp, "unknown guest suspend mode");
> + return;
> + }
> +
> + pid = fork();
> + if (!pid) {
> + /* child */
> + int fd;
> +
> + setsid();
> + reopen_fd_to_null(0);
> + reopen_fd_to_null(1);
> + reopen_fd_to_null(2);
> +
> + fd = open(LINUX_SYS_STATE_FILE, O_WRONLY);
> + if (fd < 0) {
> + _exit(EXIT_FAILURE);
> + }
> +
> + if (write(fd, sysfile_str, strlen(sysfile_str)) < 0) {
> + _exit(EXIT_FAILURE);
> + }
> +
> + _exit(EXIT_SUCCESS);
> + } else if (pid < 0) {
> + error_setg_errno(errp, errno, "failed to create child
> process");
> + return;
> + }
> +
> + ga_wait_child(pid, &status, &local_err);
> + if (local_err) {
> + error_propagate(errp, local_err);
> + return;
> + }
> +
> + if (WEXITSTATUS(status)) {
> + error_setg(errp, "child process has failed to suspend");
> + }
> +
> +}
> +
> +static void guest_suspend(SuspendMode mode, Error **errp)
> +{
> + Error *local_err = NULL;
> + bool mode_supported = false;
> +
> + if (systemd_supports_mode(mode, &local_err)) {
> + mode_supported = true;
> + systemd_suspend(mode, &local_err);
> + }
> +
> + if (!local_err) {
> + return;
> + }
> +
> + error_free(local_err);
> + local_err = NULL;
> +
> + if (pmutils_supports_mode(mode, &local_err)) {
> + mode_supported = true;
> + pmutils_suspend(mode, &local_err);
> + }
> +
> + if (!local_err) {
> + return;
> + }
> +
> + error_free(local_err);
> + local_err = NULL;
> +
> + if (linux_sys_state_supports_mode(mode, &local_err)) {
> + mode_supported = true;
> + linux_sys_state_suspend(mode, &local_err);
> + }
> +
> + if (!mode_supported) {
> + error_free(local_err);
> + error_setg(errp,
> + "the requested suspend mode is not supported
> by the guest");
> + } else {
> + error_propagate(errp, local_err);
> + }
> +}
> +
> +void qmp_guest_suspend_disk(Error **errp)
> +{
> + guest_suspend(SUSPEND_MODE_DISK, errp);
> +}
> +
> +void qmp_guest_suspend_ram(Error **errp)
> +{
> + guest_suspend(SUSPEND_MODE_RAM, errp);
> +}
> +
> +void qmp_guest_suspend_hybrid(Error **errp)
> +{
> + guest_suspend(SUSPEND_MODE_HYBRID, errp);
> +}
> +
> +/* 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,
> + char *dirpath, Error **errp)
> +{
> + int fd;
> + int res;
> + int dirfd;
> + static const char fn[] = "online";
> +
> + dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
> + if (dirfd == -1) {
> + error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
> + return;
> + }
> +
> + 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);
> +}
> +
> +GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
> +{
> + GuestLogicalProcessorList *head, **tail;
> + const char *cpu_dir = "/sys/devices/system/cpu";
> + const gchar *line;
> + g_autoptr(GDir) cpu_gdir = NULL;
> + Error *local_err = NULL;
> +
> + head = NULL;
> + tail = &head;
> + cpu_gdir = g_dir_open(cpu_dir, 0, NULL);
> +
> + if (cpu_gdir == NULL) {
> + error_setg_errno(errp, errno, "failed to list entries:
> %s", cpu_dir);
> + return NULL;
> + }
> +
> + while (local_err == NULL && (line =
> g_dir_read_name(cpu_gdir)) != NULL) {
> + GuestLogicalProcessor *vcpu;
> + int64_t id;
> + if (sscanf(line, "cpu%" PRId64, &id)) {
> + g_autofree char *path =
> g_strdup_printf("/sys/devices/system/cpu/"
> + "cpu%" PRId64
> "/", id);
> + vcpu = g_malloc0(sizeof *vcpu);
> + vcpu->logical_id = id;
> + vcpu->has_can_offline = true; /* lolspeak ftw */
> + transfer_vcpu(vcpu, true, path, &local_err);
> + QAPI_LIST_APPEND(tail, vcpu);
> + }
> + }
> +
> + 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) {
> + char *path =
> g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/",
> + vcpus->value->logical_id);
> +
> + transfer_vcpu(vcpus->value, false, path, &local_err);
> + g_free(path);
> + 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;
> +}
> +
> +void qmp_guest_set_user_password(const char *username,
> + const char *password,
> + bool crypted,
> + Error **errp)
> +{
> + Error *local_err = NULL;
> + char *passwd_path = NULL;
> + pid_t pid;
> + int status;
> + int datafd[2] = { -1, -1 };
> + char *rawpasswddata = NULL;
> + size_t rawpasswdlen;
> + char *chpasswddata = NULL;
> + size_t chpasswdlen;
> +
> + rawpasswddata = (char *)qbase64_decode(password, -1,
> &rawpasswdlen, errp);
> + if (!rawpasswddata) {
> + return;
> + }
> + rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
> + rawpasswddata[rawpasswdlen] = '\0';
> +
> + if (strchr(rawpasswddata, '\n')) {
> + error_setg(errp, "forbidden characters in raw password");
> + goto out;
> + }
> +
> + if (strchr(username, '\n') ||
> + strchr(username, ':')) {
> + error_setg(errp, "forbidden characters in username");
> + goto out;
> + }
> +
> + chpasswddata = g_strdup_printf("%s:%s\n", username,
> rawpasswddata);
> + chpasswdlen = strlen(chpasswddata);
> +
> + passwd_path = g_find_program_in_path("chpasswd");
> +
> + if (!passwd_path) {
> + error_setg(errp, "cannot find 'passwd' program in PATH");
> + goto out;
> + }
> +
> + if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) {
> + error_setg(errp, "cannot create pipe FDs");
> + goto out;
> + }
> +
> + pid = fork();
> + if (pid == 0) {
> + close(datafd[1]);
> + /* child */
> + setsid();
> + dup2(datafd[0], 0);
> + reopen_fd_to_null(1);
> + reopen_fd_to_null(2);
> +
> + if (crypted) {
> + execl(passwd_path, "chpasswd", "-e", NULL);
> + } else {
> + execl(passwd_path, "chpasswd", NULL);
> + }
> + _exit(EXIT_FAILURE);
> + } else if (pid < 0) {
> + error_setg_errno(errp, errno, "failed to create child
> process");
> + goto out;
> + }
> + close(datafd[0]);
> + datafd[0] = -1;
> +
> + if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) !=
> chpasswdlen) {
> + error_setg_errno(errp, errno, "cannot write new account
> password");
> + goto out;
> + }
> + close(datafd[1]);
> + datafd[1] = -1;
> +
> + ga_wait_child(pid, &status, &local_err);
> + if (local_err) {
> + error_propagate(errp, local_err);
> + goto out;
> + }
> +
> + if (!WIFEXITED(status)) {
> + error_setg(errp, "child process has terminated abnormally");
> + goto out;
> + }
> +
> + if (WEXITSTATUS(status)) {
> + error_setg(errp, "child process has failed to set user
> password");
> + goto out;
> + }
> +
> +out:
> + g_free(chpasswddata);
> + g_free(rawpasswddata);
> + g_free(passwd_path);
> + if (datafd[0] != -1) {
> + close(datafd[0]);
> + }
> + if (datafd[1] != -1) {
> + close(datafd[1]);
> + }
> +}
> +
> +static void ga_read_sysfs_file(int dirfd, const char *pathname,
> char *buf,
> + int size, Error **errp)
> +{
> + int fd;
> + int res;
> +
> + errno = 0;
> + fd = openat(dirfd, pathname, O_RDONLY);
> + if (fd == -1) {
> + error_setg_errno(errp, errno, "open sysfs file \"%s\"",
> pathname);
> + return;
> + }
> +
> + res = pread(fd, buf, size, 0);
> + if (res == -1) {
> + error_setg_errno(errp, errno, "pread sysfs file \"%s\"",
> pathname);
> + } else if (res == 0) {
> + error_setg(errp, "pread sysfs file \"%s\": unexpected
> EOF", pathname);
> + }
> + close(fd);
> +}
> +
> +static void ga_write_sysfs_file(int dirfd, const char *pathname,
> + const char *buf, int size, Error
> **errp)
> +{
> + int fd;
> +
> + errno = 0;
> + fd = openat(dirfd, pathname, O_WRONLY);
> + if (fd == -1) {
> + error_setg_errno(errp, errno, "open sysfs file \"%s\"",
> pathname);
> + return;
> + }
> +
> + if (pwrite(fd, buf, size, 0) == -1) {
> + error_setg_errno(errp, errno, "pwrite sysfs file \"%s\"",
> pathname);
> + }
> +
> + close(fd);
> +}
> +
> +/* Transfer online/offline status between @mem_blk and the guest
> system.
> + *
> + * On input either @errp or *@errp must be NULL.
> + *
> + * In system-to-@mem_blk direction, the following @mem_blk fields
> are accessed:
> + * - R: mem_blk->phys_index
> + * - W: mem_blk->online
> + * - W: mem_blk->can_offline
> + *
> + * In @mem_blk-to-system direction, the following @mem_blk fields
> are accessed:
> + * - R: mem_blk->phys_index
> + * - R: mem_blk->online
> + *- R: mem_blk->can_offline
> + * Written members remain unmodified on error.
> + */
> +static void transfer_memory_block(GuestMemoryBlock *mem_blk, bool
> sys2memblk,
> + GuestMemoryBlockResponse *result,
> + Error **errp)
> +{
> + char *dirpath;
> + int dirfd;
> + char *status;
> + Error *local_err = NULL;
> +
> + if (!sys2memblk) {
> + DIR *dp;
> +
> + if (!result) {
> + error_setg(errp, "Internal error, 'result' should not
> be NULL");
> + return;
> + }
> + errno = 0;
> + dp = opendir("/sys/devices/system/memory/");
> + /* if there is no 'memory' directory in sysfs,
> + * we think this VM does not support online/offline
> memory block,
> + * any other solution?
> + */
> + if (!dp) {
> + if (errno == ENOENT) {
> + result->response =
> + GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
> + }
> + goto out1;
> + }
> + closedir(dp);
> + }
> +
> + dirpath =
> g_strdup_printf("/sys/devices/system/memory/memory%" PRId64 "/",
> + mem_blk->phys_index);
> + dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
> + if (dirfd == -1) {
> + if (sys2memblk) {
> + error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
> + } else {
> + if (errno == ENOENT) {
> + result->response =
> GUEST_MEMORY_BLOCK_RESPONSE_TYPE_NOT_FOUND;
> + } else {
> + result->response =
> + GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
> + }
> + }
> + g_free(dirpath);
> + goto out1;
> + }
> + g_free(dirpath);
> +
> + status = g_malloc0(10);
> + ga_read_sysfs_file(dirfd, "state", status, 10, &local_err);
> + if (local_err) {
> + /* treat with sysfs file that not exist in old kernel */
> + if (errno == ENOENT) {
> + error_free(local_err);
> + if (sys2memblk) {
> + mem_blk->online = true;
> + mem_blk->can_offline = false;
> + } else if (!mem_blk->online) {
> + result->response =
> + GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
> + }
> + } else {
> + if (sys2memblk) {
> + error_propagate(errp, local_err);
> + } else {
> + error_free(local_err);
> + result->response =
> + GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
> + }
> + }
> + goto out2;
> + }
> +
> + if (sys2memblk) {
> + char removable = '0';
> +
> + mem_blk->online = (strncmp(status, "online", 6) == 0);
> +
> + ga_read_sysfs_file(dirfd, "removable", &removable, 1,
> &local_err);
> + if (local_err) {
> + /* if no 'removable' file, it doesn't support offline
> mem blk */
> + if (errno == ENOENT) {
> + error_free(local_err);
> + mem_blk->can_offline = false;
> + } else {
> + error_propagate(errp, local_err);
> + }
> + } else {
> + mem_blk->can_offline = (removable != '0');
> + }
> + } else {
> + if (mem_blk->online != (strncmp(status, "online", 6) == 0)) {
> + const char *new_state = mem_blk->online ? "online" :
> "offline";
> +
> + ga_write_sysfs_file(dirfd, "state", new_state,
> strlen(new_state),
> + &local_err);
> + if (local_err) {
> + error_free(local_err);
> + result->response =
> + GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
> + goto out2;
> + }
> +
> + result->response =
> GUEST_MEMORY_BLOCK_RESPONSE_TYPE_SUCCESS;
> + result->has_error_code = false;
> + } /* otherwise pretend successful re-(on|off)-lining */
> + }
> + g_free(status);
> + close(dirfd);
> + return;
> +
> +out2:
> + g_free(status);
> + close(dirfd);
> +out1:
> + if (!sys2memblk) {
> + result->has_error_code = true;
> + result->error_code = errno;
> + }
> +}
> +
> +GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
> +{
> + GuestMemoryBlockList *head, **tail;
> + Error *local_err = NULL;
> + struct dirent *de;
> + DIR *dp;
> +
> + head = NULL;
> + tail = &head;
> +
> + dp = opendir("/sys/devices/system/memory/");
> + if (!dp) {
> + /* it's ok if this happens to be a system that doesn't expose
> + * memory blocks via sysfs, but otherwise we should report
> + * an error
> + */
> + if (errno != ENOENT) {
> + error_setg_errno(errp, errno, "Can't open directory"
> + "\"/sys/devices/system/memory/\"");
> + }
> + return NULL;
> + }
> +
> + /* Note: the phys_index of memory block may be discontinuous,
> + * this is because a memblk is the unit of the Sparse Memory
> design, which
> + * allows discontinuous memory ranges (ex. NUMA), so here we
> should
> + * traverse the memory block directory.
> + */
> + while ((de = readdir(dp)) != NULL) {
> + GuestMemoryBlock *mem_blk;
> +
> + if ((strncmp(de->d_name, "memory", 6) != 0) ||
> + !(de->d_type & DT_DIR)) {
> + continue;
> + }
> +
> + mem_blk = g_malloc0(sizeof *mem_blk);
> + /* The d_name is "memoryXXX", phys_index is block id,
> same as XXX */
> + mem_blk->phys_index = strtoul(&de->d_name[6], NULL, 10);
> + mem_blk->has_can_offline = true; /* lolspeak ftw */
> + transfer_memory_block(mem_blk, true, NULL, &local_err);
> + if (local_err) {
> + break;
> + }
> +
> + QAPI_LIST_APPEND(tail, mem_blk);
> + }
> +
> + closedir(dp);
> + if (local_err == NULL) {
> + /* there's no guest with zero memory blocks */
> + if (head == NULL) {
> + error_setg(errp, "guest reported zero memory blocks!");
> + }
> + return head;
> + }
> +
> + qapi_free_GuestMemoryBlockList(head);
> + error_propagate(errp, local_err);
> + return NULL;
> +}
> +
> +GuestMemoryBlockResponseList *
> +qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error
> **errp)
> +{
> + GuestMemoryBlockResponseList *head, **tail;
> + Error *local_err = NULL;
> +
> + head = NULL;
> + tail = &head;
> +
> + while (mem_blks != NULL) {
> + GuestMemoryBlockResponse *result;
> + GuestMemoryBlock *current_mem_blk = mem_blks->value;
> +
> + result = g_malloc0(sizeof(*result));
> + result->phys_index = current_mem_blk->phys_index;
> + transfer_memory_block(current_mem_blk, false, result,
> &local_err);
> + if (local_err) { /* should never happen */
> + goto err;
> + }
> +
> + QAPI_LIST_APPEND(tail, result);
> + mem_blks = mem_blks->next;
> + }
> +
> + return head;
> +err:
> + qapi_free_GuestMemoryBlockResponseList(head);
> + error_propagate(errp, local_err);
> + return NULL;
> +}
> +
> +GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
> +{
> + Error *local_err = NULL;
> + char *dirpath;
> + int dirfd;
> + char *buf;
> + GuestMemoryBlockInfo *info;
> +
> + dirpath = g_strdup_printf("/sys/devices/system/memory/");
> + dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
> + if (dirfd == -1) {
> + error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
> + g_free(dirpath);
> + return NULL;
> + }
> + g_free(dirpath);
> +
> + buf = g_malloc0(20);
> + ga_read_sysfs_file(dirfd, "block_size_bytes", buf, 20,
> &local_err);
> + close(dirfd);
> + if (local_err) {
> + g_free(buf);
> + error_propagate(errp, local_err);
> + return NULL;
> + }
> +
> + info = g_new0(GuestMemoryBlockInfo, 1);
> + info->size = strtol(buf, NULL, 16); /* the unit is bytes */
> +
> + g_free(buf);
> +
> + return info;
> +}
> +
> +#define MAX_NAME_LEN 128
> +static GuestDiskStatsInfoList *guest_get_diskstats(Error **errp)
> +{
> +#ifdef CONFIG_LINUX
> + GuestDiskStatsInfoList *head = NULL, **tail = &head;
> + const char *diskstats = "/proc/diskstats";
> + FILE *fp;
> + size_t n;
> + char *line = NULL;
> +
> + fp = fopen(diskstats, "r");
> + if (fp == NULL) {
> + error_setg_errno(errp, errno, "open(\"%s\")", diskstats);
> + return NULL;
> + }
> +
> + while (getline(&line, &n, fp) != -1) {
> + g_autofree GuestDiskStatsInfo *diskstatinfo = NULL;
> + g_autofree GuestDiskStats *diskstat = NULL;
> + char dev_name[MAX_NAME_LEN];
> + unsigned int ios_pgr, tot_ticks, rq_ticks, wr_ticks,
> dc_ticks, fl_ticks;
> + unsigned long rd_ios, rd_merges_or_rd_sec,
> rd_ticks_or_wr_sec, wr_ios;
> + unsigned long wr_merges, rd_sec_or_wr_ios, wr_sec;
> + unsigned long dc_ios, dc_merges, dc_sec, fl_ios;
> + unsigned int major, minor;
> + int i;
> +
> + i = sscanf(line, "%u %u %s %lu %lu %lu"
> + "%lu %lu %lu %lu %u %u %u %u"
> + "%lu %lu %lu %u %lu %u",
> + &major, &minor, dev_name,
> + &rd_ios, &rd_merges_or_rd_sec, &rd_sec_or_wr_ios,
> + &rd_ticks_or_wr_sec, &wr_ios, &wr_merges, &wr_sec,
> + &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks,
> + &dc_ios, &dc_merges, &dc_sec, &dc_ticks,
> + &fl_ios, &fl_ticks);
> +
> + if (i < 7) {
> + continue;
> + }
> +
> + diskstatinfo = g_new0(GuestDiskStatsInfo, 1);
> + diskstatinfo->name = g_strdup(dev_name);
> + diskstatinfo->major = major;
> + diskstatinfo->minor = minor;
> +
> + diskstat = g_new0(GuestDiskStats, 1);
> + if (i == 7) {
> + diskstat->has_read_ios = true;
> + diskstat->read_ios = rd_ios;
> + diskstat->has_read_sectors = true;
> + diskstat->read_sectors = rd_merges_or_rd_sec;
> + diskstat->has_write_ios = true;
> + diskstat->write_ios = rd_sec_or_wr_ios;
> + diskstat->has_write_sectors = true;
> + diskstat->write_sectors = rd_ticks_or_wr_sec;
> + }
> + if (i >= 14) {
> + diskstat->has_read_ios = true;
> + diskstat->read_ios = rd_ios;
> + diskstat->has_read_sectors = true;
> + diskstat->read_sectors = rd_sec_or_wr_ios;
> + diskstat->has_read_merges = true;
> + diskstat->read_merges = rd_merges_or_rd_sec;
> + diskstat->has_read_ticks = true;
> + diskstat->read_ticks = rd_ticks_or_wr_sec;
> + diskstat->has_write_ios = true;
> + diskstat->write_ios = wr_ios;
> + diskstat->has_write_sectors = true;
> + diskstat->write_sectors = wr_sec;
> + diskstat->has_write_merges = true;
> + diskstat->write_merges = wr_merges;
> + diskstat->has_write_ticks = true;
> + diskstat->write_ticks = wr_ticks;
> + diskstat->has_ios_pgr = true;
> + diskstat->ios_pgr = ios_pgr;
> + diskstat->has_total_ticks = true;
> + diskstat->total_ticks = tot_ticks;
> + diskstat->has_weight_ticks = true;
> + diskstat->weight_ticks = rq_ticks;
> + }
> + if (i >= 18) {
> + diskstat->has_discard_ios = true;
> + diskstat->discard_ios = dc_ios;
> + diskstat->has_discard_merges = true;
> + diskstat->discard_merges = dc_merges;
> + diskstat->has_discard_sectors = true;
> + diskstat->discard_sectors = dc_sec;
> + diskstat->has_discard_ticks = true;
> + diskstat->discard_ticks = dc_ticks;
> + }
> + if (i >= 20) {
> + diskstat->has_flush_ios = true;
> + diskstat->flush_ios = fl_ios;
> + diskstat->has_flush_ticks = true;
> + diskstat->flush_ticks = fl_ticks;
> + }
> +
> + diskstatinfo->stats = g_steal_pointer(&diskstat);
> + QAPI_LIST_APPEND(tail, diskstatinfo);
> + diskstatinfo = NULL;
> + }
> + free(line);
> + fclose(fp);
> + return head;
> +#else
> + g_debug("disk stats reporting available only for Linux");
> + return NULL;
> +#endif
> +}
> +
> +GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp)
> +{
> + return guest_get_diskstats(errp);
> +}
> +
> +GuestCpuStatsList *qmp_guest_get_cpustats(Error **errp)
> +{
> + GuestCpuStatsList *head = NULL, **tail = &head;
> + const char *cpustats = "/proc/stat";
> + int clk_tck = sysconf(_SC_CLK_TCK);
> + FILE *fp;
> + size_t n;
> + char *line = NULL;
> +
> + fp = fopen(cpustats, "r");
> + if (fp == NULL) {
> + error_setg_errno(errp, errno, "open(\"%s\")", cpustats);
> + return NULL;
> + }
> +
> + while (getline(&line, &n, fp) != -1) {
> + GuestCpuStats *cpustat = NULL;
> + GuestLinuxCpuStats *linuxcpustat;
> + int i;
> + unsigned long user, system, idle, iowait, irq, softirq,
> steal, guest;
> + unsigned long nice, guest_nice;
> + char name[64];
> +
> + i = sscanf(line, "%s %lu %lu %lu %lu %lu %lu %lu %lu %lu
> %lu",
> + name, &user, &nice, &system, &idle, &iowait,
> &irq, &softirq,
> + &steal, &guest, &guest_nice);
> +
> + /* drop "cpu 1 2 3 ...", get "cpuX 1 2 3 ..." only */
> + if ((i == EOF) || strncmp(name, "cpu", 3) || (name[3] ==
> '\0')) {
> + continue;
> + }
> +
> + if (i < 5) {
> + slog("Parsing cpu stat from %s failed, see \"man
> proc\"", cpustats);
> + break;
> + }
> +
> + cpustat = g_new0(GuestCpuStats, 1);
> + cpustat->type = GUEST_CPU_STATS_TYPE_LINUX;
> +
> + linuxcpustat = &cpustat->u.q_linux;
> + linuxcpustat->cpu = atoi(&name[3]);
> + linuxcpustat->user = user * 1000 / clk_tck;
> + linuxcpustat->nice = nice * 1000 / clk_tck;
> + linuxcpustat->system = system * 1000 / clk_tck;
> + linuxcpustat->idle = idle * 1000 / clk_tck;
> +
> + if (i > 5) {
> + linuxcpustat->has_iowait = true;
> + linuxcpustat->iowait = iowait * 1000 / clk_tck;
> + }
> +
> + if (i > 6) {
> + linuxcpustat->has_irq = true;
> + linuxcpustat->irq = irq * 1000 / clk_tck;
> + linuxcpustat->has_softirq = true;
> + linuxcpustat->softirq = softirq * 1000 / clk_tck;
> + }
> +
> + if (i > 8) {
> + linuxcpustat->has_steal = true;
> + linuxcpustat->steal = steal * 1000 / clk_tck;
> + }
> +
> + if (i > 9) {
> + linuxcpustat->has_guest = true;
> + linuxcpustat->guest = guest * 1000 / clk_tck;
> + }
> +
> + if (i > 10) {
> + linuxcpustat->has_guest = true;
> + linuxcpustat->guest = guest * 1000 / clk_tck;
> + linuxcpustat->has_guestnice = true;
> + linuxcpustat->guestnice = guest_nice * 1000 / clk_tck;
> + }
> +
> + QAPI_LIST_APPEND(tail, cpustat);
> + }
> +
> + free(line);
> + fclose(fp);
> + return head;
> +}
> diff --git a/qga/commands-posix.c b/qga/commands-posix.c
> index 954efed01b..0bb8b9e2f3 100644
> --- a/qga/commands-posix.c
> +++ b/qga/commands-posix.c
> @@ -15,7 +15,6 @@
> #include <sys/ioctl.h>
> #include <sys/utsname.h>
> #include <sys/wait.h>
> -#include <dirent.h>
> #include "guest-agent-core.h"
> #include "qga-qapi-commands.h"
> #include "qapi/error.h"
> @@ -26,31 +25,12 @@
> #include "qemu/base64.h"
> #include "qemu/cutils.h"
> #include "commands-common.h"
> -#include "block/nvme.h"
> #include "cutils.h"
>
> #ifdef HAVE_UTMPX
> #include <utmpx.h>
> #endif
>
> -#if defined(__linux__)
> -#include <mntent.h>
> -#include <linux/fs.h>
> -#include <sys/statvfs.h>
> -#include <linux/nvme_ioctl.h>
> -
> -#ifdef CONFIG_LIBUDEV
> -#include <libudev.h>
> -#endif
> -
> -#ifdef FIFREEZE
> -#define CONFIG_FSFREEZE
> -#endif
> -#ifdef FITRIM
> -#define CONFIG_FSTRIM
> -#endif
> -#endif
> -
> #ifdef HAVE_GETIFADDRS
> #include <arpa/inet.h>
> #include <sys/socket.h>
> @@ -62,7 +42,173 @@
> #endif
> #endif
>
> -static void ga_wait_child(pid_t pid, int *status, Error **errp)
> +#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
> +void free_fs_mount_list(FsMountList *mounts)
> +{
> + FsMount *mount, *temp;
> +
> + if (!mounts) {
> + return;
> + }
> +
> + QTAILQ_FOREACH_SAFE(mount, mounts, next, temp) {
> + QTAILQ_REMOVE(mounts, mount, next);
> + g_free(mount->dirname);
> + g_free(mount->devtype);
> + g_free(mount);
> + }
> +}
> +#endif
> +
> +#if defined(CONFIG_FSFREEZE)
> +typedef enum {
> + FSFREEZE_HOOK_THAW = 0,
> + FSFREEZE_HOOK_FREEZE,
> +} FsfreezeHookArg;
> +
> +static const char *fsfreeze_hook_arg_string[] = {
> + "thaw",
> + "freeze",
> +};
> +
> +static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **errp)
> +{
> + 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(errp, 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);
> +
> + execl(hook, hook, arg_str, NULL);
> + _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 (local_err) {
> + error_propagate(errp, local_err);
> + return;
> + }
> +
> + if (!WIFEXITED(status)) {
> + error_setg(errp, "fsfreeze hook has terminated abnormally");
> + return;
> + }
> +
> + status = WEXITSTATUS(status);
> + if (status) {
> + error_setg(errp, "fsfreeze hook has failed with status
> %d", status);
> + return;
> + }
> +}
> +
> +int64_t qmp_guest_fsfreeze_thaw(Error **errp)
> +{
> + int ret;
> +
> + ret = qmp_guest_fsfreeze_do_thaw(errp);
> + if (ret >= 0) {
> + ga_unset_frozen(ga_state);
> + execute_fsfreeze_hook(FSFREEZE_HOOK_THAW, errp);
> + } else {
> + ret = 0;
> + }
> +
> + return ret;
> +}
> +
> +int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints,
> + strList *mountpoints,
> + Error **errp)
> +{
> + int ret;
> + FsMountList mounts;
> + Error *local_err = NULL;
> +
> + slog("guest-fsfreeze called");
> +
> + execute_fsfreeze_hook(FSFREEZE_HOOK_FREEZE, &local_err);
> + if (local_err) {
> + error_propagate(errp, local_err);
> + return -1;
> + }
> +
> + QTAILQ_INIT(&mounts);
> + if (!build_fs_mount_list(&mounts, &local_err)) {
> + error_propagate(errp, local_err);
> + return -1;
> + }
> +
> + /* cannot risk guest agent blocking itself on a write in this
> state */
> + ga_set_frozen(ga_state);
> +
> + ret = qmp_guest_fsfreeze_do_freeze_list(has_mountpoints,
> mountpoints,
> + mounts, errp);
> +
> + free_fs_mount_list(&mounts);
> + /* We may not issue any FIFREEZE here.
> + * Just unset ga_state here and ready for the next call.
> + */
> + if (ret == 0) {
> + ga_unset_frozen(ga_state);
> + } else if (ret < 0) {
> + qmp_guest_fsfreeze_thaw(NULL);
> + }
> + return ret;
> +}
> +
> +/*
> + * Return status of freeze/thaw
> + */
> +GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
> +{
> + if (ga_is_frozen(ga_state)) {
> + return GUEST_FSFREEZE_STATUS_FROZEN;
> + }
> +
> + return GUEST_FSFREEZE_STATUS_THAWED;
> +}
> +
> +int64_t qmp_guest_fsfreeze_freeze(Error **errp)
> +{
> + return qmp_guest_fsfreeze_freeze_list(false, NULL, errp);
> +}
> +
> +static void guest_fsfreeze_cleanup(void)
> +{
> + Error *err = NULL;
> +
> + if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
> + qmp_guest_fsfreeze_thaw(&err);
> + if (err) {
> + slog("failed to clean up frozen filesystems: %s",
> + error_get_pretty(err));
> + error_free(err);
> + }
> + }
> +}
> +#endif /* CONFIG_FSFREEZE */
> +
> +void ga_wait_child(pid_t pid, int *status, Error **errp)
> {
> pid_t rpid;
>
> @@ -617,2375 +763,7 @@ void qmp_guest_file_flush(int64_t handle,
> Error **errp)
> }
> }
>
> -/* linux-specific implementations. avoid this if at all possible. */
> -#if defined(__linux__)
> -
> -#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
> -typedef struct FsMount {
> - char *dirname;
> - char *devtype;
> - unsigned int devmajor, devminor;
> - QTAILQ_ENTRY(FsMount) next;
> -} FsMount;
> -
> -typedef QTAILQ_HEAD(FsMountList, FsMount) FsMountList;
> -
> -static void free_fs_mount_list(FsMountList *mounts)
> -{
> - FsMount *mount, *temp;
> -
> - if (!mounts) {
> - return;
> - }
> -
> - QTAILQ_FOREACH_SAFE(mount, mounts, next, temp) {
> - QTAILQ_REMOVE(mounts, mount, next);
> - g_free(mount->dirname);
> - g_free(mount->devtype);
> - g_free(mount);
> - }
> -}
> -
> -static int dev_major_minor(const char *devpath,
> - unsigned int *devmajor, unsigned int
> *devminor)
> -{
> - struct stat st;
> -
> - *devmajor = 0;
> - *devminor = 0;
> -
> - if (stat(devpath, &st) < 0) {
> - slog("failed to stat device file '%s': %s", devpath,
> strerror(errno));
> - return -1;
> - }
> - if (S_ISDIR(st.st_mode)) {
> - /* It is bind mount */
> - return -2;
> - }
> - if (S_ISBLK(st.st_mode)) {
> - *devmajor = major(st.st_rdev);
> - *devminor = minor(st.st_rdev);
> - return 0;
> - }
> - return -1;
> -}
> -
> -/*
> - * Walk the mount table and build a list of local file systems
> - */
> -static bool build_fs_mount_list_from_mtab(FsMountList *mounts,
> Error **errp)
> -{
> - struct mntent *ment;
> - FsMount *mount;
> - char const *mtab = "/proc/self/mounts";
> - FILE *fp;
> - unsigned int devmajor, devminor;
> -
> - fp = setmntent(mtab, "r");
> - if (!fp) {
> - error_setg(errp, "failed to open mtab file: '%s'", mtab);
> - return false;
> - }
> -
> - while ((ment = getmntent(fp))) {
> - /*
> - * An entry which device name doesn't start with a '/' is
> - * either a dummy file system or a network file system.
> - * Add special handling for smbfs and cifs as is done by
> - * coreutils as well.
> - */
> - if ((ment->mnt_fsname[0] != '/') ||
> - (strcmp(ment->mnt_type, "smbfs") == 0) ||
> - (strcmp(ment->mnt_type, "cifs") == 0)) {
> - continue;
> - }
> - if (dev_major_minor(ment->mnt_fsname, &devmajor,
> &devminor) == -2) {
> - /* Skip bind mounts */
> - continue;
> - }
> -
> - mount = g_new0(FsMount, 1);
> - mount->dirname = g_strdup(ment->mnt_dir);
> - mount->devtype = g_strdup(ment->mnt_type);
> - mount->devmajor = devmajor;
> - mount->devminor = devminor;
> -
> - QTAILQ_INSERT_TAIL(mounts, mount, next);
> - }
> -
> - endmntent(fp);
> - return true;
> -}
> -
> -static void decode_mntname(char *name, int len)
> -{
> - int i, j = 0;
> - for (i = 0; i <= len; i++) {
> - if (name[i] != '\\') {
> - name[j++] = name[i];
> - } else if (name[i + 1] == '\\') {
> - name[j++] = '\\';
> - i++;
> - } else if (name[i + 1] >= '0' && name[i + 1] <= '3' &&
> - name[i + 2] >= '0' && name[i + 2] <= '7' &&
> - name[i + 3] >= '0' && name[i + 3] <= '7') {
> - name[j++] = (name[i + 1] - '0') * 64 +
> - (name[i + 2] - '0') * 8 +
> - (name[i + 3] - '0');
> - i += 3;
> - } else {
> - name[j++] = name[i];
> - }
> - }
> -}
> -
> -static bool build_fs_mount_list(FsMountList *mounts, Error **errp)
> -{
> - FsMount *mount;
> - char const *mountinfo = "/proc/self/mountinfo";
> - FILE *fp;
> - char *line = NULL, *dash;
> - size_t n;
> - char check;
> - unsigned int devmajor, devminor;
> - int ret, dir_s, dir_e, type_s, type_e, dev_s, dev_e;
> -
> - fp = fopen(mountinfo, "r");
> - if (!fp) {
> - return build_fs_mount_list_from_mtab(mounts, errp);
> - }
> -
> - while (getline(&line, &n, fp) != -1) {
> - ret = sscanf(line, "%*u %*u %u:%u %*s %n%*s%n%c",
> - &devmajor, &devminor, &dir_s, &dir_e, &check);
> - if (ret < 3) {
> - continue;
> - }
> - dash = strstr(line + dir_e, " - ");
> - if (!dash) {
> - continue;
> - }
> - ret = sscanf(dash, " - %n%*s%n %n%*s%n%c",
> - &type_s, &type_e, &dev_s, &dev_e, &check);
> - if (ret < 1) {
> - continue;
> - }
> - line[dir_e] = 0;
> - dash[type_e] = 0;
> - dash[dev_e] = 0;
> - decode_mntname(line + dir_s, dir_e - dir_s);
> - decode_mntname(dash + dev_s, dev_e - dev_s);
> - if (devmajor == 0) {
> - /* btrfs reports major number = 0 */
> - if (strcmp("btrfs", dash + type_s) != 0 ||
> - dev_major_minor(dash + dev_s, &devmajor,
> &devminor) < 0) {
> - continue;
> - }
> - }
> -
> - mount = g_new0(FsMount, 1);
> - mount->dirname = g_strdup(line + dir_s);
> - mount->devtype = g_strdup(dash + type_s);
> - mount->devmajor = devmajor;
> - mount->devminor = devminor;
> -
> - QTAILQ_INSERT_TAIL(mounts, mount, next);
> - }
> - free(line);
> -
> - fclose(fp);
> - return true;
> -}
> -#endif
> -
> -#if defined(CONFIG_FSFREEZE)
> -
> -static char *get_pci_driver(char const *syspath, int pathlen,
> Error **errp)
> -{
> - char *path;
> - char *dpath;
> - char *driver = NULL;
> - char buf[PATH_MAX];
> - ssize_t len;
> -
> - path = g_strndup(syspath, pathlen);
> - dpath = g_strdup_printf("%s/driver", path);
> - len = readlink(dpath, buf, sizeof(buf) - 1);
> - if (len != -1) {
> - buf[len] = 0;
> - driver = g_path_get_basename(buf);
> - }
> - g_free(dpath);
> - g_free(path);
> - return driver;
> -}
> -
> -static int compare_uint(const void *_a, const void *_b)
> -{
> - unsigned int a = *(unsigned int *)_a;
> - unsigned int b = *(unsigned int *)_b;
> -
> - return a < b ? -1 : a > b ? 1 : 0;
> -}
> -
> -/* Walk the specified sysfs and build a sorted list of host or
> ata numbers */
> -static int build_hosts(char const *syspath, char const *host,
> bool ata,
> - unsigned int *hosts, int hosts_max, Error
> **errp)
> -{
> - char *path;
> - DIR *dir;
> - struct dirent *entry;
> - int i = 0;
> -
> - path = g_strndup(syspath, host - syspath);
> - dir = opendir(path);
> - if (!dir) {
> - error_setg_errno(errp, errno, "opendir(\"%s\")", path);
> - g_free(path);
> - return -1;
> - }
> -
> - while (i < hosts_max) {
> - entry = readdir(dir);
> - if (!entry) {
> - break;
> - }
> - if (ata && sscanf(entry->d_name, "ata%d", hosts + i) == 1) {
> - ++i;
> - } else if (!ata && sscanf(entry->d_name, "host%d", hosts
> + i) == 1) {
> - ++i;
> - }
> - }
> -
> - qsort(hosts, i, sizeof(hosts[0]), compare_uint);
> -
> - g_free(path);
> - closedir(dir);
> - return i;
> -}
> -
> -/*
> - * Store disk device info for devices on the PCI bus.
> - * Returns true if information has been stored, or false for failure.
> - */
> -static bool build_guest_fsinfo_for_pci_dev(char const *syspath,
> - GuestDiskAddress *disk,
> - Error **errp)
> -{
> - unsigned int pci[4], host, hosts[8], tgt[3];
> - int i, nhosts = 0, pcilen;
> - GuestPCIAddress *pciaddr = disk->pci_controller;
> - bool has_ata = false, has_host = false, has_tgt = false;
> - char *p, *q, *driver = NULL;
> - bool ret = false;
> -
> - p = strstr(syspath, "/devices/pci");
> - if (!p || sscanf(p + 12, "%*x:%*x/%x:%x:%x.%x%n",
> - pci, pci + 1, pci + 2, pci + 3, &pcilen) < 4) {
> - g_debug("only pci device is supported: sysfs path '%s'",
> syspath);
> - return false;
> - }
> -
> - p += 12 + pcilen;
> - while (true) {
> - driver = get_pci_driver(syspath, p - syspath, errp);
> - if (driver && (g_str_equal(driver, "ata_piix") ||
> - g_str_equal(driver, "sym53c8xx") ||
> - g_str_equal(driver, "virtio-pci") ||
> - g_str_equal(driver, "ahci") ||
> - g_str_equal(driver, "nvme"))) {
> - break;
> - }
> -
> - g_free(driver);
> - if (sscanf(p, "/%x:%x:%x.%x%n",
> - pci, pci + 1, pci + 2, pci + 3,
> &pcilen) == 4) {
> - p += pcilen;
> - continue;
> - }
> -
> - g_debug("unsupported driver or sysfs path '%s'", syspath);
> - return false;
> - }
> -
> - p = strstr(syspath, "/target");
> - if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u",
> - tgt, tgt + 1, tgt + 2) == 3) {
> - has_tgt = true;
> - }
> -
> - p = strstr(syspath, "/ata");
> - if (p) {
> - q = p + 4;
> - has_ata = true;
> - } else {
> - p = strstr(syspath, "/host");
> - q = p + 5;
> - }
> - if (p && sscanf(q, "%u", &host) == 1) {
> - has_host = true;
> - nhosts = build_hosts(syspath, p, has_ata, hosts,
> - ARRAY_SIZE(hosts), errp);
> - if (nhosts < 0) {
> - goto cleanup;
> - }
> - }
> -
> - pciaddr->domain = pci[0];
> - pciaddr->bus = pci[1];
> - pciaddr->slot = pci[2];
> - pciaddr->function = pci[3];
> -
> - if (strcmp(driver, "ata_piix") == 0) {
> - /* a host per ide bus, target*:0:<unit>:0 */
> - if (!has_host || !has_tgt) {
> - g_debug("invalid sysfs path '%s' (driver '%s')",
> syspath, driver);
> - goto cleanup;
> - }
> - for (i = 0; i < nhosts; i++) {
> - if (host == hosts[i]) {
> - disk->bus_type = GUEST_DISK_BUS_TYPE_IDE;
> - disk->bus = i;
> - disk->unit = tgt[1];
> - break;
> - }
> - }
> - if (i >= nhosts) {
> - g_debug("no host for '%s' (driver '%s')", syspath,
> driver);
> - goto cleanup;
> - }
> - } else if (strcmp(driver, "sym53c8xx") == 0) {
> - /* scsi(LSI Logic): target*:0:<unit>:0 */
> - if (!has_tgt) {
> - g_debug("invalid sysfs path '%s' (driver '%s')",
> syspath, driver);
> - goto cleanup;
> - }
> - disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
> - disk->unit = tgt[1];
> - } else if (strcmp(driver, "virtio-pci") == 0) {
> - if (has_tgt) {
> - /* virtio-scsi: target*:0:0:<unit> */
> - disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
> - disk->unit = tgt[2];
> - } else {
> - /* virtio-blk: 1 disk per 1 device */
> - disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
> - }
> - } else if (strcmp(driver, "ahci") == 0) {
> - /* ahci: 1 host per 1 unit */
> - if (!has_host || !has_tgt) {
> - g_debug("invalid sysfs path '%s' (driver '%s')",
> syspath, driver);
> - goto cleanup;
> - }
> - for (i = 0; i < nhosts; i++) {
> - if (host == hosts[i]) {
> - disk->unit = i;
> - disk->bus_type = GUEST_DISK_BUS_TYPE_SATA;
> - break;
> - }
> - }
> - if (i >= nhosts) {
> - g_debug("no host for '%s' (driver '%s')", syspath,
> driver);
> - goto cleanup;
> - }
> - } else if (strcmp(driver, "nvme") == 0) {
> - disk->bus_type = GUEST_DISK_BUS_TYPE_NVME;
> - } else {
> - g_debug("unknown driver '%s' (sysfs path '%s')", driver,
> syspath);
> - goto cleanup;
> - }
> -
> - ret = true;
> -
> -cleanup:
> - g_free(driver);
> - return ret;
> -}
> -
> -/*
> - * Store disk device info for non-PCI virtio devices (for example
> s390x
> - * channel I/O devices). Returns true if information has been
> stored, or
> - * false for failure.
> - */
> -static bool build_guest_fsinfo_for_nonpci_virtio(char const *syspath,
> - GuestDiskAddress *disk,
> - Error **errp)
> -{
> - unsigned int tgt[3];
> - char *p;
> -
> - if (!strstr(syspath, "/virtio") || !strstr(syspath, "/block")) {
> - g_debug("Unsupported virtio device '%s'", syspath);
> - return false;
> - }
> -
> - p = strstr(syspath, "/target");
> - if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u",
> - &tgt[0], &tgt[1], &tgt[2]) == 3) {
> - /* virtio-scsi: target*:0:<target>:<unit> */
> - disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
> - disk->bus = tgt[0];
> - disk->target = tgt[1];
> - disk->unit = tgt[2];
> - } else {
> - /* virtio-blk: 1 disk per 1 device */
> - disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
> - }
> -
> - return true;
> -}
> -
> -/*
> - * Store disk device info for CCW devices (s390x channel I/O
> devices).
> - * Returns true if information has been stored, or false for failure.
> - */
> -static bool build_guest_fsinfo_for_ccw_dev(char const *syspath,
> - GuestDiskAddress *disk,
> - Error **errp)
> -{
> - unsigned int cssid, ssid, subchno, devno;
> - char *p;
> -
> - p = strstr(syspath, "/devices/css");
> - if (!p || sscanf(p + 12, "%*x/%x.%x.%x/%*x.%*x.%x/",
> - &cssid, &ssid, &subchno, &devno) < 4) {
> - g_debug("could not parse ccw device sysfs path: %s",
> syspath);
> - return false;
> - }
> -
> - disk->has_ccw_address = true;
> - disk->ccw_address = g_new0(GuestCCWAddress, 1);
> - disk->ccw_address->cssid = cssid;
> - disk->ccw_address->ssid = ssid;
> - disk->ccw_address->subchno = subchno;
> - disk->ccw_address->devno = devno;
> -
> - if (strstr(p, "/virtio")) {
> - build_guest_fsinfo_for_nonpci_virtio(syspath, disk, errp);
> - }
> -
> - return true;
> -}
> -
> -/* Store disk device info specified by @sysfs into @fs */
> -static void build_guest_fsinfo_for_real_device(char const *syspath,
> - GuestFilesystemInfo *fs,
> - Error **errp)
> -{
> - GuestDiskAddress *disk;
> - GuestPCIAddress *pciaddr;
> - bool has_hwinf;
> -#ifdef CONFIG_LIBUDEV
> - struct udev *udev = NULL;
> - struct udev_device *udevice = NULL;
> -#endif
> -
> - pciaddr = g_new0(GuestPCIAddress, 1);
> - pciaddr->domain = -1; /* -1 means field
> is invalid */
> - pciaddr->bus = -1;
> - pciaddr->slot = -1;
> - pciaddr->function = -1;
> -
> - disk = g_new0(GuestDiskAddress, 1);
> - disk->pci_controller = pciaddr;
> - disk->bus_type = GUEST_DISK_BUS_TYPE_UNKNOWN;
> -
> -#ifdef CONFIG_LIBUDEV
> - udev = udev_new();
> - udevice = udev_device_new_from_syspath(udev, syspath);
> - if (udev == NULL || udevice == NULL) {
> - g_debug("failed to query udev");
> - } else {
> - const char *devnode, *serial;
> - devnode = udev_device_get_devnode(udevice);
> - if (devnode != NULL) {
> - disk->dev = g_strdup(devnode);
> - disk->has_dev = true;
> - }
> - serial = udev_device_get_property_value(udevice,
> "ID_SERIAL");
> - if (serial != NULL && *serial != 0) {
> - disk->serial = g_strdup(serial);
> - disk->has_serial = true;
> - }
> - }
> -
> - udev_unref(udev);
> - udev_device_unref(udevice);
> -#endif
> -
> - if (strstr(syspath, "/devices/pci")) {
> - has_hwinf = build_guest_fsinfo_for_pci_dev(syspath, disk,
> errp);
> - } else if (strstr(syspath, "/devices/css")) {
> - has_hwinf = build_guest_fsinfo_for_ccw_dev(syspath, disk,
> errp);
> - } else if (strstr(syspath, "/virtio")) {
> - has_hwinf = build_guest_fsinfo_for_nonpci_virtio(syspath,
> disk, errp);
> - } else {
> - g_debug("Unsupported device type for '%s'", syspath);
> - has_hwinf = false;
> - }
> -
> - if (has_hwinf || disk->has_dev || disk->has_serial) {
> - QAPI_LIST_PREPEND(fs->disk, disk);
> - } else {
> - qapi_free_GuestDiskAddress(disk);
> - }
> -}
> -
> -static void build_guest_fsinfo_for_device(char const *devpath,
> - GuestFilesystemInfo *fs,
> - Error **errp);
> -
> -/* Store a list of slave devices of virtual volume specified by
> @syspath into
> - * @fs */
> -static void build_guest_fsinfo_for_virtual_device(char const
> *syspath,
> - GuestFilesystemInfo *fs,
> - Error **errp)
> -{
> - Error *err = NULL;
> - DIR *dir;
> - char *dirpath;
> - struct dirent *entry;
> -
> - dirpath = g_strdup_printf("%s/slaves", syspath);
> - dir = opendir(dirpath);
> - if (!dir) {
> - if (errno != ENOENT) {
> - error_setg_errno(errp, errno, "opendir(\"%s\")",
> dirpath);
> - }
> - g_free(dirpath);
> - return;
> - }
> -
> - for (;;) {
> - errno = 0;
> - entry = readdir(dir);
> - if (entry == NULL) {
> - if (errno) {
> - error_setg_errno(errp, errno, "readdir(\"%s\")",
> dirpath);
> - }
> - break;
> - }
> -
> - if (entry->d_type == DT_LNK) {
> - char *path;
> -
> - g_debug(" slave device '%s'", entry->d_name);
> - path = g_strdup_printf("%s/slaves/%s", syspath,
> entry->d_name);
> - build_guest_fsinfo_for_device(path, fs, &err);
> - g_free(path);
> -
> - if (err) {
> - error_propagate(errp, err);
> - break;
> - }
> - }
> - }
> -
> - g_free(dirpath);
> - closedir(dir);
> -}
> -
> -static bool is_disk_virtual(const char *devpath, Error **errp)
> -{
> - g_autofree char *syspath = realpath(devpath, NULL);
> -
> - if (!syspath) {
> - error_setg_errno(errp, errno, "realpath(\"%s\")", devpath);
> - return false;
> - }
> - return strstr(syspath, "/devices/virtual/block/") != NULL;
> -}
> -
> -/* Dispatch to functions for virtual/real device */
> -static void build_guest_fsinfo_for_device(char const *devpath,
> - GuestFilesystemInfo *fs,
> - Error **errp)
> -{
> - ERRP_GUARD();
> - g_autofree char *syspath = NULL;
> - bool is_virtual = false;
> -
> - syspath = realpath(devpath, NULL);
> - if (!syspath) {
> - if (errno != ENOENT) {
> - error_setg_errno(errp, errno, "realpath(\"%s\")",
> devpath);
> - return;
> - }
> -
> - /* ENOENT: This devpath may not exist because of
> container config */
> - if (!fs->name) {
> - fs->name = g_path_get_basename(devpath);
> - }
> - return;
> - }
> -
> - if (!fs->name) {
> - fs->name = g_path_get_basename(syspath);
> - }
> -
> - g_debug(" parse sysfs path '%s'", syspath);
> - is_virtual = is_disk_virtual(syspath, errp);
> - if (*errp != NULL) {
> - return;
> - }
> - if (is_virtual) {
> - build_guest_fsinfo_for_virtual_device(syspath, fs, errp);
> - } else {
> - build_guest_fsinfo_for_real_device(syspath, fs, errp);
> - }
> -}
> -
> -#ifdef CONFIG_LIBUDEV
> -
> -/*
> - * Wrapper around build_guest_fsinfo_for_device() for getting just
> - * the disk address.
> - */
> -static GuestDiskAddress *get_disk_address(const char *syspath,
> Error **errp)
> -{
> - g_autoptr(GuestFilesystemInfo) fs = NULL;
> -
> - fs = g_new0(GuestFilesystemInfo, 1);
> - build_guest_fsinfo_for_device(syspath, fs, errp);
> - if (fs->disk != NULL) {
> - return g_steal_pointer(&fs->disk->value);
> - }
> - return NULL;
> -}
> -
> -static char *get_alias_for_syspath(const char *syspath)
> -{
> - struct udev *udev = NULL;
> - struct udev_device *udevice = NULL;
> - char *ret = NULL;
> -
> - udev = udev_new();
> - if (udev == NULL) {
> - g_debug("failed to query udev");
> - goto out;
> - }
> - udevice = udev_device_new_from_syspath(udev, syspath);
> - if (udevice == NULL) {
> - g_debug("failed to query udev for path: %s", syspath);
> - goto out;
> - } else {
> - const char *alias = udev_device_get_property_value(
> - udevice, "DM_NAME");
> - /*
> - * NULL means there was an error and empty string means
> there is no
> - * alias. In case of no alias we return NULL instead of
> empty string.
> - */
> - if (alias == NULL) {
> - g_debug("failed to query udev for device alias for: %s",
> - syspath);
> - } else if (*alias != 0) {
> - ret = g_strdup(alias);
> - }
> - }
> -
> -out:
> - udev_unref(udev);
> - udev_device_unref(udevice);
> - return ret;
> -}
> -
> -static char *get_device_for_syspath(const char *syspath)
> -{
> - struct udev *udev = NULL;
> - struct udev_device *udevice = NULL;
> - char *ret = NULL;
> -
> - udev = udev_new();
> - if (udev == NULL) {
> - g_debug("failed to query udev");
> - goto out;
> - }
> - udevice = udev_device_new_from_syspath(udev, syspath);
> - if (udevice == NULL) {
> - g_debug("failed to query udev for path: %s", syspath);
> - goto out;
> - } else {
> - ret = g_strdup(udev_device_get_devnode(udevice));
> - }
> -
> -out:
> - udev_unref(udev);
> - udev_device_unref(udevice);
> - return ret;
> -}
> -
> -static void get_disk_deps(const char *disk_dir, GuestDiskInfo *disk)
> -{
> - g_autofree char *deps_dir = NULL;
> - const gchar *dep;
> - GDir *dp_deps = NULL;
> -
> - /* List dependent disks */
> - deps_dir = g_strdup_printf("%s/slaves", disk_dir);
> - g_debug(" listing entries in: %s", deps_dir);
> - dp_deps = g_dir_open(deps_dir, 0, NULL);
> - if (dp_deps == NULL) {
> - g_debug("failed to list entries in %s", deps_dir);
> - return;
> - }
> - disk->has_dependencies = true;
> - while ((dep = g_dir_read_name(dp_deps)) != NULL) {
> - g_autofree char *dep_dir = NULL;
> - char *dev_name;
> -
> - /* Add dependent disks */
> - dep_dir = g_strdup_printf("%s/%s", deps_dir, dep);
> - dev_name = get_device_for_syspath(dep_dir);
> - if (dev_name != NULL) {
> - g_debug(" adding dependent device: %s", dev_name);
> - QAPI_LIST_PREPEND(disk->dependencies, dev_name);
> - }
> - }
> - g_dir_close(dp_deps);
> -}
> -
> -/*
> - * Detect partitions subdirectory, name is "<disk_name><number>" or
> - * "<disk_name>p<number>"
> - *
> - * @disk_name -- last component of /sys path (e.g. sda)
> - * @disk_dir -- sys path of the disk (e.g. /sys/block/sda)
> - * @disk_dev -- device node of the disk (e.g. /dev/sda)
> - */
> -static GuestDiskInfoList *get_disk_partitions(
> - GuestDiskInfoList *list,
> - const char *disk_name, const char *disk_dir,
> - const char *disk_dev)
> -{
> - GuestDiskInfoList *ret = list;
> - struct dirent *de_disk;
> - DIR *dp_disk = NULL;
> - size_t len = strlen(disk_name);
> -
> - dp_disk = opendir(disk_dir);
> - while ((de_disk = readdir(dp_disk)) != NULL) {
> - g_autofree char *partition_dir = NULL;
> - char *dev_name;
> - GuestDiskInfo *partition;
> -
> - if (!(de_disk->d_type & DT_DIR)) {
> - continue;
> - }
> -
> - if (!(strncmp(disk_name, de_disk->d_name, len) == 0 &&
> - ((*(de_disk->d_name + len) == 'p' &&
> - isdigit(*(de_disk->d_name + len + 1))) ||
> - isdigit(*(de_disk->d_name + len))))) {
> - continue;
> - }
> -
> - partition_dir = g_strdup_printf("%s/%s",
> - disk_dir, de_disk->d_name);
> - dev_name = get_device_for_syspath(partition_dir);
> - if (dev_name == NULL) {
> - g_debug("Failed to get device name for syspath: %s",
> - disk_dir);
> - continue;
> - }
> - partition = g_new0(GuestDiskInfo, 1);
> - partition->name = dev_name;
> - partition->partition = true;
> - partition->has_dependencies = true;
> - /* Add parent disk as dependent for easier tracking of
> hierarchy */
> - QAPI_LIST_PREPEND(partition->dependencies,
> g_strdup(disk_dev));
> -
> - QAPI_LIST_PREPEND(ret, partition);
> - }
> - closedir(dp_disk);
> -
> - return ret;
> -}
> -
> -static void get_nvme_smart(GuestDiskInfo *disk)
> -{
> - int fd;
> - GuestNVMeSmart *smart;
> - NvmeSmartLog log = {0};
> - struct nvme_admin_cmd cmd = {
> - .opcode = NVME_ADM_CMD_GET_LOG_PAGE,
> - .nsid = NVME_NSID_BROADCAST,
> - .addr = (uintptr_t)&log,
> - .data_len = sizeof(log),
> - .cdw10 = NVME_LOG_SMART_INFO | (1 << 15) /* RAE bit */
> - | (((sizeof(log) >> 2) - 1) << 16)
> - };
> -
> - fd = qga_open_cloexec(disk->name, O_RDONLY, 0);
> - if (fd == -1) {
> - g_debug("Failed to open device: %s: %s", disk->name,
> g_strerror(errno));
> - return;
> - }
> -
> - if (ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd)) {
> - g_debug("Failed to get smart: %s: %s", disk->name,
> g_strerror(errno));
> - close(fd);
> - return;
> - }
> -
> - disk->has_smart = true;
> - disk->smart = g_new0(GuestDiskSmart, 1);
> - disk->smart->type = GUEST_DISK_BUS_TYPE_NVME;
> -
> - smart = &disk->smart->u.nvme;
> - smart->critical_warning = log.critical_warning;
> - smart->temperature = lduw_le_p(&log.temperature); /*
> unaligned field */
> - smart->available_spare = log.available_spare;
> - smart->available_spare_threshold = log.available_spare_threshold;
> - smart->percentage_used = log.percentage_used;
> - smart->data_units_read_lo = le64_to_cpu(log.data_units_read[0]);
> - smart->data_units_read_hi = le64_to_cpu(log.data_units_read[1]);
> - smart->data_units_written_lo =
> le64_to_cpu(log.data_units_written[0]);
> - smart->data_units_written_hi =
> le64_to_cpu(log.data_units_written[1]);
> - smart->host_read_commands_lo =
> le64_to_cpu(log.host_read_commands[0]);
> - smart->host_read_commands_hi =
> le64_to_cpu(log.host_read_commands[1]);
> - smart->host_write_commands_lo =
> le64_to_cpu(log.host_write_commands[0]);
> - smart->host_write_commands_hi =
> le64_to_cpu(log.host_write_commands[1]);
> - smart->controller_busy_time_lo =
> le64_to_cpu(log.controller_busy_time[0]);
> - smart->controller_busy_time_hi =
> le64_to_cpu(log.controller_busy_time[1]);
> - smart->power_cycles_lo = le64_to_cpu(log.power_cycles[0]);
> - smart->power_cycles_hi = le64_to_cpu(log.power_cycles[1]);
> - smart->power_on_hours_lo = le64_to_cpu(log.power_on_hours[0]);
> - smart->power_on_hours_hi = le64_to_cpu(log.power_on_hours[1]);
> - smart->unsafe_shutdowns_lo =
> le64_to_cpu(log.unsafe_shutdowns[0]);
> - smart->unsafe_shutdowns_hi =
> le64_to_cpu(log.unsafe_shutdowns[1]);
> - smart->media_errors_lo = le64_to_cpu(log.media_errors[0]);
> - smart->media_errors_hi = le64_to_cpu(log.media_errors[1]);
> - smart->number_of_error_log_entries_lo =
> - le64_to_cpu(log.number_of_error_log_entries[0]);
> - smart->number_of_error_log_entries_hi =
> - le64_to_cpu(log.number_of_error_log_entries[1]);
> -
> - close(fd);
> -}
> -
> -static void get_disk_smart(GuestDiskInfo *disk)
> -{
> - if (disk->has_address
> - && (disk->address->bus_type == GUEST_DISK_BUS_TYPE_NVME)) {
> - get_nvme_smart(disk);
> - }
> -}
> -
> -GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
> -{
> - GuestDiskInfoList *ret = NULL;
> - GuestDiskInfo *disk;
> - DIR *dp = NULL;
> - struct dirent *de = NULL;
> -
> - g_debug("listing /sys/block directory");
> - dp = opendir("/sys/block");
> - if (dp == NULL) {
> - error_setg_errno(errp, errno, "Can't open directory
> \"/sys/block\"");
> - return NULL;
> - }
> - while ((de = readdir(dp)) != NULL) {
> - g_autofree char *disk_dir = NULL, *line = NULL,
> - *size_path = NULL;
> - char *dev_name;
> - Error *local_err = NULL;
> - if (de->d_type != DT_LNK) {
> - g_debug(" skipping entry: %s", de->d_name);
> - continue;
> - }
> -
> - /* Check size and skip zero-sized disks */
> - g_debug(" checking disk size");
> - size_path = g_strdup_printf("/sys/block/%s/size",
> de->d_name);
> - if (!g_file_get_contents(size_path, &line, NULL, NULL)) {
> - g_debug(" failed to read disk size");
> - continue;
> - }
> - if (g_strcmp0(line, "0\n") == 0) {
> - g_debug(" skipping zero-sized disk");
> - continue;
> - }
> -
> - g_debug(" adding %s", de->d_name);
> - disk_dir = g_strdup_printf("/sys/block/%s", de->d_name);
> - dev_name = get_device_for_syspath(disk_dir);
> - if (dev_name == NULL) {
> - g_debug("Failed to get device name for syspath: %s",
> - disk_dir);
> - continue;
> - }
> - disk = g_new0(GuestDiskInfo, 1);
> - disk->name = dev_name;
> - disk->partition = false;
> - disk->alias = get_alias_for_syspath(disk_dir);
> - disk->has_alias = (disk->alias != NULL);
> - QAPI_LIST_PREPEND(ret, disk);
> -
> - /* Get address for non-virtual devices */
> - bool is_virtual = is_disk_virtual(disk_dir, &local_err);
> - if (local_err != NULL) {
> - g_debug(" failed to check disk path, ignoring error:
> %s",
> - error_get_pretty(local_err));
> - error_free(local_err);
> - local_err = NULL;
> - /* Don't try to get the address */
> - is_virtual = true;
> - }
> - if (!is_virtual) {
> - disk->address = get_disk_address(disk_dir, &local_err);
> - if (local_err != NULL) {
> - g_debug(" failed to get device info, ignoring
> error: %s",
> - error_get_pretty(local_err));
> - error_free(local_err);
> - local_err = NULL;
> - } else if (disk->address != NULL) {
> - disk->has_address = true;
> - }
> - }
> -
> - get_disk_deps(disk_dir, disk);
> - get_disk_smart(disk);
> - ret = get_disk_partitions(ret, de->d_name, disk_dir,
> dev_name);
> - }
> -
> - closedir(dp);
> -
> - return ret;
> -}
> -
> -#else
> -
> -GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
> -{
> - error_setg(errp, QERR_UNSUPPORTED);
> - return NULL;
> -}
> -
> -#endif
> -
> -/* Return a list of the disk device(s)' info which @mount lies on */
> -static GuestFilesystemInfo *build_guest_fsinfo(struct FsMount *mount,
> - Error **errp)
> -{
> - GuestFilesystemInfo *fs = g_malloc0(sizeof(*fs));
> - struct statvfs buf;
> - unsigned long used, nonroot_total, fr_size;
> - char *devpath = g_strdup_printf("/sys/dev/block/%u:%u",
> - mount->devmajor,
> mount->devminor);
> -
> - fs->mountpoint = g_strdup(mount->dirname);
> - fs->type = g_strdup(mount->devtype);
> - build_guest_fsinfo_for_device(devpath, fs, errp);
> -
> - if (statvfs(fs->mountpoint, &buf) == 0) {
> - fr_size = buf.f_frsize;
> - used = buf.f_blocks - buf.f_bfree;
> - nonroot_total = used + buf.f_bavail;
> - fs->used_bytes = used * fr_size;
> - fs->total_bytes = nonroot_total * fr_size;
> -
> - fs->has_total_bytes = true;
> - fs->has_used_bytes = true;
> - }
> -
> - g_free(devpath);
> -
> - return fs;
> -}
> -
> -GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
> -{
> - FsMountList mounts;
> - struct FsMount *mount;
> - GuestFilesystemInfoList *ret = NULL;
> - Error *local_err = NULL;
> -
> - QTAILQ_INIT(&mounts);
> - if (!build_fs_mount_list(&mounts, &local_err)) {
> - error_propagate(errp, local_err);
> - return NULL;
> - }
> -
> - QTAILQ_FOREACH(mount, &mounts, next) {
> - g_debug("Building guest fsinfo for '%s'", mount->dirname);
> -
> - QAPI_LIST_PREPEND(ret, build_guest_fsinfo(mount,
> &local_err));
> - if (local_err) {
> - error_propagate(errp, local_err);
> - qapi_free_GuestFilesystemInfoList(ret);
> - ret = NULL;
> - break;
> - }
> - }
> -
> - free_fs_mount_list(&mounts);
> - return ret;
> -}
> -
> -
> -typedef enum {
> - FSFREEZE_HOOK_THAW = 0,
> - FSFREEZE_HOOK_FREEZE,
> -} FsfreezeHookArg;
> -
> -static const char *fsfreeze_hook_arg_string[] = {
> - "thaw",
> - "freeze",
> -};
> -
> -static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **errp)
> -{
> - 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(errp, 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);
> -
> - execl(hook, hook, arg_str, NULL);
> - _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 (local_err) {
> - error_propagate(errp, local_err);
> - return;
> - }
> -
> - if (!WIFEXITED(status)) {
> - error_setg(errp, "fsfreeze hook has terminated abnormally");
> - return;
> - }
> -
> - status = WEXITSTATUS(status);
> - if (status) {
> - error_setg(errp, "fsfreeze hook has failed with status
> %d", status);
> - return;
> - }
> -}
> -
> -/*
> - * Return status of freeze/thaw
> - */
> -GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
> -{
> - if (ga_is_frozen(ga_state)) {
> - return GUEST_FSFREEZE_STATUS_FROZEN;
> - }
> -
> - return GUEST_FSFREEZE_STATUS_THAWED;
> -}
> -
> -int64_t qmp_guest_fsfreeze_freeze(Error **errp)
> -{
> - return qmp_guest_fsfreeze_freeze_list(false, NULL, errp);
> -}
> -
> -/*
> - * Walk list of mounted file systems in the guest, and freeze the
> ones which
> - * are real local file systems.
> - */
> -int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints,
> - strList *mountpoints,
> - Error **errp)
> -{
> - int ret = 0, i = 0;
> - strList *list;
> - FsMountList mounts;
> - struct FsMount *mount;
> - Error *local_err = NULL;
> - int fd;
> -
> - slog("guest-fsfreeze called");
> -
> - execute_fsfreeze_hook(FSFREEZE_HOOK_FREEZE, &local_err);
> - if (local_err) {
> - error_propagate(errp, local_err);
> - return -1;
> - }
> -
> - QTAILQ_INIT(&mounts);
> - if (!build_fs_mount_list(&mounts, &local_err)) {
> - error_propagate(errp, local_err);
> - return -1;
> - }
> -
> - /* cannot risk guest agent blocking itself on a write in this
> state */
> - ga_set_frozen(ga_state);
> -
> - QTAILQ_FOREACH_REVERSE(mount, &mounts, next) {
> - /* To issue fsfreeze in the reverse order of mounts,
> check if the
> - * mount is listed in the list here */
> - if (has_mountpoints) {
> - for (list = mountpoints; list; list = list->next) {
> - if (strcmp(list->value, mount->dirname) == 0) {
> - break;
> - }
> - }
> - if (!list) {
> - continue;
> - }
> - }
> -
> - fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
> - if (fd == -1) {
> - error_setg_errno(errp, errno, "failed to open %s",
> mount->dirname);
> - goto error;
> - }
> -
> - /* we try to cull filesystems we know won't work in
> advance, but other
> - * filesystems may not implement fsfreeze for less
> obvious reasons.
> - * these will report EOPNOTSUPP. we simply ignore these
> when tallying
> - * the number of frozen filesystems.
> - * if a filesystem is mounted more than once (aka bind
> mount) a
> - * consecutive attempt to freeze an already frozen
> filesystem will
> - * return EBUSY.
> - *
> - * any other error means a failure to freeze a filesystem we
> - * expect to be freezable, so return an error in those cases
> - * and return system to thawed state.
> - */
> - ret = ioctl(fd, FIFREEZE);
> - if (ret == -1) {
> - if (errno != EOPNOTSUPP && errno != EBUSY) {
> - error_setg_errno(errp, errno, "failed to freeze %s",
> - mount->dirname);
> - close(fd);
> - goto error;
> - }
> - } else {
> - i++;
> - }
> - close(fd);
> - }
> -
> - free_fs_mount_list(&mounts);
> - /* We may not issue any FIFREEZE here.
> - * Just unset ga_state here and ready for the next call.
> - */
> - if (i == 0) {
> - ga_unset_frozen(ga_state);
> - }
> - return i;
> -
> -error:
> - free_fs_mount_list(&mounts);
> - qmp_guest_fsfreeze_thaw(NULL);
> - return 0;
> -}
> -
> -/*
> - * Walk list of frozen file systems in the guest, and thaw them.
> - */
> -int64_t qmp_guest_fsfreeze_thaw(Error **errp)
> -{
> - int ret;
> - FsMountList mounts;
> - FsMount *mount;
> - int fd, i = 0, logged;
> - Error *local_err = NULL;
> -
> - QTAILQ_INIT(&mounts);
> - if (!build_fs_mount_list(&mounts, &local_err)) {
> - error_propagate(errp, local_err);
> - return 0;
> - }
> -
> - QTAILQ_FOREACH(mount, &mounts, next) {
> - logged = false;
> - fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
> - if (fd == -1) {
> - continue;
> - }
> - /* we have no way of knowing whether a filesystem was
> actually unfrozen
> - * as a result of a successful call to FITHAW, only that
> if an error
> - * was returned the filesystem was *not* unfrozen by that
> particular
> - * call.
> - *
> - * since multiple preceding FIFREEZEs require multiple
> calls to FITHAW
> - * to unfreeze, continuing issuing FITHAW until an error
> is returned,
> - * in which case either the filesystem is in an
> unfreezable state, or,
> - * more likely, it was thawed previously (and remains so
> afterward).
> - *
> - * also, since the most recent successful call is the one
> that did
> - * the actual unfreeze, we can use this to provide an
> accurate count
> - * of the number of filesystems unfrozen by
> guest-fsfreeze-thaw, which
> - * may * be useful for determining whether a filesystem
> was unfrozen
> - * during the freeze/thaw phase by a process other than
> qemu-ga.
> - */
> - do {
> - ret = ioctl(fd, FITHAW);
> - if (ret == 0 && !logged) {
> - i++;
> - logged = true;
> - }
> - } while (ret == 0);
> - close(fd);
> - }
> -
> - ga_unset_frozen(ga_state);
> - free_fs_mount_list(&mounts);
> -
> - execute_fsfreeze_hook(FSFREEZE_HOOK_THAW, errp);
> -
> - return i;
> -}
> -
> -static void guest_fsfreeze_cleanup(void)
> -{
> - Error *err = NULL;
> -
> - if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
> - qmp_guest_fsfreeze_thaw(&err);
> - if (err) {
> - slog("failed to clean up frozen filesystems: %s",
> - error_get_pretty(err));
> - error_free(err);
> - }
> - }
> -}
> -#endif /* CONFIG_FSFREEZE */
> -
> -#if defined(CONFIG_FSTRIM)
> -/*
> - * Walk list of mounted file systems in the guest, and trim them.
> - */
> -GuestFilesystemTrimResponse *
> -qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
> -{
> - GuestFilesystemTrimResponse *response;
> - GuestFilesystemTrimResult *result;
> - int ret = 0;
> - FsMountList mounts;
> - struct FsMount *mount;
> - int fd;
> - struct fstrim_range r;
> -
> - slog("guest-fstrim called");
> -
> - QTAILQ_INIT(&mounts);
> - if (!build_fs_mount_list(&mounts, errp)) {
> - return NULL;
> - }
> -
> - response = g_malloc0(sizeof(*response));
> -
> - QTAILQ_FOREACH(mount, &mounts, next) {
> - result = g_malloc0(sizeof(*result));
> - result->path = g_strdup(mount->dirname);
> -
> - QAPI_LIST_PREPEND(response->paths, result);
> -
> - fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
> - if (fd == -1) {
> - result->error = g_strdup_printf("failed to open: %s",
> - strerror(errno));
> - result->has_error = true;
> - continue;
> - }
> -
> - /* We try to cull filesystems we know won't work in
> advance, but other
> - * filesystems may not implement fstrim for less obvious
> reasons.
> - * These will report EOPNOTSUPP; while in some other
> cases ENOTTY
> - * will be reported (e.g. CD-ROMs).
> - * Any other error means an unexpected error.
> - */
> - r.start = 0;
> - r.len = -1;
> - r.minlen = has_minimum ? minimum : 0;
> - ret = ioctl(fd, FITRIM, &r);
> - if (ret == -1) {
> - result->has_error = true;
> - if (errno == ENOTTY || errno == EOPNOTSUPP) {
> - result->error = g_strdup("trim not supported");
> - } else {
> - result->error = g_strdup_printf("failed to trim: %s",
> - strerror(errno));
> - }
> - close(fd);
> - continue;
> - }
> -
> - result->has_minimum = true;
> - result->minimum = r.minlen;
> - result->has_trimmed = true;
> - result->trimmed = r.len;
> - close(fd);
> - }
> -
> - free_fs_mount_list(&mounts);
> - return response;
> -}
> -#endif /* CONFIG_FSTRIM */
> -
> -
> -#define LINUX_SYS_STATE_FILE "/sys/power/state"
> -#define SUSPEND_SUPPORTED 0
> -#define SUSPEND_NOT_SUPPORTED 1
> -
> -typedef enum {
> - SUSPEND_MODE_DISK = 0,
> - SUSPEND_MODE_RAM = 1,
> - SUSPEND_MODE_HYBRID = 2,
> -} SuspendMode;
> -
> -/*
> - * Executes a command in a child process using g_spawn_sync,
> - * returning an int >= 0 representing the exit status of the
> - * process.
> - *
> - * If the program wasn't found in path, returns -1.
> - *
> - * If a problem happened when creating the child process,
> - * returns -1 and errp is set.
> - */
> -static int run_process_child(const char *command[], Error **errp)
> -{
> - int exit_status, spawn_flag;
> - GError *g_err = NULL;
> - bool success;
> -
> - spawn_flag = G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL |
> - G_SPAWN_STDERR_TO_DEV_NULL;
> -
> - success = g_spawn_sync(NULL, (char **)command, NULL, spawn_flag,
> - NULL, NULL, NULL, NULL,
> - &exit_status, &g_err);
> -
> - if (success) {
> - return WEXITSTATUS(exit_status);
> - }
> -
> - if (g_err && (g_err->code != G_SPAWN_ERROR_NOENT)) {
> - error_setg(errp, "failed to create child process, error
> '%s'",
> - g_err->message);
> - }
> -
> - g_error_free(g_err);
> - return -1;
> -}
> -
> -static bool systemd_supports_mode(SuspendMode mode, Error **errp)
> -{
> - const char *systemctl_args[3] = {"systemd-hibernate",
> "systemd-suspend",
> - "systemd-hybrid-sleep"};
> - const char *cmd[4] = {"systemctl", "status",
> systemctl_args[mode], NULL};
> - int status;
> -
> - status = run_process_child(cmd, errp);
> -
> - /*
> - * systemctl status uses LSB return codes so we can expect
> - * status > 0 and be ok. To assert if the guest has support
> - * for the selected suspend mode, status should be < 4. 4 is
> - * the code for unknown service status, the return value when
> - * the service does not exist. A common value is status = 3
> - * (program is not running).
> - */
> - if (status > 0 && status < 4) {
> - return true;
> - }
> -
> - return false;
> -}
> -
> -static void systemd_suspend(SuspendMode mode, Error **errp)
> -{
> - Error *local_err = NULL;
> - const char *systemctl_args[3] = {"hibernate", "suspend",
> "hybrid-sleep"};
> - const char *cmd[3] = {"systemctl", systemctl_args[mode], NULL};
> - int status;
> -
> - status = run_process_child(cmd, &local_err);
> -
> - if (status == 0) {
> - return;
> - }
> -
> - if ((status == -1) && !local_err) {
> - error_setg(errp, "the helper program 'systemctl %s' was
> not found",
> - systemctl_args[mode]);
> - return;
> - }
> -
> - if (local_err) {
> - error_propagate(errp, local_err);
> - } else {
> - error_setg(errp, "the helper program 'systemctl %s'
> returned an "
> - "unexpected exit status code (%d)",
> - systemctl_args[mode], status);
> - }
> -}
> -
> -static bool pmutils_supports_mode(SuspendMode mode, Error **errp)
> -{
> - Error *local_err = NULL;
> - const char *pmutils_args[3] = {"--hibernate", "--suspend",
> - "--suspend-hybrid"};
> - const char *cmd[3] = {"pm-is-supported", pmutils_args[mode],
> NULL};
> - int status;
> -
> - status = run_process_child(cmd, &local_err);
> -
> - if (status == SUSPEND_SUPPORTED) {
> - return true;
> - }
> -
> - if ((status == -1) && !local_err) {
> - return false;
> - }
> -
> - if (local_err) {
> - error_propagate(errp, local_err);
> - } else {
> - error_setg(errp,
> - "the helper program '%s' returned an
> unexpected exit"
> - " status code (%d)", "pm-is-supported", status);
> - }
> -
> - return false;
> -}
> -
> -static void pmutils_suspend(SuspendMode mode, Error **errp)
> -{
> - Error *local_err = NULL;
> - const char *pmutils_binaries[3] = {"pm-hibernate", "pm-suspend",
> - "pm-suspend-hybrid"};
> - const char *cmd[2] = {pmutils_binaries[mode], NULL};
> - int status;
> -
> - status = run_process_child(cmd, &local_err);
> -
> - if (status == 0) {
> - return;
> - }
> -
> - if ((status == -1) && !local_err) {
> - error_setg(errp, "the helper program '%s' was not found",
> - pmutils_binaries[mode]);
> - return;
> - }
> -
> - if (local_err) {
> - error_propagate(errp, local_err);
> - } else {
> - error_setg(errp,
> - "the helper program '%s' returned an
> unexpected exit"
> - " status code (%d)", pmutils_binaries[mode],
> status);
> - }
> -}
> -
> -static bool linux_sys_state_supports_mode(SuspendMode mode, Error
> **errp)
> -{
> - const char *sysfile_strs[3] = {"disk", "mem", NULL};
> - const char *sysfile_str = sysfile_strs[mode];
> - char buf[32]; /* hopefully big enough */
> - int fd;
> - ssize_t ret;
> -
> - if (!sysfile_str) {
> - error_setg(errp, "unknown guest suspend mode");
> - return false;
> - }
> -
> - fd = open(LINUX_SYS_STATE_FILE, O_RDONLY);
> - if (fd < 0) {
> - return false;
> - }
> -
> - ret = read(fd, buf, sizeof(buf) - 1);
> - close(fd);
> - if (ret <= 0) {
> - return false;
> - }
> - buf[ret] = '\0';
> -
> - if (strstr(buf, sysfile_str)) {
> - return true;
> - }
> - return false;
> -}
> -
> -static void linux_sys_state_suspend(SuspendMode mode, Error **errp)
> -{
> - Error *local_err = NULL;
> - const char *sysfile_strs[3] = {"disk", "mem", NULL};
> - const char *sysfile_str = sysfile_strs[mode];
> - pid_t pid;
> - int status;
> -
> - if (!sysfile_str) {
> - error_setg(errp, "unknown guest suspend mode");
> - return;
> - }
> -
> - pid = fork();
> - if (!pid) {
> - /* child */
> - int fd;
> -
> - setsid();
> - reopen_fd_to_null(0);
> - reopen_fd_to_null(1);
> - reopen_fd_to_null(2);
> -
> - fd = open(LINUX_SYS_STATE_FILE, O_WRONLY);
> - if (fd < 0) {
> - _exit(EXIT_FAILURE);
> - }
> -
> - if (write(fd, sysfile_str, strlen(sysfile_str)) < 0) {
> - _exit(EXIT_FAILURE);
> - }
> -
> - _exit(EXIT_SUCCESS);
> - } else if (pid < 0) {
> - error_setg_errno(errp, errno, "failed to create child
> process");
> - return;
> - }
> -
> - ga_wait_child(pid, &status, &local_err);
> - if (local_err) {
> - error_propagate(errp, local_err);
> - return;
> - }
> -
> - if (WEXITSTATUS(status)) {
> - error_setg(errp, "child process has failed to suspend");
> - }
> -
> -}
> -
> -static void guest_suspend(SuspendMode mode, Error **errp)
> -{
> - Error *local_err = NULL;
> - bool mode_supported = false;
> -
> - if (systemd_supports_mode(mode, &local_err)) {
> - mode_supported = true;
> - systemd_suspend(mode, &local_err);
> - }
> -
> - if (!local_err) {
> - return;
> - }
> -
> - error_free(local_err);
> - local_err = NULL;
> -
> - if (pmutils_supports_mode(mode, &local_err)) {
> - mode_supported = true;
> - pmutils_suspend(mode, &local_err);
> - }
> -
> - if (!local_err) {
> - return;
> - }
> -
> - error_free(local_err);
> - local_err = NULL;
> -
> - if (linux_sys_state_supports_mode(mode, &local_err)) {
> - mode_supported = true;
> - linux_sys_state_suspend(mode, &local_err);
> - }
> -
> - if (!mode_supported) {
> - error_free(local_err);
> - error_setg(errp,
> - "the requested suspend mode is not supported
> by the guest");
> - } else {
> - error_propagate(errp, local_err);
> - }
> -}
> -
> -void qmp_guest_suspend_disk(Error **errp)
> -{
> - guest_suspend(SUSPEND_MODE_DISK, errp);
> -}
> -
> -void qmp_guest_suspend_ram(Error **errp)
> -{
> - guest_suspend(SUSPEND_MODE_RAM, errp);
> -}
> -
> -void qmp_guest_suspend_hybrid(Error **errp)
> -{
> - guest_suspend(SUSPEND_MODE_HYBRID, errp);
> -}
> -
> -/* 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,
> - char *dirpath, Error **errp)
> -{
> - int fd;
> - int res;
> - int dirfd;
> - static const char fn[] = "online";
> -
> - dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
> - if (dirfd == -1) {
> - error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
> - return;
> - }
> -
> - 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);
> -}
> -
> -GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
> -{
> - GuestLogicalProcessorList *head, **tail;
> - const char *cpu_dir = "/sys/devices/system/cpu";
> - const gchar *line;
> - g_autoptr(GDir) cpu_gdir = NULL;
> - Error *local_err = NULL;
> -
> - head = NULL;
> - tail = &head;
> - cpu_gdir = g_dir_open(cpu_dir, 0, NULL);
> -
> - if (cpu_gdir == NULL) {
> - error_setg_errno(errp, errno, "failed to list entries:
> %s", cpu_dir);
> - return NULL;
> - }
> -
> - while (local_err == NULL && (line =
> g_dir_read_name(cpu_gdir)) != NULL) {
> - GuestLogicalProcessor *vcpu;
> - int64_t id;
> - if (sscanf(line, "cpu%" PRId64, &id)) {
> - g_autofree char *path =
> g_strdup_printf("/sys/devices/system/cpu/"
> - "cpu%" PRId64
> "/", id);
> - vcpu = g_malloc0(sizeof *vcpu);
> - vcpu->logical_id = id;
> - vcpu->has_can_offline = true; /* lolspeak ftw */
> - transfer_vcpu(vcpu, true, path, &local_err);
> - QAPI_LIST_APPEND(tail, vcpu);
> - }
> - }
> -
> - 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) {
> - char *path =
> g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/",
> - vcpus->value->logical_id);
> -
> - transfer_vcpu(vcpus->value, false, path, &local_err);
> - g_free(path);
> - 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;
> -}
> -
> -void qmp_guest_set_user_password(const char *username,
> - const char *password,
> - bool crypted,
> - Error **errp)
> -{
> - Error *local_err = NULL;
> - char *passwd_path = NULL;
> - pid_t pid;
> - int status;
> - int datafd[2] = { -1, -1 };
> - char *rawpasswddata = NULL;
> - size_t rawpasswdlen;
> - char *chpasswddata = NULL;
> - size_t chpasswdlen;
> -
> - rawpasswddata = (char *)qbase64_decode(password, -1,
> &rawpasswdlen, errp);
> - if (!rawpasswddata) {
> - return;
> - }
> - rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
> - rawpasswddata[rawpasswdlen] = '\0';
> -
> - if (strchr(rawpasswddata, '\n')) {
> - error_setg(errp, "forbidden characters in raw password");
> - goto out;
> - }
> -
> - if (strchr(username, '\n') ||
> - strchr(username, ':')) {
> - error_setg(errp, "forbidden characters in username");
> - goto out;
> - }
> -
> - chpasswddata = g_strdup_printf("%s:%s\n", username,
> rawpasswddata);
> - chpasswdlen = strlen(chpasswddata);
> -
> - passwd_path = g_find_program_in_path("chpasswd");
> -
> - if (!passwd_path) {
> - error_setg(errp, "cannot find 'passwd' program in PATH");
> - goto out;
> - }
> -
> - if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) {
> - error_setg(errp, "cannot create pipe FDs");
> - goto out;
> - }
> -
> - pid = fork();
> - if (pid == 0) {
> - close(datafd[1]);
> - /* child */
> - setsid();
> - dup2(datafd[0], 0);
> - reopen_fd_to_null(1);
> - reopen_fd_to_null(2);
> -
> - if (crypted) {
> - execl(passwd_path, "chpasswd", "-e", NULL);
> - } else {
> - execl(passwd_path, "chpasswd", NULL);
> - }
> - _exit(EXIT_FAILURE);
> - } else if (pid < 0) {
> - error_setg_errno(errp, errno, "failed to create child
> process");
> - goto out;
> - }
> - close(datafd[0]);
> - datafd[0] = -1;
> -
> - if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) !=
> chpasswdlen) {
> - error_setg_errno(errp, errno, "cannot write new account
> password");
> - goto out;
> - }
> - close(datafd[1]);
> - datafd[1] = -1;
> -
> - ga_wait_child(pid, &status, &local_err);
> - if (local_err) {
> - error_propagate(errp, local_err);
> - goto out;
> - }
> -
> - if (!WIFEXITED(status)) {
> - error_setg(errp, "child process has terminated abnormally");
> - goto out;
> - }
> -
> - if (WEXITSTATUS(status)) {
> - error_setg(errp, "child process has failed to set user
> password");
> - goto out;
> - }
> -
> -out:
> - g_free(chpasswddata);
> - g_free(rawpasswddata);
> - g_free(passwd_path);
> - if (datafd[0] != -1) {
> - close(datafd[0]);
> - }
> - if (datafd[1] != -1) {
> - close(datafd[1]);
> - }
> -}
> -
> -static void ga_read_sysfs_file(int dirfd, const char *pathname,
> char *buf,
> - int size, Error **errp)
> -{
> - int fd;
> - int res;
> -
> - errno = 0;
> - fd = openat(dirfd, pathname, O_RDONLY);
> - if (fd == -1) {
> - error_setg_errno(errp, errno, "open sysfs file \"%s\"",
> pathname);
> - return;
> - }
> -
> - res = pread(fd, buf, size, 0);
> - if (res == -1) {
> - error_setg_errno(errp, errno, "pread sysfs file \"%s\"",
> pathname);
> - } else if (res == 0) {
> - error_setg(errp, "pread sysfs file \"%s\": unexpected
> EOF", pathname);
> - }
> - close(fd);
> -}
> -
> -static void ga_write_sysfs_file(int dirfd, const char *pathname,
> - const char *buf, int size, Error
> **errp)
> -{
> - int fd;
> -
> - errno = 0;
> - fd = openat(dirfd, pathname, O_WRONLY);
> - if (fd == -1) {
> - error_setg_errno(errp, errno, "open sysfs file \"%s\"",
> pathname);
> - return;
> - }
> -
> - if (pwrite(fd, buf, size, 0) == -1) {
> - error_setg_errno(errp, errno, "pwrite sysfs file \"%s\"",
> pathname);
> - }
> -
> - close(fd);
> -}
> -
> -/* Transfer online/offline status between @mem_blk and the guest
> system.
> - *
> - * On input either @errp or *@errp must be NULL.
> - *
> - * In system-to-@mem_blk direction, the following @mem_blk fields
> are accessed:
> - * - R: mem_blk->phys_index
> - * - W: mem_blk->online
> - * - W: mem_blk->can_offline
> - *
> - * In @mem_blk-to-system direction, the following @mem_blk fields
> are accessed:
> - * - R: mem_blk->phys_index
> - * - R: mem_blk->online
> - *- R: mem_blk->can_offline
> - * Written members remain unmodified on error.
> - */
> -static void transfer_memory_block(GuestMemoryBlock *mem_blk, bool
> sys2memblk,
> - GuestMemoryBlockResponse *result,
> - Error **errp)
> -{
> - char *dirpath;
> - int dirfd;
> - char *status;
> - Error *local_err = NULL;
> -
> - if (!sys2memblk) {
> - DIR *dp;
> -
> - if (!result) {
> - error_setg(errp, "Internal error, 'result' should not
> be NULL");
> - return;
> - }
> - errno = 0;
> - dp = opendir("/sys/devices/system/memory/");
> - /* if there is no 'memory' directory in sysfs,
> - * we think this VM does not support online/offline
> memory block,
> - * any other solution?
> - */
> - if (!dp) {
> - if (errno == ENOENT) {
> - result->response =
> - GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
> - }
> - goto out1;
> - }
> - closedir(dp);
> - }
> -
> - dirpath =
> g_strdup_printf("/sys/devices/system/memory/memory%" PRId64 "/",
> - mem_blk->phys_index);
> - dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
> - if (dirfd == -1) {
> - if (sys2memblk) {
> - error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
> - } else {
> - if (errno == ENOENT) {
> - result->response =
> GUEST_MEMORY_BLOCK_RESPONSE_TYPE_NOT_FOUND;
> - } else {
> - result->response =
> - GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
> - }
> - }
> - g_free(dirpath);
> - goto out1;
> - }
> - g_free(dirpath);
> -
> - status = g_malloc0(10);
> - ga_read_sysfs_file(dirfd, "state", status, 10, &local_err);
> - if (local_err) {
> - /* treat with sysfs file that not exist in old kernel */
> - if (errno == ENOENT) {
> - error_free(local_err);
> - if (sys2memblk) {
> - mem_blk->online = true;
> - mem_blk->can_offline = false;
> - } else if (!mem_blk->online) {
> - result->response =
> - GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
> - }
> - } else {
> - if (sys2memblk) {
> - error_propagate(errp, local_err);
> - } else {
> - error_free(local_err);
> - result->response =
> - GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
> - }
> - }
> - goto out2;
> - }
> -
> - if (sys2memblk) {
> - char removable = '0';
> -
> - mem_blk->online = (strncmp(status, "online", 6) == 0);
> -
> - ga_read_sysfs_file(dirfd, "removable", &removable, 1,
> &local_err);
> - if (local_err) {
> - /* if no 'removable' file, it doesn't support offline
> mem blk */
> - if (errno == ENOENT) {
> - error_free(local_err);
> - mem_blk->can_offline = false;
> - } else {
> - error_propagate(errp, local_err);
> - }
> - } else {
> - mem_blk->can_offline = (removable != '0');
> - }
> - } else {
> - if (mem_blk->online != (strncmp(status, "online", 6) == 0)) {
> - const char *new_state = mem_blk->online ? "online" :
> "offline";
> -
> - ga_write_sysfs_file(dirfd, "state", new_state,
> strlen(new_state),
> - &local_err);
> - if (local_err) {
> - error_free(local_err);
> - result->response =
> - GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
> - goto out2;
> - }
> -
> - result->response =
> GUEST_MEMORY_BLOCK_RESPONSE_TYPE_SUCCESS;
> - result->has_error_code = false;
> - } /* otherwise pretend successful re-(on|off)-lining */
> - }
> - g_free(status);
> - close(dirfd);
> - return;
> -
> -out2:
> - g_free(status);
> - close(dirfd);
> -out1:
> - if (!sys2memblk) {
> - result->has_error_code = true;
> - result->error_code = errno;
> - }
> -}
> -
> -GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
> -{
> - GuestMemoryBlockList *head, **tail;
> - Error *local_err = NULL;
> - struct dirent *de;
> - DIR *dp;
> -
> - head = NULL;
> - tail = &head;
> -
> - dp = opendir("/sys/devices/system/memory/");
> - if (!dp) {
> - /* it's ok if this happens to be a system that doesn't expose
> - * memory blocks via sysfs, but otherwise we should report
> - * an error
> - */
> - if (errno != ENOENT) {
> - error_setg_errno(errp, errno, "Can't open directory"
> - "\"/sys/devices/system/memory/\"");
> - }
> - return NULL;
> - }
> -
> - /* Note: the phys_index of memory block may be discontinuous,
> - * this is because a memblk is the unit of the Sparse Memory
> design, which
> - * allows discontinuous memory ranges (ex. NUMA), so here we
> should
> - * traverse the memory block directory.
> - */
> - while ((de = readdir(dp)) != NULL) {
> - GuestMemoryBlock *mem_blk;
> -
> - if ((strncmp(de->d_name, "memory", 6) != 0) ||
> - !(de->d_type & DT_DIR)) {
> - continue;
> - }
> -
> - mem_blk = g_malloc0(sizeof *mem_blk);
> - /* The d_name is "memoryXXX", phys_index is block id,
> same as XXX */
> - mem_blk->phys_index = strtoul(&de->d_name[6], NULL, 10);
> - mem_blk->has_can_offline = true; /* lolspeak ftw */
> - transfer_memory_block(mem_blk, true, NULL, &local_err);
> - if (local_err) {
> - break;
> - }
> -
> - QAPI_LIST_APPEND(tail, mem_blk);
> - }
> -
> - closedir(dp);
> - if (local_err == NULL) {
> - /* there's no guest with zero memory blocks */
> - if (head == NULL) {
> - error_setg(errp, "guest reported zero memory blocks!");
> - }
> - return head;
> - }
> -
> - qapi_free_GuestMemoryBlockList(head);
> - error_propagate(errp, local_err);
> - return NULL;
> -}
> -
> -GuestMemoryBlockResponseList *
> -qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error
> **errp)
> -{
> - GuestMemoryBlockResponseList *head, **tail;
> - Error *local_err = NULL;
> -
> - head = NULL;
> - tail = &head;
> -
> - while (mem_blks != NULL) {
> - GuestMemoryBlockResponse *result;
> - GuestMemoryBlock *current_mem_blk = mem_blks->value;
> -
> - result = g_malloc0(sizeof(*result));
> - result->phys_index = current_mem_blk->phys_index;
> - transfer_memory_block(current_mem_blk, false, result,
> &local_err);
> - if (local_err) { /* should never happen */
> - goto err;
> - }
> -
> - QAPI_LIST_APPEND(tail, result);
> - mem_blks = mem_blks->next;
> - }
> -
> - return head;
> -err:
> - qapi_free_GuestMemoryBlockResponseList(head);
> - error_propagate(errp, local_err);
> - return NULL;
> -}
> -
> -GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
> -{
> - Error *local_err = NULL;
> - char *dirpath;
> - int dirfd;
> - char *buf;
> - GuestMemoryBlockInfo *info;
> -
> - dirpath = g_strdup_printf("/sys/devices/system/memory/");
> - dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
> - if (dirfd == -1) {
> - error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
> - g_free(dirpath);
> - return NULL;
> - }
> - g_free(dirpath);
> -
> - buf = g_malloc0(20);
> - ga_read_sysfs_file(dirfd, "block_size_bytes", buf, 20,
> &local_err);
> - close(dirfd);
> - if (local_err) {
> - g_free(buf);
> - error_propagate(errp, local_err);
> - return NULL;
> - }
> -
> - info = g_new0(GuestMemoryBlockInfo, 1);
> - info->size = strtol(buf, NULL, 16); /* the unit is bytes */
> -
> - g_free(buf);
> -
> - return info;
> -}
> -
> -#define MAX_NAME_LEN 128
> -static GuestDiskStatsInfoList *guest_get_diskstats(Error **errp)
> -{
> -#ifdef CONFIG_LINUX
> - GuestDiskStatsInfoList *head = NULL, **tail = &head;
> - const char *diskstats = "/proc/diskstats";
> - FILE *fp;
> - size_t n;
> - char *line = NULL;
> -
> - fp = fopen(diskstats, "r");
> - if (fp == NULL) {
> - error_setg_errno(errp, errno, "open(\"%s\")", diskstats);
> - return NULL;
> - }
> -
> - while (getline(&line, &n, fp) != -1) {
> - g_autofree GuestDiskStatsInfo *diskstatinfo = NULL;
> - g_autofree GuestDiskStats *diskstat = NULL;
> - char dev_name[MAX_NAME_LEN];
> - unsigned int ios_pgr, tot_ticks, rq_ticks, wr_ticks,
> dc_ticks, fl_ticks;
> - unsigned long rd_ios, rd_merges_or_rd_sec,
> rd_ticks_or_wr_sec, wr_ios;
> - unsigned long wr_merges, rd_sec_or_wr_ios, wr_sec;
> - unsigned long dc_ios, dc_merges, dc_sec, fl_ios;
> - unsigned int major, minor;
> - int i;
> -
> - i = sscanf(line, "%u %u %s %lu %lu %lu"
> - "%lu %lu %lu %lu %u %u %u %u"
> - "%lu %lu %lu %u %lu %u",
> - &major, &minor, dev_name,
> - &rd_ios, &rd_merges_or_rd_sec, &rd_sec_or_wr_ios,
> - &rd_ticks_or_wr_sec, &wr_ios, &wr_merges, &wr_sec,
> - &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks,
> - &dc_ios, &dc_merges, &dc_sec, &dc_ticks,
> - &fl_ios, &fl_ticks);
> -
> - if (i < 7) {
> - continue;
> - }
> -
> - diskstatinfo = g_new0(GuestDiskStatsInfo, 1);
> - diskstatinfo->name = g_strdup(dev_name);
> - diskstatinfo->major = major;
> - diskstatinfo->minor = minor;
> -
> - diskstat = g_new0(GuestDiskStats, 1);
> - if (i == 7) {
> - diskstat->has_read_ios = true;
> - diskstat->read_ios = rd_ios;
> - diskstat->has_read_sectors = true;
> - diskstat->read_sectors = rd_merges_or_rd_sec;
> - diskstat->has_write_ios = true;
> - diskstat->write_ios = rd_sec_or_wr_ios;
> - diskstat->has_write_sectors = true;
> - diskstat->write_sectors = rd_ticks_or_wr_sec;
> - }
> - if (i >= 14) {
> - diskstat->has_read_ios = true;
> - diskstat->read_ios = rd_ios;
> - diskstat->has_read_sectors = true;
> - diskstat->read_sectors = rd_sec_or_wr_ios;
> - diskstat->has_read_merges = true;
> - diskstat->read_merges = rd_merges_or_rd_sec;
> - diskstat->has_read_ticks = true;
> - diskstat->read_ticks = rd_ticks_or_wr_sec;
> - diskstat->has_write_ios = true;
> - diskstat->write_ios = wr_ios;
> - diskstat->has_write_sectors = true;
> - diskstat->write_sectors = wr_sec;
> - diskstat->has_write_merges = true;
> - diskstat->write_merges = wr_merges;
> - diskstat->has_write_ticks = true;
> - diskstat->write_ticks = wr_ticks;
> - diskstat->has_ios_pgr = true;
> - diskstat->ios_pgr = ios_pgr;
> - diskstat->has_total_ticks = true;
> - diskstat->total_ticks = tot_ticks;
> - diskstat->has_weight_ticks = true;
> - diskstat->weight_ticks = rq_ticks;
> - }
> - if (i >= 18) {
> - diskstat->has_discard_ios = true;
> - diskstat->discard_ios = dc_ios;
> - diskstat->has_discard_merges = true;
> - diskstat->discard_merges = dc_merges;
> - diskstat->has_discard_sectors = true;
> - diskstat->discard_sectors = dc_sec;
> - diskstat->has_discard_ticks = true;
> - diskstat->discard_ticks = dc_ticks;
> - }
> - if (i >= 20) {
> - diskstat->has_flush_ios = true;
> - diskstat->flush_ios = fl_ios;
> - diskstat->has_flush_ticks = true;
> - diskstat->flush_ticks = fl_ticks;
> - }
> -
> - diskstatinfo->stats = g_steal_pointer(&diskstat);
> - QAPI_LIST_APPEND(tail, diskstatinfo);
> - diskstatinfo = NULL;
> - }
> - free(line);
> - fclose(fp);
> - return head;
> -#else
> - g_debug("disk stats reporting available only for Linux");
> - return NULL;
> -#endif
> -}
> -
> -GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp)
> -{
> - return guest_get_diskstats(errp);
> -}
> -
> -GuestCpuStatsList *qmp_guest_get_cpustats(Error **errp)
> -{
> - GuestCpuStatsList *head = NULL, **tail = &head;
> - const char *cpustats = "/proc/stat";
> - int clk_tck = sysconf(_SC_CLK_TCK);
> - FILE *fp;
> - size_t n;
> - char *line = NULL;
> -
> - fp = fopen(cpustats, "r");
> - if (fp == NULL) {
> - error_setg_errno(errp, errno, "open(\"%s\")", cpustats);
> - return NULL;
> - }
> -
> - while (getline(&line, &n, fp) != -1) {
> - GuestCpuStats *cpustat = NULL;
> - GuestLinuxCpuStats *linuxcpustat;
> - int i;
> - unsigned long user, system, idle, iowait, irq, softirq,
> steal, guest;
> - unsigned long nice, guest_nice;
> - char name[64];
> -
> - i = sscanf(line, "%s %lu %lu %lu %lu %lu %lu %lu %lu %lu
> %lu",
> - name, &user, &nice, &system, &idle, &iowait,
> &irq, &softirq,
> - &steal, &guest, &guest_nice);
> -
> - /* drop "cpu 1 2 3 ...", get "cpuX 1 2 3 ..." only */
> - if ((i == EOF) || strncmp(name, "cpu", 3) || (name[3] ==
> '\0')) {
> - continue;
> - }
> -
> - if (i < 5) {
> - slog("Parsing cpu stat from %s failed, see \"man
> proc\"", cpustats);
> - break;
> - }
> -
> - cpustat = g_new0(GuestCpuStats, 1);
> - cpustat->type = GUEST_CPU_STATS_TYPE_LINUX;
> -
> - linuxcpustat = &cpustat->u.q_linux;
> - linuxcpustat->cpu = atoi(&name[3]);
> - linuxcpustat->user = user * 1000 / clk_tck;
> - linuxcpustat->nice = nice * 1000 / clk_tck;
> - linuxcpustat->system = system * 1000 / clk_tck;
> - linuxcpustat->idle = idle * 1000 / clk_tck;
> -
> - if (i > 5) {
> - linuxcpustat->has_iowait = true;
> - linuxcpustat->iowait = iowait * 1000 / clk_tck;
> - }
> -
> - if (i > 6) {
> - linuxcpustat->has_irq = true;
> - linuxcpustat->irq = irq * 1000 / clk_tck;
> - linuxcpustat->has_softirq = true;
> - linuxcpustat->softirq = softirq * 1000 / clk_tck;
> - }
> -
> - if (i > 8) {
> - linuxcpustat->has_steal = true;
> - linuxcpustat->steal = steal * 1000 / clk_tck;
> - }
> -
> - if (i > 9) {
> - linuxcpustat->has_guest = true;
> - linuxcpustat->guest = guest * 1000 / clk_tck;
> - }
> -
> - if (i > 10) {
> - linuxcpustat->has_guest = true;
> - linuxcpustat->guest = guest * 1000 / clk_tck;
> - linuxcpustat->has_guestnice = true;
> - linuxcpustat->guestnice = guest_nice * 1000 / clk_tck;
> - }
> -
> - QAPI_LIST_APPEND(tail, cpustat);
> - }
> -
> - free(line);
> - fclose(fp);
> - return head;
> -}
> -
> -#else /* defined(__linux__) */
> +#if !defined(__linux__)
>
> void qmp_guest_suspend_disk(Error **errp)
> {
> diff --git a/qga/meson.build b/qga/meson.build
> index 65c1e93846..409f49a000 100644
> --- a/qga/meson.build
> +++ b/qga/meson.build
> @@ -72,6 +72,9 @@ qga_ss.add(when: 'CONFIG_POSIX', if_true: files(
> 'commands-posix.c',
> 'commands-posix-ssh.c',
> ))
> +qga_ss.add(when: 'CONFIG_LINUX', if_true: files(
> + 'commands-linux.c',
> +))
> qga_ss.add(when: 'CONFIG_WIN32', if_true: files(
> 'channel-win32.c',
> 'commands-win32.c',
> --
> 2.34.1
>
>
>
>
> --
> Marc-André Lureau
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 2/5] qga: Add initial FreeBSD support
2022-09-23 7:56 ` Marc-André Lureau
@ 2022-09-23 9:32 ` Alexander Ivanov
0 siblings, 0 replies; 14+ messages in thread
From: Alexander Ivanov @ 2022-09-23 9:32 UTC (permalink / raw)
To: Marc-André Lureau; +Cc: qemu-devel, den, michael.roth, kkostiuk
On 23.09.2022 09:56, Marc-André Lureau wrote:
> Hi
>
> On Thu, Sep 22, 2022 at 4:28 PM Alexander Ivanov
> <alexander.ivanov@virtuozzo.com> wrote:
>
> Add commands-bsd.c file with dumb functions, fix device path and make
> the code buildable in FreeBSD.
>
> Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
> ---
> meson.build | 2 +-
> qga/commands-bsd.c | 121
> +++++++++++++++++++++++++++++++++++++++++++
> qga/commands-posix.c | 6 ++-
> qga/main.c | 11 +++-
> qga/meson.build | 3 ++
> 5 files changed, 140 insertions(+), 3 deletions(-)
> create mode 100644 qga/commands-bsd.c
>
> diff --git a/meson.build b/meson.build
> index c2adb7caf4..574cc1e91e 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -75,7 +75,7 @@ have_tools = get_option('tools') \
> .allowed()
> have_ga = get_option('guest_agent') \
> .disable_auto_if(not have_system and not have_tools) \
> - .require(targetos in ['sunos', 'linux', 'windows'],
> + .require(targetos in ['sunos', 'linux', 'windows', 'freebsd'],
> error_message: 'unsupported OS for QEMU guest agent') \
> .allowed()
> have_block = have_system or have_tools
> diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
> new file mode 100644
> index 0000000000..c1e3ed13e9
> --- /dev/null
> +++ b/qga/commands-bsd.c
> @@ -0,0 +1,121 @@
> +/*
> + * QEMU Guest Agent BSD-specific command implementations
> + *
> + * Copyright (c) Virtuozzo International GmbH.
> + *
> + * Authors:
> + * Alexander Ivanov <alexander.ivanov@virtuozzo.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 "qemu/osdep.h"
> +#include "qga-qapi-commands.h"
> +#include "qapi/qmp/qerror.h"
> +#include "qapi/error.h"
> +#include "qemu/queue.h"
> +#include "commands-common.h"
> +
> +#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
> +bool build_fs_mount_list(FsMountList *mounts, Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return false;
> +}
> +#endif
> +
> +#if defined(CONFIG_FSFREEZE)
> +int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints,
> + strList *mountpoints,
> + FsMountList mounts,
> + Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return 0;
> +}
> +
> +int qmp_guest_fsfreeze_do_thaw(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return 0;
> +}
> +#endif
> +
> +GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +GuestCpuStatsList *qmp_guest_get_cpustats(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +void qmp_guest_suspend_disk(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> +}
> +
> +void qmp_guest_suspend_ram(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> +}
> +
> +void qmp_guest_suspend_hybrid(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> +}
> +
> +GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus,
> Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return -1;
> +}
> +
> +void qmp_guest_set_user_password(const char *username,
> + const char *password,
> + bool crypted,
> + Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> +}
> +
> +GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +GuestMemoryBlockResponseList *
> +qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error
> **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> +
> +GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
> +{
> + error_setg(errp, QERR_UNSUPPORTED);
> + return NULL;
> +}
> diff --git a/qga/commands-posix.c b/qga/commands-posix.c
> index 0bb8b9e2f3..3a1055d5c3 100644
> --- a/qga/commands-posix.c
> +++ b/qga/commands-posix.c
> @@ -31,6 +31,10 @@
> #include <utmpx.h>
> #endif
>
> +#ifdef __FreeBSD__
> +#undef HAVE_GETIFADDRS
> +#endif
>
>
> Why? leave a comment in code please (or in commit message if it's
> solved later in the series)
Now this code break building in FreeBSD. Maybe I'll add a note to the
commit message and add a patch to fix it.
> +
> #ifdef HAVE_GETIFADDRS
> #include <arpa/inet.h>
> #include <sys/socket.h>
> @@ -763,7 +767,7 @@ void qmp_guest_file_flush(int64_t handle,
> Error **errp)
> }
> }
>
> -#if !defined(__linux__)
> +#if !(defined(__linux__) || defined(__FreeBSD__))
>
> void qmp_guest_suspend_disk(Error **errp)
> {
> diff --git a/qga/main.c b/qga/main.c
> index 5f1efa2333..22b3c0df11 100644
> --- a/qga/main.c
> +++ b/qga/main.c
> @@ -45,9 +45,14 @@
> #endif
>
> #ifndef _WIN32
> +#ifdef __FreeBSD__
> +#define QGA_VIRTIO_PATH_DEFAULT "/dev/vtcon/org.qemu.guest_agent.0"
> +#define QGA_SERIAL_PATH_DEFAULT "/dev/vtcon/org.qemu.guest_agent.0"
>
>
> The same path for serial and virtio?
Hmm, I saw such an approach somewhere and it works. Will investigate this.
>
> +#else /* __FreeBSD__ */
> #define QGA_VIRTIO_PATH_DEFAULT
> "/dev/virtio-ports/org.qemu.guest_agent.0"
> -#define QGA_STATE_RELATIVE_DIR "run"
> #define QGA_SERIAL_PATH_DEFAULT "/dev/ttyS0"
> +#endif /* __FreeBSD__ */
> +#define QGA_STATE_RELATIVE_DIR "run"
> #else
> #define QGA_VIRTIO_PATH_DEFAULT
> "\\\\.\\Global\\org.qemu.guest_agent.0"
> #define QGA_STATE_RELATIVE_DIR "qemu-ga"
> @@ -1475,7 +1480,11 @@ int main(int argc, char **argv)
> }
>
> if (config->method == NULL) {
> +#ifdef CONFIG_BSD
> + config->method = g_strdup("isa-serial");
>
>
> Why is isa serial the default ?
I think, an answer "because it works" is not enough? =) Will investigate.
Also I've read all your comments in other emails and will fix my patches
accordingly.
>
> +#else
> config->method = g_strdup("virtio-serial");
> +#endif
> }
>
> socket_activation = check_socket_activation();
> diff --git a/qga/meson.build b/qga/meson.build
> index 409f49a000..456ba4c29f 100644
> --- a/qga/meson.build
> +++ b/qga/meson.build
> @@ -75,6 +75,9 @@ qga_ss.add(when: 'CONFIG_POSIX', if_true: files(
> qga_ss.add(when: 'CONFIG_LINUX', if_true: files(
> 'commands-linux.c',
> ))
> +qga_ss.add(when: 'CONFIG_BSD', if_true: files(
> + 'commands-bsd.c',
> +))
> qga_ss.add(when: 'CONFIG_WIN32', if_true: files(
> 'channel-win32.c',
> 'commands-win32.c',
> --
> 2.34.1
>
>
>
> otherwise, lgtm
>
> --
> Marc-André Lureau
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 5/5] qga: Add support for user password settinng in FreeeBSD
2022-09-23 8:19 ` Marc-André Lureau
@ 2022-09-23 13:43 ` Marc-André Lureau
0 siblings, 0 replies; 14+ messages in thread
From: Marc-André Lureau @ 2022-09-23 13:43 UTC (permalink / raw)
To: Alexander Ivanov; +Cc: qemu-devel, den, michael.roth, kkostiuk
[-- Attachment #1: Type: text/plain, Size: 9531 bytes --]
Hi
On Fri, Sep 23, 2022 at 10:19 AM Marc-André Lureau <
marcandre.lureau@gmail.com> wrote:
> Hi
>
> On Thu, Sep 22, 2022 at 4:25 PM Alexander Ivanov <
> alexander.ivanov@virtuozzo.com> wrote:
>
>> Move qmp_guest_set_user_password() to commands-posix.c under (__linux__ or
>> __FreeBSD) condition. Add command and arguments for password setting in
>> FreeBSD.
>>
>> Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
>>
>
> If you avoid the code move, the patch is much smaller and easier to review
> and lgtm.
>
Also, please fix the typos in the commit message ("settinng" & "FreeeBSD")
>
>> ---
>> qga/commands-bsd.c | 8 ---
>> qga/commands-linux.c | 105 --------------------------------------
>> qga/commands-posix.c | 117 ++++++++++++++++++++++++++++++++++++++++++-
>> 3 files changed, 116 insertions(+), 114 deletions(-)
>>
>> diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
>> index 5d3f46804a..fa3933f2f4 100644
>> --- a/qga/commands-bsd.c
>> +++ b/qga/commands-bsd.c
>> @@ -190,14 +190,6 @@ int64_t
>> qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
>> return -1;
>> }
>>
>> -void qmp_guest_set_user_password(const char *username,
>> - const char *password,
>> - bool crypted,
>> - Error **errp)
>> -{
>> - error_setg(errp, QERR_UNSUPPORTED);
>> -}
>> -
>> GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
>> {
>> error_setg(errp, QERR_UNSUPPORTED);
>> diff --git a/qga/commands-linux.c b/qga/commands-linux.c
>> index 615e9a0027..1f25c80482 100644
>> --- a/qga/commands-linux.c
>> +++ b/qga/commands-linux.c
>> @@ -1629,111 +1629,6 @@ int64_t
>> qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
>> return processed;
>> }
>>
>> -void qmp_guest_set_user_password(const char *username,
>> - const char *password,
>> - bool crypted,
>> - Error **errp)
>> -{
>> - Error *local_err = NULL;
>> - char *passwd_path = NULL;
>> - pid_t pid;
>> - int status;
>> - int datafd[2] = { -1, -1 };
>> - char *rawpasswddata = NULL;
>> - size_t rawpasswdlen;
>> - char *chpasswddata = NULL;
>> - size_t chpasswdlen;
>> -
>> - rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen,
>> errp);
>> - if (!rawpasswddata) {
>> - return;
>> - }
>> - rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
>> - rawpasswddata[rawpasswdlen] = '\0';
>> -
>> - if (strchr(rawpasswddata, '\n')) {
>> - error_setg(errp, "forbidden characters in raw password");
>> - goto out;
>> - }
>> -
>> - if (strchr(username, '\n') ||
>> - strchr(username, ':')) {
>> - error_setg(errp, "forbidden characters in username");
>> - goto out;
>> - }
>> -
>> - chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata);
>> - chpasswdlen = strlen(chpasswddata);
>> -
>> - passwd_path = g_find_program_in_path("chpasswd");
>> -
>> - if (!passwd_path) {
>> - error_setg(errp, "cannot find 'passwd' program in PATH");
>> - goto out;
>> - }
>> -
>> - if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) {
>> - error_setg(errp, "cannot create pipe FDs");
>> - goto out;
>> - }
>> -
>> - pid = fork();
>> - if (pid == 0) {
>> - close(datafd[1]);
>> - /* child */
>> - setsid();
>> - dup2(datafd[0], 0);
>> - reopen_fd_to_null(1);
>> - reopen_fd_to_null(2);
>> -
>> - if (crypted) {
>> - execl(passwd_path, "chpasswd", "-e", NULL);
>> - } else {
>> - execl(passwd_path, "chpasswd", NULL);
>> - }
>> - _exit(EXIT_FAILURE);
>> - } else if (pid < 0) {
>> - error_setg_errno(errp, errno, "failed to create child process");
>> - goto out;
>> - }
>> - close(datafd[0]);
>> - datafd[0] = -1;
>> -
>> - if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) !=
>> chpasswdlen) {
>> - error_setg_errno(errp, errno, "cannot write new account
>> password");
>> - goto out;
>> - }
>> - close(datafd[1]);
>> - datafd[1] = -1;
>> -
>> - ga_wait_child(pid, &status, &local_err);
>> - if (local_err) {
>> - error_propagate(errp, local_err);
>> - goto out;
>> - }
>> -
>> - if (!WIFEXITED(status)) {
>> - error_setg(errp, "child process has terminated abnormally");
>> - goto out;
>> - }
>> -
>> - if (WEXITSTATUS(status)) {
>> - error_setg(errp, "child process has failed to set user
>> password");
>> - goto out;
>> - }
>> -
>> -out:
>> - g_free(chpasswddata);
>> - g_free(rawpasswddata);
>> - g_free(passwd_path);
>> - if (datafd[0] != -1) {
>> - close(datafd[0]);
>> - }
>> - if (datafd[1] != -1) {
>> - close(datafd[1]);
>> - }
>> -}
>> -
>> static void ga_read_sysfs_file(int dirfd, const char *pathname, char
>> *buf,
>> int size, Error **errp)
>> {
>> diff --git a/qga/commands-posix.c b/qga/commands-posix.c
>> index 60cc673f25..e8fc7bd516 100644
>> --- a/qga/commands-posix.c
>> +++ b/qga/commands-posix.c
>> @@ -774,8 +774,123 @@ void qmp_guest_file_flush(int64_t handle, Error
>> **errp)
>> }
>> }
>>
>> -#if !(defined(__linux__) || defined(__FreeBSD__))
>> +#if defined(__linux__) || defined(__FreeBSD__)
>> +void qmp_guest_set_user_password(const char *username,
>> + const char *password,
>> + bool crypted,
>> + Error **errp)
>> +{
>> + Error *local_err = NULL;
>> + char *passwd_path = NULL;
>> + pid_t pid;
>> + int status;
>> + int datafd[2] = { -1, -1 };
>> + char *rawpasswddata = NULL;
>> + size_t rawpasswdlen;
>> + char *chpasswddata = NULL;
>> + size_t chpasswdlen;
>>
>> + rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen,
>> errp);
>> + if (!rawpasswddata) {
>> + return;
>> + }
>> + rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
>> + rawpasswddata[rawpasswdlen] = '\0';
>> +
>> + if (strchr(rawpasswddata, '\n')) {
>> + error_setg(errp, "forbidden characters in raw password");
>> + goto out;
>> + }
>> +
>> + if (strchr(username, '\n') ||
>> + strchr(username, ':')) {
>> + error_setg(errp, "forbidden characters in username");
>> + goto out;
>> + }
>> +
>> +#ifdef __FreeBSD__
>> + chpasswddata = g_strdup(rawpasswddata);
>> + passwd_path = g_find_program_in_path("pw");
>> +#else
>> + chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata);
>> + passwd_path = g_find_program_in_path("chpasswd");
>> +#endif
>> +
>> + chpasswdlen = strlen(chpasswddata);
>> +
>> + if (!passwd_path) {
>> + error_setg(errp, "cannot find 'passwd' program in PATH");
>> + goto out;
>> + }
>> +
>> + if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) {
>> + error_setg(errp, "cannot create pipe FDs");
>> + goto out;
>> + }
>> +
>> + pid = fork();
>> + if (pid == 0) {
>> + close(datafd[1]);
>> + /* child */
>> + setsid();
>> + dup2(datafd[0], 0);
>> + reopen_fd_to_null(1);
>> + reopen_fd_to_null(2);
>> +
>> +#ifdef __FreeBSD__
>> + const char *h_arg;
>> + h_arg = (crypted) ? "-H" : "-h";
>> + execl(passwd_path, "pw", "usermod", "-n", username, h_arg, "0",
>> NULL);
>> +#else
>> + if (crypted) {
>> + execl(passwd_path, "chpasswd", "-e", NULL);
>> + } else {
>> + execl(passwd_path, "chpasswd", NULL);
>> + }
>> +#endif
>> + _exit(EXIT_FAILURE);
>> + } else if (pid < 0) {
>> + error_setg_errno(errp, errno, "failed to create child process");
>> + goto out;
>> + }
>> + close(datafd[0]);
>> + datafd[0] = -1;
>> +
>> + if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) !=
>> chpasswdlen) {
>> + error_setg_errno(errp, errno, "cannot write new account
>> password");
>> + goto out;
>> + }
>> + close(datafd[1]);
>> + datafd[1] = -1;
>> +
>> + ga_wait_child(pid, &status, &local_err);
>> + if (local_err) {
>> + error_propagate(errp, local_err);
>> + goto out;
>> + }
>> +
>> + if (!WIFEXITED(status)) {
>> + error_setg(errp, "child process has terminated abnormally");
>> + goto out;
>> + }
>> +
>> + if (WEXITSTATUS(status)) {
>> + error_setg(errp, "child process has failed to set user
>> password");
>> + goto out;
>> + }
>> +
>> +out:
>> + g_free(chpasswddata);
>> + g_free(rawpasswddata);
>> + g_free(passwd_path);
>> + if (datafd[0] != -1) {
>> + close(datafd[0]);
>> + }
>> + if (datafd[1] != -1) {
>> + close(datafd[1]);
>> + }
>> +}
>> +#else
>> void qmp_guest_suspend_disk(Error **errp)
>> {
>> error_setg(errp, QERR_UNSUPPORTED);
>> --
>> 2.34.1
>>
>>
>>
>
> --
> Marc-André Lureau
>
--
Marc-André Lureau
[-- Attachment #2: Type: text/html, Size: 12095 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2022-09-23 13:45 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2022-09-22 13:19 [PATCH 0/5] qga: Add FreeBSD support Alexander Ivanov
2022-09-22 13:19 ` [PATCH 1/5] qga: Move Linux-specific commands code to separate file Alexander Ivanov
2022-09-23 7:59 ` Marc-André Lureau
2022-09-23 8:51 ` Alexander Ivanov
2022-09-22 13:19 ` [PATCH 2/5] qga: Add initial FreeBSD support Alexander Ivanov
2022-09-23 7:56 ` Marc-André Lureau
2022-09-23 9:32 ` Alexander Ivanov
2022-09-22 13:19 ` [PATCH 3/5] qga: Add UFS freeze/thaw support for FreeBSD Alexander Ivanov
2022-09-23 8:09 ` Marc-André Lureau
2022-09-22 13:20 ` [PATCH 4/5] qga: Add shutdown/halt/reboot " Alexander Ivanov
2022-09-23 8:13 ` Marc-André Lureau
2022-09-22 13:20 ` [PATCH 5/5] qga: Add support for user password settinng in FreeeBSD Alexander Ivanov
2022-09-23 8:19 ` Marc-André Lureau
2022-09-23 13:43 ` Marc-André Lureau
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).