diff mbox series

[v3,3/7] qga: Add UFS freeze/thaw support for FreeBSD

Message ID 20221003093911.1844031-4-alexander.ivanov@virtuozzo.com
State New
Headers show
Series qga: Add FreeBSD support | expand

Commit Message

Alexander Ivanov Oct. 3, 2022, 9:39 a.m. UTC
UFS supports FS freezing through ioctl UFSSUSPEND on /dev/ufssuspend.
Frozen FS can be thawed by closing /dev/ufssuspend file descriptior.

Use getmntinfo to get a list of mounted FS.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
---
 qga/commands-bsd.c    | 169 +++++++++++++++++++++++
 qga/commands-common.h |  11 ++
 qga/commands-posix.c  | 308 ++++++++++++++++++++----------------------
 qga/main.c            |   7 +-
 qga/meson.build       |   3 +
 5 files changed, 334 insertions(+), 164 deletions(-)
 create mode 100644 qga/commands-bsd.c

Comments

Konstantin Kostiuk Oct. 3, 2022, 1:42 p.m. UTC | #1
Reviewed-by: Konstantin Kostiuk <kkostiuk@redhat.com>

On Mon, Oct 3, 2022 at 12:39 PM Alexander Ivanov <
alexander.ivanov@virtuozzo.com> wrote:

> UFS supports FS freezing through ioctl UFSSUSPEND on /dev/ufssuspend.
> Frozen FS can be thawed by closing /dev/ufssuspend file descriptior.
>
> Use getmntinfo to get a list of mounted FS.
>
> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
> ---
>  qga/commands-bsd.c    | 169 +++++++++++++++++++++++
>  qga/commands-common.h |  11 ++
>  qga/commands-posix.c  | 308 ++++++++++++++++++++----------------------
>  qga/main.c            |   7 +-
>  qga/meson.build       |   3 +
>  5 files changed, 334 insertions(+), 164 deletions(-)
>  create mode 100644 qga/commands-bsd.c
>
> diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
> new file mode 100644
> index 0000000000..ca06692179
> --- /dev/null
> +++ b/qga/commands-bsd.c
> @@ -0,0 +1,169 @@
> +/*
> + * 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"
> +#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)
> +{
> +    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) {
> +            error_setg_errno(errp, errno, "stat failed on %s",
> +                             mntp->f_mntonname);
> +            return false;
> +        }
> +
> +        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 /* CONFIG_FSFREEZE || CONFIG_FSTRIM */
> +
> +#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)
> +{
> +    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 (g_str_equal(list->value, mount->dirname)) {
> +                    break;
> +                }
> +            }
> +            if (!list) {
> +                continue;
> +            }
> +        }
> +
> +        /* Only UFS supports suspend */
> +        if (!g_str_equal(mount->devtype, "ufs")) {
> +            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;
> +
> +}
> +
> +/*
> + * We don't need to call UFSRESUME ioctl because all the frozen FS
> + * are thawed on /dev/ufssuspend closing.
> + */
> +int qmp_guest_fsfreeze_do_thaw(Error **errp)
> +{
> +    int ret = ufssuspend_cnt;
> +    ufssuspend_cnt = 0;
> +    if (ufssuspend_fd != -1) {
> +        close(ufssuspend_fd);
> +        ufssuspend_fd = -1;
> +    }
> +    return ret;
> +}
> +
> +GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
> +{
> +    error_setg(errp, QERR_UNSUPPORTED);
> +    return NULL;
> +}
> +
> +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;
> +}
> +#endif /* CONFIG_FSFREEZE */
> diff --git a/qga/commands-common.h b/qga/commands-common.h
> index 181fc330aa..2d9878a634 100644
> --- a/qga/commands-common.h
> +++ b/qga/commands-common.h
> @@ -23,11 +23,22 @@
>  #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/commands-posix.c b/qga/commands-posix.c
> index 9574b83c92..49f9996a9c 100644
> --- a/qga/commands-posix.c
> +++ b/qga/commands-posix.c
> @@ -33,20 +33,12 @@
>
>  #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 __FreeBSD__
> @@ -623,9 +615,6 @@ 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)
>  void free_fs_mount_list(FsMountList *mounts)
>  {
> @@ -644,6 +633,156 @@ void free_fs_mount_list(FsMountList *mounts)
>  }
>  #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;
> +    }
> +}
> +
> +/*
> + * 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);
> +}
> +
> +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;
> +}
> +
> +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;
> +}
> +
> +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
> +
> +/* linux-specific implementations. avoid this if at all possible. */
> +#if defined(__linux__)
>  #if defined(CONFIG_FSFREEZE)
>
>  static char *get_pci_driver(char const *syspath, int pathlen, Error
> **errp)
> @@ -1467,153 +1606,6 @@ GuestFilesystemInfoList
> *qmp_guest_get_fsinfo(Error **errp)
>      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);
> -}
> -
> -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;
> -}
> -
> -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;
> -}
> -
> -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)
> diff --git a/qga/main.c b/qga/main.c
> index 0d27c97d38..b3580508fa 100644
> --- a/qga/main.c
> +++ b/qga/main.c
> @@ -37,12 +37,7 @@
>  #include "qga/service-win32.h"
>  #include "qga/vss-win32.h"
>  #endif
> -#ifdef __linux__
> -#include <linux/fs.h>
> -#ifdef FIFREEZE
> -#define CONFIG_FSFREEZE
> -#endif
> -#endif
> +#include "commands-common.h"
>
>  #ifndef _WIN32
>  #ifdef __FreeBSD__
> 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
>
>
diff mbox series

Patch

diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
new file mode 100644
index 0000000000..ca06692179
--- /dev/null
+++ b/qga/commands-bsd.c
@@ -0,0 +1,169 @@ 
+/*
+ * 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"
+#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)
+{
+    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) {
+            error_setg_errno(errp, errno, "stat failed on %s",
+                             mntp->f_mntonname);
+            return false;
+        }
+
+        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 /* CONFIG_FSFREEZE || CONFIG_FSTRIM */
+
+#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)
+{
+    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 (g_str_equal(list->value, mount->dirname)) {
+                    break;
+                }
+            }
+            if (!list) {
+                continue;
+            }
+        }
+
+        /* Only UFS supports suspend */
+        if (!g_str_equal(mount->devtype, "ufs")) {
+            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;
+
+}
+
+/*
+ * We don't need to call UFSRESUME ioctl because all the frozen FS
+ * are thawed on /dev/ufssuspend closing.
+ */
+int qmp_guest_fsfreeze_do_thaw(Error **errp)
+{
+    int ret = ufssuspend_cnt;
+    ufssuspend_cnt = 0;
+    if (ufssuspend_fd != -1) {
+        close(ufssuspend_fd);
+        ufssuspend_fd = -1;
+    }
+    return ret;
+}
+
+GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
+{
+    error_setg(errp, QERR_UNSUPPORTED);
+    return NULL;
+}
+
+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;
+}
+#endif /* CONFIG_FSFREEZE */
diff --git a/qga/commands-common.h b/qga/commands-common.h
index 181fc330aa..2d9878a634 100644
--- a/qga/commands-common.h
+++ b/qga/commands-common.h
@@ -23,11 +23,22 @@ 
 #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/commands-posix.c b/qga/commands-posix.c
index 9574b83c92..49f9996a9c 100644
--- a/qga/commands-posix.c
+++ b/qga/commands-posix.c
@@ -33,20 +33,12 @@ 
 
 #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 __FreeBSD__
@@ -623,9 +615,6 @@  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)
 void free_fs_mount_list(FsMountList *mounts)
 {
@@ -644,6 +633,156 @@  void free_fs_mount_list(FsMountList *mounts)
 }
 #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;
+    }
+}
+
+/*
+ * 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);
+}
+
+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;
+}
+
+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;
+}
+
+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
+
+/* linux-specific implementations. avoid this if at all possible. */
+#if defined(__linux__)
 #if defined(CONFIG_FSFREEZE)
 
 static char *get_pci_driver(char const *syspath, int pathlen, Error **errp)
@@ -1467,153 +1606,6 @@  GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
     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);
-}
-
-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;
-}
-
-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;
-}
-
-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)
diff --git a/qga/main.c b/qga/main.c
index 0d27c97d38..b3580508fa 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -37,12 +37,7 @@ 
 #include "qga/service-win32.h"
 #include "qga/vss-win32.h"
 #endif
-#ifdef __linux__
-#include <linux/fs.h>
-#ifdef FIFREEZE
-#define CONFIG_FSFREEZE
-#endif
-#endif
+#include "commands-common.h"
 
 #ifndef _WIN32
 #ifdef __FreeBSD__
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',