]> git.proxmox.com Git - qemu.git/blobdiff - qga/commands-posix.c
qemu-ga: add guest-fstrim command
[qemu.git] / qga / commands-posix.c
index d58730ad80f2d69e9cb630cfb93ef4909eb7e5f6..ce9042123cd5e7b0492189d5d166001943da1480 100644 (file)
 #include <glib.h>
 #include <sys/types.h>
 #include <sys/ioctl.h>
+#include <sys/wait.h>
 #include "qga/guest-agent-core.h"
 #include "qga-qmp-commands.h"
 #include "qerror.h"
 #include "qemu-queue.h"
 #include "host-utils.h"
 
+#ifndef CONFIG_HAS_ENVIRON
+#ifdef __APPLE__
+#include <crt_externs.h>
+#define environ (*_NSGetEnviron())
+#else
+extern char **environ;
+#endif
+#endif
+
 #if defined(__linux__)
 #include <mntent.h>
 #include <linux/fs.h>
 #include <arpa/inet.h>
 #include <sys/socket.h>
 #include <net/if.h>
-#include <sys/wait.h>
 
-#if defined(__linux__) && defined(FIFREEZE)
+#ifdef FIFREEZE
 #define CONFIG_FSFREEZE
 #endif
+#ifdef FITRIM
+#define CONFIG_FSTRIM
+#endif
 #endif
-
-#if defined(__linux__)
-/* TODO: use this in place of all post-fork() fclose(std*) callers */
-static void reopen_fd_to_null(int fd)
-{
-    int nullfd;
-
-    nullfd = open("/dev/null", O_RDWR);
-    if (nullfd < 0) {
-        return;
-    }
-
-    dup2(nullfd, fd);
-
-    if (nullfd != fd) {
-        close(nullfd);
-    }
-}
-#endif /* defined(__linux__) */
 
 void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
 {
-    int ret;
     const char *shutdown_flag;
+    pid_t rpid, pid;
+    int status;
 
     slog("guest-shutdown called, mode: %s", mode);
     if (!has_mode || strcmp(mode, "powerdown") == 0) {
@@ -71,23 +65,30 @@ void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
         return;
     }
 
-    ret = fork();
-    if (ret == 0) {
+    pid = fork();
+    if (pid == 0) {
         /* child, start the shutdown */
         setsid();
-        fclose(stdin);
-        fclose(stdout);
-        fclose(stderr);
-
-        ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
-                    "hypervisor initiated shutdown", (char*)NULL);
-        if (ret) {
-            slog("guest-shutdown failed: %s", strerror(errno));
-        }
-        exit(!!ret);
-    } else if (ret < 0) {
-        error_set(err, QERR_UNDEFINED_ERROR);
+        reopen_fd_to_null(0);
+        reopen_fd_to_null(1);
+        reopen_fd_to_null(2);
+
+        execle("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
+               "hypervisor initiated shutdown", (char*)NULL, environ);
+        _exit(EXIT_FAILURE);
+    } else if (pid < 0) {
+        goto exit_err;
     }
+
+    do {
+        rpid = waitpid(pid, &status, 0);
+    } while (rpid == -1 && errno == EINTR);
+    if (rpid == pid && WIFEXITED(status) && !WEXITSTATUS(status)) {
+        return;
+    }
+
+exit_err:
+    error_set(err, QERR_UNDEFINED_ERROR);
 }
 
 typedef struct GuestFileHandle {
@@ -314,19 +315,18 @@ static void guest_file_init(void)
 /* linux-specific implementations. avoid this if at all possible. */
 #if defined(__linux__)
 
-#if defined(CONFIG_FSFREEZE)
-
-typedef struct GuestFsfreezeMount {
+#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
+typedef struct FsMount {
     char *dirname;
     char *devtype;
-    QTAILQ_ENTRY(GuestFsfreezeMount) next;
-} GuestFsfreezeMount;
+    QTAILQ_ENTRY(FsMount) next;
+} FsMount;
 
-typedef QTAILQ_HEAD(, GuestFsfreezeMount) GuestFsfreezeMountList;
+typedef QTAILQ_HEAD(, FsMount) FsMountList;
 
-static void guest_fsfreeze_free_mount_list(GuestFsfreezeMountList *mounts)
+static void free_fs_mount_list(FsMountList *mounts)
 {
-     GuestFsfreezeMount *mount, *temp;
+     FsMount *mount, *temp;
 
      if (!mounts) {
          return;
@@ -343,11 +343,11 @@ static void guest_fsfreeze_free_mount_list(GuestFsfreezeMountList *mounts)
 /*
  * Walk the mount table and build a list of local file systems
  */
-static int guest_fsfreeze_build_mount_list(GuestFsfreezeMountList *mounts)
+static int build_fs_mount_list(FsMountList *mounts)
 {
     struct mntent *ment;
-    GuestFsfreezeMount *mount;
-    char const *mtab = MOUNTED;
+    FsMount *mount;
+    char const *mtab = "/proc/self/mounts";
     FILE *fp;
 
     fp = setmntent(mtab, "r");
@@ -369,7 +369,7 @@ static int guest_fsfreeze_build_mount_list(GuestFsfreezeMountList *mounts)
             continue;
         }
 
-        mount = g_malloc0(sizeof(GuestFsfreezeMount));
+        mount = g_malloc0(sizeof(FsMount));
         mount->dirname = g_strdup(ment->mnt_dir);
         mount->devtype = g_strdup(ment->mnt_type);
 
@@ -380,6 +380,9 @@ static int guest_fsfreeze_build_mount_list(GuestFsfreezeMountList *mounts)
 
     return 0;
 }
+#endif
+
+#if defined(CONFIG_FSFREEZE)
 
 /*
  * Return status of freeze/thaw
@@ -400,15 +403,15 @@ GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err)
 int64_t qmp_guest_fsfreeze_freeze(Error **err)
 {
     int ret = 0, i = 0;
-    GuestFsfreezeMountList mounts;
-    struct GuestFsfreezeMount *mount;
+    FsMountList mounts;
+    struct FsMount *mount;
     int fd;
     char err_msg[512];
 
     slog("guest-fsfreeze called");
 
     QTAILQ_INIT(&mounts);
-    ret = guest_fsfreeze_build_mount_list(&mounts);
+    ret = build_fs_mount_list(&mounts);
     if (ret < 0) {
         return ret;
     }
@@ -449,11 +452,11 @@ int64_t qmp_guest_fsfreeze_freeze(Error **err)
         close(fd);
     }
 
-    guest_fsfreeze_free_mount_list(&mounts);
+    free_fs_mount_list(&mounts);
     return i;
 
 error:
-    guest_fsfreeze_free_mount_list(&mounts);
+    free_fs_mount_list(&mounts);
     qmp_guest_fsfreeze_thaw(NULL);
     return 0;
 }
@@ -464,12 +467,12 @@ error:
 int64_t qmp_guest_fsfreeze_thaw(Error **err)
 {
     int ret;
-    GuestFsfreezeMountList mounts;
-    GuestFsfreezeMount *mount;
+    FsMountList mounts;
+    FsMount *mount;
     int fd, i = 0, logged;
 
     QTAILQ_INIT(&mounts);
-    ret = guest_fsfreeze_build_mount_list(&mounts);
+    ret = build_fs_mount_list(&mounts);
     if (ret) {
         error_set(err, QERR_QGA_COMMAND_FAILED,
                   "failed to enumerate filesystems");
@@ -487,7 +490,7 @@ int64_t qmp_guest_fsfreeze_thaw(Error **err)
          * was returned the filesystem was *not* unfrozen by that particular
          * call.
          *
-         * since multiple preceeding FIFREEZEs require multiple calls to FITHAW
+         * 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).
@@ -509,7 +512,7 @@ int64_t qmp_guest_fsfreeze_thaw(Error **err)
     }
 
     ga_unset_frozen(ga_state);
-    guest_fsfreeze_free_mount_list(&mounts);
+    free_fs_mount_list(&mounts);
     return i;
 }
 
@@ -527,121 +530,151 @@ static void guest_fsfreeze_cleanup(void)
 }
 #endif /* CONFIG_FSFREEZE */
 
+#if defined(CONFIG_FSTRIM)
+/*
+ * Walk list of mounted file systems in the guest, and trim them.
+ */
+void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err)
+{
+    int ret = 0;
+    FsMountList mounts;
+    struct FsMount *mount;
+    int fd;
+    char err_msg[512];
+    struct fstrim_range r = {
+        .start = 0,
+        .len = -1,
+        .minlen = has_minimum ? minimum : 0,
+    };
+
+    slog("guest-fstrim called");
+
+    QTAILQ_INIT(&mounts);
+    ret = build_fs_mount_list(&mounts);
+    if (ret < 0) {
+        return;
+    }
+
+    QTAILQ_FOREACH(mount, &mounts, next) {
+        fd = qemu_open(mount->dirname, O_RDONLY);
+        if (fd == -1) {
+            sprintf(err_msg, "failed to open %s, %s", mount->dirname,
+                    strerror(errno));
+            error_set(err, QERR_QGA_COMMAND_FAILED, err_msg);
+            goto error;
+        }
+
+        /* We try to cull filesytems we know won't work in advance, but other
+         * filesytems may not implement fstrim for less obvious reasons.  These
+         * will report EOPNOTSUPP; we simply ignore these errors.  Any other
+         * error means an unexpected error, so return it in those cases.  In
+         * some other cases ENOTTY will be reported (e.g. CD-ROMs).
+         */
+        ret = ioctl(fd, FITRIM, &r);
+        if (ret == -1) {
+            if (errno != ENOTTY && errno != EOPNOTSUPP) {
+                sprintf(err_msg, "failed to trim %s, %s",
+                        mount->dirname, strerror(errno));
+                error_set(err, QERR_QGA_COMMAND_FAILED, err_msg);
+                close(fd);
+                goto error;
+            }
+        }
+        close(fd);
+    }
+
+error:
+    free_fs_mount_list(&mounts);
+}
+#endif /* CONFIG_FSTRIM */
+
+
 #define LINUX_SYS_STATE_FILE "/sys/power/state"
 #define SUSPEND_SUPPORTED 0
 #define SUSPEND_NOT_SUPPORTED 1
 
-/**
- * This function forks twice and the information about the mode support
- * status is passed to the qemu-ga process via a pipe.
- *
- * This approach allows us to keep the way we reap terminated children
- * in qemu-ga quite simple.
- */
 static void bios_supports_mode(const char *pmutils_bin, const char *pmutils_arg,
                                const char *sysfile_str, Error **err)
 {
-    pid_t pid;
-    ssize_t ret;
     char *pmutils_path;
-    int status, pipefds[2];
-
-    if (pipe(pipefds) < 0) {
-        error_set(err, QERR_UNDEFINED_ERROR);
-        return;
-    }
+    pid_t pid, rpid;
+    int status;
 
     pmutils_path = g_find_program_in_path(pmutils_bin);
 
     pid = fork();
     if (!pid) {
-        struct sigaction act;
-
-        memset(&act, 0, sizeof(act));
-        act.sa_handler = SIG_DFL;
-        sigaction(SIGCHLD, &act, NULL);
+        char buf[32]; /* hopefully big enough */
+        ssize_t ret;
+        int fd;
 
         setsid();
-        close(pipefds[0]);
         reopen_fd_to_null(0);
         reopen_fd_to_null(1);
         reopen_fd_to_null(2);
 
-        pid = fork();
-        if (!pid) {
-            int fd;
-            char buf[32]; /* hopefully big enough */
-
-            if (pmutils_path) {
-                execle(pmutils_path, pmutils_bin, pmutils_arg, NULL, environ);
-            }
-
-            /*
-             * If we get here either pm-utils is not installed or execle() has
-             * failed. Let's try the manual method if the caller wants it.
-             */
-
-            if (!sysfile_str) {
-                _exit(SUSPEND_NOT_SUPPORTED);
-            }
-
-            fd = open(LINUX_SYS_STATE_FILE, O_RDONLY);
-            if (fd < 0) {
-                _exit(SUSPEND_NOT_SUPPORTED);
-            }
+        if (pmutils_path) {
+            execle(pmutils_path, pmutils_bin, pmutils_arg, NULL, environ);
+        }
 
-            ret = read(fd, buf, sizeof(buf)-1);
-            if (ret <= 0) {
-                _exit(SUSPEND_NOT_SUPPORTED);
-            }
-            buf[ret] = '\0';
+        /*
+         * If we get here either pm-utils is not installed or execle() has
+         * failed. Let's try the manual method if the caller wants it.
+         */
 
-            if (strstr(buf, sysfile_str)) {
-                _exit(SUSPEND_SUPPORTED);
-            }
+        if (!sysfile_str) {
+            _exit(SUSPEND_NOT_SUPPORTED);
+        }
 
+        fd = open(LINUX_SYS_STATE_FILE, O_RDONLY);
+        if (fd < 0) {
             _exit(SUSPEND_NOT_SUPPORTED);
         }
 
-        if (pid > 0) {
-            wait(&status);
-        } else {
-            status = SUSPEND_NOT_SUPPORTED;
+        ret = read(fd, buf, sizeof(buf)-1);
+        if (ret <= 0) {
+            _exit(SUSPEND_NOT_SUPPORTED);
         }
+        buf[ret] = '\0';
 
-        ret = write(pipefds[1], &status, sizeof(status));
-        if (ret != sizeof(status)) {
-            _exit(EXIT_FAILURE);
+        if (strstr(buf, sysfile_str)) {
+            _exit(SUSPEND_SUPPORTED);
         }
 
-        _exit(EXIT_SUCCESS);
+        _exit(SUSPEND_NOT_SUPPORTED);
     }
 
-    close(pipefds[1]);
     g_free(pmutils_path);
 
     if (pid < 0) {
-        error_set(err, QERR_UNDEFINED_ERROR);
-        goto out;
-    }
-
-    ret = read(pipefds[0], &status, sizeof(status));
-    if (ret == sizeof(status) && WIFEXITED(status) &&
-        WEXITSTATUS(status) == SUSPEND_SUPPORTED) {
-            goto out;
+        goto undef_err;
+    }
+
+    do {
+        rpid = waitpid(pid, &status, 0);
+    } while (rpid == -1 && errno == EINTR);
+    if (rpid == pid && WIFEXITED(status)) {
+        switch (WEXITSTATUS(status)) {
+        case SUSPEND_SUPPORTED:
+            return;
+        case SUSPEND_NOT_SUPPORTED:
+            error_set(err, QERR_UNSUPPORTED);
+            return;
+        default:
+            goto undef_err;
+        }
     }
 
-    error_set(err, QERR_UNSUPPORTED);
-
-out:
-    close(pipefds[0]);
+undef_err:
+    error_set(err, QERR_UNDEFINED_ERROR);
 }
 
 static void guest_suspend(const char *pmutils_bin, const char *sysfile_str,
                           Error **err)
 {
-    pid_t pid;
     char *pmutils_path;
+    pid_t rpid, pid;
+    int status;
 
     pmutils_path = g_find_program_in_path(pmutils_bin);
 
@@ -683,9 +716,18 @@ static void guest_suspend(const char *pmutils_bin, const char *sysfile_str,
     g_free(pmutils_path);
 
     if (pid < 0) {
-        error_set(err, QERR_UNDEFINED_ERROR);
+        goto exit_err;
+    }
+
+    do {
+        rpid = waitpid(pid, &status, 0);
+    } while (rpid == -1 && errno == EINTR);
+    if (rpid == pid && WIFEXITED(status) && !WEXITSTATUS(status)) {
         return;
     }
+
+exit_err:
+    error_set(err, QERR_UNDEFINED_ERROR);
 }
 
 void qmp_guest_suspend_disk(Error **err)
@@ -789,7 +831,7 @@ GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
             strncpy(ifr.ifr_name,  info->value->name, IF_NAMESIZE);
             if (ioctl(sock, SIOCGIFHWADDR, &ifr) == -1) {
                 snprintf(err_msg, sizeof(err_msg),
-                         "failed to get MAC addres of %s: %s",
+                         "failed to get MAC address of %s: %s",
                          ifa->ifa_name,
                          strerror(errno));
                 error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg);
@@ -940,7 +982,15 @@ int64_t qmp_guest_fsfreeze_thaw(Error **err)
 
     return 0;
 }
+#endif /* CONFIG_FSFREEZE */
 
+#if !defined(CONFIG_FSTRIM)
+void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err)
+{
+    error_set(err, QERR_UNSUPPORTED);
+
+    return;
+}
 #endif
 
 /* register init/cleanup routines for stateful command groups */