On Thu, Sep 29, 2022 at 12:04 PM Alexander Ivanov < alexander.iva...@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. > > Signed-off-by: Alexander Ivanov <alexander.iva...@virtuozzo.com> > lgtm, Reviewed-by: Marc-André Lureau <marcandre.lur...@redhat.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.iva...@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 > > > -- Marc-André Lureau