Message ID | 20220929075239.1675374-3-alexander.ivanov@virtuozzo.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | qga: Add FreeBSD support | expand |
On Thu, Sep 29, 2022 at 12:16 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 FS freeze/thaw code to a separate file commands-linux.c > keeping common POSIX code in commands-posix.c. > > Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com> > lgtm, Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com> > --- > qga/commands-common.h | 35 +++++ > qga/commands-linux.c | 286 +++++++++++++++++++++++++++++++++++++++++ > qga/commands-posix.c | 289 +++--------------------------------------- > qga/meson.build | 3 + > 4 files changed, 340 insertions(+), 273 deletions(-) > create mode 100644 qga/commands-linux.c > > diff --git a/qga/commands-common.h b/qga/commands-common.h > index d0e4a9696f..181fc330aa 100644 > --- a/qga/commands-common.h > +++ b/qga/commands-common.h > @@ -10,6 +10,40 @@ > #define QGA_COMMANDS_COMMON_H > > #include "qga-qapi-types.h" > +#include "guest-agent-core.h" > +#include "qemu/queue.h" > + > +#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 /* CONFIG_FSFREEZE || CONFIG_FSTRIM */ > + > +#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 /* CONFIG_FSFREEZE */ > > typedef struct GuestFileHandle GuestFileHandle; > > @@ -29,4 +63,5 @@ 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); > #endif > diff --git a/qga/commands-linux.c b/qga/commands-linux.c > new file mode 100644 > index 0000000000..214e408fcd > --- /dev/null > +++ b/qga/commands-linux.c > @@ -0,0 +1,286 @@ > +/* > + * 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 "qapi/error.h" > +#include "commands-common.h" > +#include "cutils.h" > +#include <mntent.h> > +#include <sys/ioctl.h> > + > +#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; > +} > + > +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]; > + } > + } > +} > + > +/* > + * Walk the mount table and build a list of local file systems > + */ > +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 /* CONFIG_FSFREEZE || CONFIG_FSTRIM */ > + > +#ifdef CONFIG_FSFREEZE > +/* > + * 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 */ > diff --git a/qga/commands-posix.c b/qga/commands-posix.c > index 16d67e9f6d..9574b83c92 100644 > --- a/qga/commands-posix.c > +++ b/qga/commands-posix.c > @@ -16,11 +16,9 @@ > #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" > #include "qapi/qmp/qerror.h" > -#include "qemu/queue.h" > #include "qemu/host-utils.h" > #include "qemu/sockets.h" > #include "qemu/base64.h" > @@ -70,7 +68,7 @@ > #endif > #endif > > -static void ga_wait_child(pid_t pid, int *status, Error **errp) > +void ga_wait_child(pid_t pid, int *status, Error **errp) > { > pid_t rpid; > > @@ -629,16 +627,7 @@ void qmp_guest_file_flush(int64_t handle, Error > **errp) > #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) > +void free_fs_mount_list(FsMountList *mounts) > { > FsMount *mount, *temp; > > @@ -653,157 +642,6 @@ static void free_fs_mount_list(FsMountList *mounts) > 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) > @@ -1708,20 +1546,13 @@ 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; > + int ret; > FsMountList mounts; > - struct FsMount *mount; > Error *local_err = NULL; > - int fd; > > slog("guest-fsfreeze called"); > > @@ -1740,122 +1571,34 @@ int64_t qmp_guest_fsfreeze_freeze_list(bool > has_mountpoints, > /* 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); > - } > + 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 (i == 0) { > + if (ret == 0) { > ga_unset_frozen(ga_state); > + } else if (ret < 0) { > + qmp_guest_fsfreeze_thaw(NULL); > } > - return i; > - > -error: > - free_fs_mount_list(&mounts); > - qmp_guest_fsfreeze_thaw(NULL); > - return 0; > + return ret; > } > > -/* > - * 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; > + 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; > } > > - 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; > + return ret; > } > > static void guest_fsfreeze_cleanup(void) > 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 > > >
diff --git a/qga/commands-common.h b/qga/commands-common.h index d0e4a9696f..181fc330aa 100644 --- a/qga/commands-common.h +++ b/qga/commands-common.h @@ -10,6 +10,40 @@ #define QGA_COMMANDS_COMMON_H #include "qga-qapi-types.h" +#include "guest-agent-core.h" +#include "qemu/queue.h" + +#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 /* CONFIG_FSFREEZE || CONFIG_FSTRIM */ + +#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 /* CONFIG_FSFREEZE */ typedef struct GuestFileHandle GuestFileHandle; @@ -29,4 +63,5 @@ 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); #endif diff --git a/qga/commands-linux.c b/qga/commands-linux.c new file mode 100644 index 0000000000..214e408fcd --- /dev/null +++ b/qga/commands-linux.c @@ -0,0 +1,286 @@ +/* + * 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 "qapi/error.h" +#include "commands-common.h" +#include "cutils.h" +#include <mntent.h> +#include <sys/ioctl.h> + +#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; +} + +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]; + } + } +} + +/* + * Walk the mount table and build a list of local file systems + */ +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 /* CONFIG_FSFREEZE || CONFIG_FSTRIM */ + +#ifdef CONFIG_FSFREEZE +/* + * 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 */ diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 16d67e9f6d..9574b83c92 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -16,11 +16,9 @@ #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" #include "qapi/qmp/qerror.h" -#include "qemu/queue.h" #include "qemu/host-utils.h" #include "qemu/sockets.h" #include "qemu/base64.h" @@ -70,7 +68,7 @@ #endif #endif -static void ga_wait_child(pid_t pid, int *status, Error **errp) +void ga_wait_child(pid_t pid, int *status, Error **errp) { pid_t rpid; @@ -629,16 +627,7 @@ void qmp_guest_file_flush(int64_t handle, Error **errp) #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) +void free_fs_mount_list(FsMountList *mounts) { FsMount *mount, *temp; @@ -653,157 +642,6 @@ static void free_fs_mount_list(FsMountList *mounts) 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) @@ -1708,20 +1546,13 @@ 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; + int ret; FsMountList mounts; - struct FsMount *mount; Error *local_err = NULL; - int fd; slog("guest-fsfreeze called"); @@ -1740,122 +1571,34 @@ int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints, /* 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); - } + 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 (i == 0) { + if (ret == 0) { ga_unset_frozen(ga_state); + } else if (ret < 0) { + qmp_guest_fsfreeze_thaw(NULL); } - return i; - -error: - free_fs_mount_list(&mounts); - qmp_guest_fsfreeze_thaw(NULL); - return 0; + return ret; } -/* - * 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; + 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; } - 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; + return ret; } static void guest_fsfreeze_cleanup(void) 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',
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 FS freeze/thaw code to a separate file commands-linux.c keeping common POSIX code in commands-posix.c. Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com> --- qga/commands-common.h | 35 +++++ qga/commands-linux.c | 286 +++++++++++++++++++++++++++++++++++++++++ qga/commands-posix.c | 289 +++--------------------------------------- qga/meson.build | 3 + 4 files changed, 340 insertions(+), 273 deletions(-) create mode 100644 qga/commands-linux.c