qemu/qga/commands-linux.c
<<
>>
Prefs
   1/*
   2 * QEMU Guest Agent Linux-specific command implementations
   3 *
   4 * Copyright IBM Corp. 2011
   5 *
   6 * Authors:
   7 *  Michael Roth      <mdroth@linux.vnet.ibm.com>
   8 *  Michal Privoznik  <mprivozn@redhat.com>
   9 *
  10 * This work is licensed under the terms of the GNU GPL, version 2 or later.
  11 * See the COPYING file in the top-level directory.
  12 */
  13
  14#include "qemu/osdep.h"
  15#include "qapi/error.h"
  16#include "commands-common.h"
  17#include "cutils.h"
  18#include <mntent.h>
  19#include <sys/ioctl.h>
  20
  21#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
  22static int dev_major_minor(const char *devpath,
  23                           unsigned int *devmajor, unsigned int *devminor)
  24{
  25    struct stat st;
  26
  27    *devmajor = 0;
  28    *devminor = 0;
  29
  30    if (stat(devpath, &st) < 0) {
  31        slog("failed to stat device file '%s': %s", devpath, strerror(errno));
  32        return -1;
  33    }
  34    if (S_ISDIR(st.st_mode)) {
  35        /* It is bind mount */
  36        return -2;
  37    }
  38    if (S_ISBLK(st.st_mode)) {
  39        *devmajor = major(st.st_rdev);
  40        *devminor = minor(st.st_rdev);
  41        return 0;
  42    }
  43    return -1;
  44}
  45
  46static bool build_fs_mount_list_from_mtab(FsMountList *mounts, Error **errp)
  47{
  48    struct mntent *ment;
  49    FsMount *mount;
  50    char const *mtab = "/proc/self/mounts";
  51    FILE *fp;
  52    unsigned int devmajor, devminor;
  53
  54    fp = setmntent(mtab, "r");
  55    if (!fp) {
  56        error_setg(errp, "failed to open mtab file: '%s'", mtab);
  57        return false;
  58    }
  59
  60    while ((ment = getmntent(fp))) {
  61        /*
  62         * An entry which device name doesn't start with a '/' is
  63         * either a dummy file system or a network file system.
  64         * Add special handling for smbfs and cifs as is done by
  65         * coreutils as well.
  66         */
  67        if ((ment->mnt_fsname[0] != '/') ||
  68            (strcmp(ment->mnt_type, "smbfs") == 0) ||
  69            (strcmp(ment->mnt_type, "cifs") == 0)) {
  70            continue;
  71        }
  72        if (dev_major_minor(ment->mnt_fsname, &devmajor, &devminor) == -2) {
  73            /* Skip bind mounts */
  74            continue;
  75        }
  76
  77        mount = g_new0(FsMount, 1);
  78        mount->dirname = g_strdup(ment->mnt_dir);
  79        mount->devtype = g_strdup(ment->mnt_type);
  80        mount->devmajor = devmajor;
  81        mount->devminor = devminor;
  82
  83        QTAILQ_INSERT_TAIL(mounts, mount, next);
  84    }
  85
  86    endmntent(fp);
  87    return true;
  88}
  89
  90static void decode_mntname(char *name, int len)
  91{
  92    int i, j = 0;
  93    for (i = 0; i <= len; i++) {
  94        if (name[i] != '\\') {
  95            name[j++] = name[i];
  96        } else if (name[i + 1] == '\\') {
  97            name[j++] = '\\';
  98            i++;
  99        } else if (name[i + 1] >= '0' && name[i + 1] <= '3' &&
 100                   name[i + 2] >= '0' && name[i + 2] <= '7' &&
 101                   name[i + 3] >= '0' && name[i + 3] <= '7') {
 102            name[j++] = (name[i + 1] - '0') * 64 +
 103                        (name[i + 2] - '0') * 8 +
 104                        (name[i + 3] - '0');
 105            i += 3;
 106        } else {
 107            name[j++] = name[i];
 108        }
 109    }
 110}
 111
 112/*
 113 * Walk the mount table and build a list of local file systems
 114 */
 115bool build_fs_mount_list(FsMountList *mounts, Error **errp)
 116{
 117    FsMount *mount;
 118    char const *mountinfo = "/proc/self/mountinfo";
 119    FILE *fp;
 120    char *line = NULL, *dash;
 121    size_t n;
 122    char check;
 123    unsigned int devmajor, devminor;
 124    int ret, dir_s, dir_e, type_s, type_e, dev_s, dev_e;
 125
 126    fp = fopen(mountinfo, "r");
 127    if (!fp) {
 128        return build_fs_mount_list_from_mtab(mounts, errp);
 129    }
 130
 131    while (getline(&line, &n, fp) != -1) {
 132        ret = sscanf(line, "%*u %*u %u:%u %*s %n%*s%n%c",
 133                     &devmajor, &devminor, &dir_s, &dir_e, &check);
 134        if (ret < 3) {
 135            continue;
 136        }
 137        dash = strstr(line + dir_e, " - ");
 138        if (!dash) {
 139            continue;
 140        }
 141        ret = sscanf(dash, " - %n%*s%n %n%*s%n%c",
 142                     &type_s, &type_e, &dev_s, &dev_e, &check);
 143        if (ret < 1) {
 144            continue;
 145        }
 146        line[dir_e] = 0;
 147        dash[type_e] = 0;
 148        dash[dev_e] = 0;
 149        decode_mntname(line + dir_s, dir_e - dir_s);
 150        decode_mntname(dash + dev_s, dev_e - dev_s);
 151        if (devmajor == 0) {
 152            /* btrfs reports major number = 0 */
 153            if (strcmp("btrfs", dash + type_s) != 0 ||
 154                dev_major_minor(dash + dev_s, &devmajor, &devminor) < 0) {
 155                continue;
 156            }
 157        }
 158
 159        mount = g_new0(FsMount, 1);
 160        mount->dirname = g_strdup(line + dir_s);
 161        mount->devtype = g_strdup(dash + type_s);
 162        mount->devmajor = devmajor;
 163        mount->devminor = devminor;
 164
 165        QTAILQ_INSERT_TAIL(mounts, mount, next);
 166    }
 167    free(line);
 168
 169    fclose(fp);
 170    return true;
 171}
 172#endif /* CONFIG_FSFREEZE || CONFIG_FSTRIM */
 173
 174#ifdef CONFIG_FSFREEZE
 175/*
 176 * Walk list of mounted file systems in the guest, and freeze the ones which
 177 * are real local file systems.
 178 */
 179int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints,
 180                                          strList *mountpoints,
 181                                          FsMountList mounts,
 182                                          Error **errp)
 183{
 184    struct FsMount *mount;
 185    strList *list;
 186    int fd, ret, i = 0;
 187
 188    QTAILQ_FOREACH_REVERSE(mount, &mounts, next) {
 189        /* To issue fsfreeze in the reverse order of mounts, check if the
 190         * mount is listed in the list here */
 191        if (has_mountpoints) {
 192            for (list = mountpoints; list; list = list->next) {
 193                if (strcmp(list->value, mount->dirname) == 0) {
 194                    break;
 195                }
 196            }
 197            if (!list) {
 198                continue;
 199            }
 200        }
 201
 202        fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
 203        if (fd == -1) {
 204            error_setg_errno(errp, errno, "failed to open %s", mount->dirname);
 205            return -1;
 206        }
 207
 208        /* we try to cull filesystems we know won't work in advance, but other
 209         * filesystems may not implement fsfreeze for less obvious reasons.
 210         * these will report EOPNOTSUPP. we simply ignore these when tallying
 211         * the number of frozen filesystems.
 212         * if a filesystem is mounted more than once (aka bind mount) a
 213         * consecutive attempt to freeze an already frozen filesystem will
 214         * return EBUSY.
 215         *
 216         * any other error means a failure to freeze a filesystem we
 217         * expect to be freezable, so return an error in those cases
 218         * and return system to thawed state.
 219         */
 220        ret = ioctl(fd, FIFREEZE);
 221        if (ret == -1) {
 222            if (errno != EOPNOTSUPP && errno != EBUSY) {
 223                error_setg_errno(errp, errno, "failed to freeze %s",
 224                                 mount->dirname);
 225                close(fd);
 226                return -1;
 227            }
 228        } else {
 229            i++;
 230        }
 231        close(fd);
 232    }
 233    return i;
 234}
 235
 236int qmp_guest_fsfreeze_do_thaw(Error **errp)
 237{
 238    int ret;
 239    FsMountList mounts;
 240    FsMount *mount;
 241    int fd, i = 0, logged;
 242    Error *local_err = NULL;
 243
 244    QTAILQ_INIT(&mounts);
 245    if (!build_fs_mount_list(&mounts, &local_err)) {
 246        error_propagate(errp, local_err);
 247        return -1;
 248    }
 249
 250    QTAILQ_FOREACH(mount, &mounts, next) {
 251        logged = false;
 252        fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
 253        if (fd == -1) {
 254            continue;
 255        }
 256        /* we have no way of knowing whether a filesystem was actually unfrozen
 257         * as a result of a successful call to FITHAW, only that if an error
 258         * was returned the filesystem was *not* unfrozen by that particular
 259         * call.
 260         *
 261         * since multiple preceding FIFREEZEs require multiple calls to FITHAW
 262         * to unfreeze, continuing issuing FITHAW until an error is returned,
 263         * in which case either the filesystem is in an unfreezable state, or,
 264         * more likely, it was thawed previously (and remains so afterward).
 265         *
 266         * also, since the most recent successful call is the one that did
 267         * the actual unfreeze, we can use this to provide an accurate count
 268         * of the number of filesystems unfrozen by guest-fsfreeze-thaw, which
 269         * may * be useful for determining whether a filesystem was unfrozen
 270         * during the freeze/thaw phase by a process other than qemu-ga.
 271         */
 272        do {
 273            ret = ioctl(fd, FITHAW);
 274            if (ret == 0 && !logged) {
 275                i++;
 276                logged = true;
 277            }
 278        } while (ret == 0);
 279        close(fd);
 280    }
 281
 282    free_fs_mount_list(&mounts);
 283
 284    return i;
 285}
 286#endif /* CONFIG_FSFREEZE */
 287