]> git.proxmox.com Git - mirror_qemu.git/commitdiff
hw/9pfs: Add new security model mapped-file.
authorAneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
Thu, 19 Jan 2012 06:51:11 +0000 (12:21 +0530)
committerAneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
Thu, 19 Jan 2012 06:51:11 +0000 (12:21 +0530)
This enable us to do passthrough equivalent security model on NFS directory.
NFS server mostly do root squashing and don't support xattr. Hence we cannot
use 'passthrough' or 'mapped' security model

Also added "mapped-xattr" security to indicate earlier "mapped" security model
Older name is still supported.

POSIX rules regarding ctime update on chmod are not followed by this security model.

Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
fsdev/file-op-9p.h
hw/9pfs/cofile.c
hw/9pfs/virtio-9p-device.c
hw/9pfs/virtio-9p-local.c
qemu-options.hx

index 1e96c8bed93c9942b63d98914ab6ee5ca84aa693..956fda091968b34326246209109b4b05697fcf31 100644 (file)
@@ -56,11 +56,15 @@ typedef struct extended_ops {
  * On failure ignore the error.
  */
 #define V9FS_SM_NONE                0x00000010
-#define V9FS_RDONLY                 0x00000020
-#define V9FS_PROXY_SOCK_FD          0x00000040
-#define V9FS_PROXY_SOCK_NAME        0x00000080
+/*
+ * uid/gid part of .virtfs_meatadata namespace
+ */
+#define V9FS_SM_MAPPED_FILE         0x00000020
+#define V9FS_RDONLY                 0x00000040
+#define V9FS_PROXY_SOCK_FD          0x00000080
+#define V9FS_PROXY_SOCK_NAME        0x00000100
 
-#define V9FS_SEC_MASK               0x0000001C
+#define V9FS_SEC_MASK               0x0000003C
 
 
 typedef struct FileOperations FileOperations;
index b15838c1e6a206ca21b3224e7e18b87f997ad0d9..9345aaeb2efbe0b3cd88a5f58f5707c64419245f 100644 (file)
@@ -76,6 +76,20 @@ int v9fs_co_fstat(V9fsPDU *pdu, V9fsFidState *fidp, struct stat *stbuf)
                 err = -errno;
             }
         });
+    /*
+     * Some FS driver (local:mapped-file) can't support fetching attributes
+     * using file descriptor. Use Path name in that case.
+     */
+    if (err == -EOPNOTSUPP) {
+        err = v9fs_co_lstat(pdu, &fidp->path, stbuf);
+        if (err == -ENOENT) {
+            /*
+             * fstat on an unlinked file. Work with partial results
+             * returned from s->ops->fstat
+             */
+            err = 0;
+        }
+    }
     return err;
 }
 
index 642d5e2c46bc63f246adafb2707ce2d26786b567..41a1a820e08ebf6f7e5278eb608f1b0ae16c25a9 100644 (file)
@@ -91,15 +91,6 @@ VirtIODevice *virtio_9p_init(DeviceState *dev, V9fsConf *conf)
         s->ctx.fs_root = NULL;
     }
     s->ctx.exops.get_st_gen = NULL;
-
-    if (fse->export_flags & V9FS_SM_PASSTHROUGH) {
-        s->ctx.xops = passthrough_xattr_ops;
-    } else if (fse->export_flags & V9FS_SM_MAPPED) {
-        s->ctx.xops = mapped_xattr_ops;
-    } else if (fse->export_flags & V9FS_SM_NONE) {
-        s->ctx.xops = none_xattr_ops;
-    }
-
     len = strlen(conf->tag);
     if (len > MAX_TAG_LEN - 1) {
         fprintf(stderr, "mount tag '%s' (%d bytes) is longer than "
index 3ae6ef2e395336cb768c8ea5941e191114f2cd35..6e3f9d1d97d6fd4c26d65bb4073c2a4a26f23438 100644 (file)
@@ -20,6 +20,7 @@
 #include <sys/socket.h>
 #include <sys/un.h>
 #include "qemu-xattr.h"
+#include <libgen.h>
 #include <linux/fs.h>
 #ifdef CONFIG_LINUX_MAGIC_H
 #include <linux/magic.h>
 #define BTRFS_SUPER_MAGIC 0x9123683E
 #endif
 
+#define VIRTFS_META_DIR ".virtfs_metadata"
+
+static const char *local_mapped_attr_path(FsContext *ctx,
+                                          const char *path, char *buffer)
+{
+    char *dir_name;
+    char *tmp_path = strdup(path);
+    char *base_name = basename(tmp_path);
+
+    /* NULL terminate the directory */
+    dir_name = tmp_path;
+    *(base_name - 1) = '\0';
+
+    snprintf(buffer, PATH_MAX, "%s/%s/%s/%s",
+             ctx->fs_root, dir_name, VIRTFS_META_DIR, base_name);
+    free(tmp_path);
+    return buffer;
+}
+
+#define ATTR_MAX 100
+static void local_mapped_file_attr(FsContext *ctx, const char *path,
+                                   struct stat *stbuf)
+{
+    FILE *fp;
+    char buf[ATTR_MAX];
+    char attr_path[PATH_MAX];
+
+    local_mapped_attr_path(ctx, path, attr_path);
+    fp = fopen(attr_path, "r");
+    if (!fp) {
+        return;
+    }
+    memset(buf, 0, ATTR_MAX);
+    while (fgets(buf, ATTR_MAX, fp)) {
+        if (!strncmp(buf, "virtfs.uid", 10)) {
+            stbuf->st_uid = atoi(buf+11);
+        } else if (!strncmp(buf, "virtfs.gid", 10)) {
+            stbuf->st_gid = atoi(buf+11);
+        } else if (!strncmp(buf, "virtfs.mode", 11)) {
+            stbuf->st_mode = atoi(buf+12);
+        } else if (!strncmp(buf, "virtfs.rdev", 11)) {
+            stbuf->st_rdev = atoi(buf+12);
+        }
+        memset(buf, 0, ATTR_MAX);
+    }
+    fclose(fp);
+}
+
 static int local_lstat(FsContext *fs_ctx, V9fsPath *fs_path, struct stat *stbuf)
 {
     int err;
@@ -71,10 +120,103 @@ static int local_lstat(FsContext *fs_ctx, V9fsPath *fs_path, struct stat *stbuf)
                         sizeof(dev_t)) > 0) {
                 stbuf->st_rdev = tmp_dev;
         }
+    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+        local_mapped_file_attr(fs_ctx, path, stbuf);
+    }
+    return err;
+}
+
+static int local_create_mapped_attr_dir(FsContext *ctx, const char *path)
+{
+    int err;
+    char attr_dir[PATH_MAX];
+    char *tmp_path = strdup(path);
+
+    snprintf(attr_dir, PATH_MAX, "%s/%s/%s",
+             ctx->fs_root, dirname(tmp_path), VIRTFS_META_DIR);
+
+    err = mkdir(attr_dir, 0700);
+    if (err < 0 && errno == EEXIST) {
+        err = 0;
     }
+    free(tmp_path);
     return err;
 }
 
+static int local_set_mapped_file_attr(FsContext *ctx,
+                                      const char *path, FsCred *credp)
+{
+    FILE *fp;
+    int ret = 0;
+    char buf[ATTR_MAX];
+    char attr_path[PATH_MAX];
+    int uid = -1, gid = -1, mode = -1, rdev = -1;
+
+    fp = fopen(local_mapped_attr_path(ctx, path, attr_path), "r");
+    if (!fp) {
+        goto create_map_file;
+    }
+    memset(buf, 0, ATTR_MAX);
+    while (fgets(buf, ATTR_MAX, fp)) {
+        if (!strncmp(buf, "virtfs.uid", 10)) {
+            uid = atoi(buf+11);
+        } else if (!strncmp(buf, "virtfs.gid", 10)) {
+            gid = atoi(buf+11);
+        } else if (!strncmp(buf, "virtfs.mode", 11)) {
+            mode = atoi(buf+12);
+        } else if (!strncmp(buf, "virtfs.rdev", 11)) {
+            rdev = atoi(buf+12);
+        }
+        memset(buf, 0, ATTR_MAX);
+    }
+    fclose(fp);
+    goto update_map_file;
+
+create_map_file:
+    ret = local_create_mapped_attr_dir(ctx, path);
+    if (ret < 0) {
+        goto err_out;
+    }
+
+update_map_file:
+    fp = fopen(attr_path, "w");
+    if (!fp) {
+        ret = -1;
+        goto err_out;
+    }
+
+    if (credp->fc_uid != -1) {
+        uid = credp->fc_uid;
+    }
+    if (credp->fc_gid != -1) {
+        gid = credp->fc_gid;
+    }
+    if (credp->fc_mode != -1) {
+        mode = credp->fc_mode;
+    }
+    if (credp->fc_rdev != -1) {
+        rdev = credp->fc_rdev;
+    }
+
+
+    if (uid != -1) {
+        fprintf(fp, "virtfs.uid=%d\n", uid);
+    }
+    if (gid != -1) {
+        fprintf(fp, "virtfs.gid=%d\n", gid);
+    }
+    if (mode != -1) {
+        fprintf(fp, "virtfs.mode=%d\n", mode);
+    }
+    if (rdev != -1) {
+        fprintf(fp, "virtfs.rdev=%d\n", rdev);
+    }
+    fclose(fp);
+
+err_out:
+    return ret;
+}
+
 static int local_set_xattr(const char *path, FsCred *credp)
 {
     int err;
@@ -138,7 +280,8 @@ static ssize_t local_readlink(FsContext *fs_ctx, V9fsPath *fs_path,
     char buffer[PATH_MAX];
     char *path = fs_path->data;
 
-    if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
+    if ((fs_ctx->export_flags & V9FS_SM_MAPPED) ||
+        (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE)) {
         int fd;
         fd = open(rpath(fs_ctx, path, buffer), O_RDONLY);
         if (fd == -1) {
@@ -203,7 +346,18 @@ static int local_readdir_r(FsContext *ctx, V9fsFidOpenState *fs,
                            struct dirent *entry,
                            struct dirent **result)
 {
-    return readdir_r(fs->dir, entry, result);
+    int ret;
+
+again:
+    ret = readdir_r(fs->dir, entry, result);
+    if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+        if (!ret && *result != NULL &&
+            !strcmp(entry->d_name, VIRTFS_META_DIR)) {
+            /* skp the meta data directory */
+            goto again;
+        }
+    }
+    return ret;
 }
 
 static void local_seekdir(FsContext *ctx, V9fsFidOpenState *fs, off_t off)
@@ -264,6 +418,8 @@ static int local_chmod(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
 
     if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
         return local_set_xattr(rpath(fs_ctx, path, buffer), credp);
+    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+        return local_set_mapped_file_attr(fs_ctx, path, credp);
     } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
                (fs_ctx->export_flags & V9FS_SM_NONE)) {
         return chmod(rpath(fs_ctx, path, buffer), credp->fc_mode);
@@ -296,6 +452,18 @@ static int local_mknod(FsContext *fs_ctx, V9fsPath *dir_path,
             serrno = errno;
             goto err_end;
         }
+    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+
+        err = mknod(rpath(fs_ctx, path, buffer),
+                    SM_LOCAL_MODE_BITS|S_IFREG, 0);
+        if (err == -1) {
+            goto out;
+        }
+        err = local_set_mapped_file_attr(fs_ctx, path, credp);
+        if (err == -1) {
+            serrno = errno;
+            goto err_end;
+        }
     } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
                (fs_ctx->export_flags & V9FS_SM_NONE)) {
         err = mknod(rpath(fs_ctx, path, buffer), credp->fc_mode,
@@ -344,6 +512,17 @@ static int local_mkdir(FsContext *fs_ctx, V9fsPath *dir_path,
             serrno = errno;
             goto err_end;
         }
+    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+        err = mkdir(rpath(fs_ctx, path, buffer), SM_LOCAL_DIR_MODE_BITS);
+        if (err == -1) {
+            goto out;
+        }
+        credp->fc_mode = credp->fc_mode|S_IFDIR;
+        err = local_set_mapped_file_attr(fs_ctx, path, credp);
+        if (err == -1) {
+            serrno = errno;
+            goto err_end;
+        }
     } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
                (fs_ctx->export_flags & V9FS_SM_NONE)) {
         err = mkdir(rpath(fs_ctx, path, buffer), credp->fc_mode);
@@ -404,6 +583,9 @@ static int local_fstat(FsContext *fs_ctx, int fid_type,
                       &tmp_dev, sizeof(dev_t)) > 0) {
                 stbuf->st_rdev = tmp_dev;
         }
+    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+        errno = EOPNOTSUPP;
+        return -1;
     }
     return err;
 }
@@ -436,6 +618,19 @@ static int local_open2(FsContext *fs_ctx, V9fsPath *dir_path, const char *name,
             serrno = errno;
             goto err_end;
         }
+    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+        fd = open(rpath(fs_ctx, path, buffer), flags, SM_LOCAL_MODE_BITS);
+        if (fd == -1) {
+            err = fd;
+            goto out;
+        }
+        credp->fc_mode = credp->fc_mode|S_IFREG;
+        /* Set client credentials in .virtfs_metadata directory files */
+        err = local_set_mapped_file_attr(fs_ctx, path, credp);
+        if (err == -1) {
+            serrno = errno;
+            goto err_end;
+        }
     } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
                (fs_ctx->export_flags & V9FS_SM_NONE)) {
         fd = open(rpath(fs_ctx, path, buffer), flags, credp->fc_mode);
@@ -506,6 +701,35 @@ static int local_symlink(FsContext *fs_ctx, const char *oldpath,
             serrno = errno;
             goto err_end;
         }
+    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+        int fd;
+        ssize_t oldpath_size, write_size;
+        fd = open(rpath(fs_ctx, newpath, buffer), O_CREAT|O_EXCL|O_RDWR,
+                  SM_LOCAL_MODE_BITS);
+        if (fd == -1) {
+            err = fd;
+            goto out;
+        }
+        /* Write the oldpath (target) to the file. */
+        oldpath_size = strlen(oldpath);
+        do {
+            write_size = write(fd, (void *)oldpath, oldpath_size);
+        } while (write_size == -1 && errno == EINTR);
+
+        if (write_size != oldpath_size) {
+            serrno = errno;
+            close(fd);
+            err = -1;
+            goto err_end;
+        }
+        close(fd);
+        /* Set cleint credentials in symlink's xattr */
+        credp->fc_mode = credp->fc_mode|S_IFLNK;
+        err = local_set_mapped_file_attr(fs_ctx, newpath, credp);
+        if (err == -1) {
+            serrno = errno;
+            goto err_end;
+        }
     } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
                (fs_ctx->export_flags & V9FS_SM_NONE)) {
         err = symlink(oldpath, rpath(fs_ctx, newpath, buffer));
@@ -548,6 +772,21 @@ static int local_link(FsContext *ctx, V9fsPath *oldpath,
 
     ret = link(rpath(ctx, oldpath->data, buffer),
                rpath(ctx, newpath.data, buffer1));
+
+    /* now link the virtfs_metadata files */
+    if (!ret && (ctx->export_flags & V9FS_SM_MAPPED_FILE)) {
+        /* Link the .virtfs_metadata files. Create the metada directory */
+        ret = local_create_mapped_attr_dir(ctx, newpath.data);
+        if (ret < 0) {
+            goto err_out;
+        }
+        ret = link(local_mapped_attr_path(ctx, oldpath->data, buffer),
+                   local_mapped_attr_path(ctx, newpath.data, buffer1));
+        if (ret < 0 && errno != ENOENT) {
+            goto err_out;
+        }
+    }
+err_out:
     v9fs_string_free(&newpath);
     return ret;
 }
@@ -563,8 +802,21 @@ static int local_truncate(FsContext *ctx, V9fsPath *fs_path, off_t size)
 static int local_rename(FsContext *ctx, const char *oldpath,
                         const char *newpath)
 {
+    int err;
     char buffer[PATH_MAX], buffer1[PATH_MAX];
 
+    if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+        err = local_create_mapped_attr_dir(ctx, newpath);
+        if (err < 0) {
+            return err;
+        }
+        /* rename the .virtfs_metadata files */
+        err = rename(local_mapped_attr_path(ctx, oldpath, buffer),
+                     local_mapped_attr_path(ctx, newpath, buffer1));
+        if (err < 0 && errno != ENOENT) {
+            return err;
+        }
+    }
     return rename(rpath(ctx, oldpath, buffer), rpath(ctx, newpath, buffer1));
 }
 
@@ -580,6 +832,8 @@ static int local_chown(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
                       credp->fc_uid, credp->fc_gid);
     } else if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
         return local_set_xattr(rpath(fs_ctx, path, buffer), credp);
+    } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+        return local_set_mapped_file_attr(fs_ctx, path, credp);
     }
     return -1;
 }
@@ -595,8 +849,46 @@ static int local_utimensat(FsContext *s, V9fsPath *fs_path,
 
 static int local_remove(FsContext *ctx, const char *path)
 {
+    int err;
+    struct stat stbuf;
     char buffer[PATH_MAX];
+
+    if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+        err =  lstat(rpath(ctx, path, buffer), &stbuf);
+        if (err) {
+            goto err_out;
+        }
+        /*
+         * If directory remove .virtfs_metadata contained in the
+         * directory
+         */
+        if (S_ISDIR(stbuf.st_mode)) {
+            sprintf(buffer, "%s/%s/%s", ctx->fs_root, path, VIRTFS_META_DIR);
+            err = remove(buffer);
+            if (err < 0 && errno != ENOENT) {
+                /*
+                 * We didn't had the .virtfs_metadata file. May be file created
+                 * in non-mapped mode ?. Ignore ENOENT.
+                 */
+                goto err_out;
+            }
+        }
+        /*
+         * Now remove the name from parent directory
+         * .virtfs_metadata directory
+         */
+        err = remove(local_mapped_attr_path(ctx, path, buffer));;
+        if (err < 0 && errno != ENOENT) {
+            /*
+             * We didn't had the .virtfs_metadata file. May be file created
+             * in non-mapped mode ?. Ignore ENOENT.
+             */
+            goto err_out;
+        }
+    }
     return remove(rpath(ctx, path, buffer));
+err_out:
+    return err;
 }
 
 static int local_fsync(FsContext *ctx, int fid_type,
@@ -696,12 +988,45 @@ static int local_unlinkat(FsContext *ctx, V9fsPath *dir,
     int ret;
     V9fsString fullname;
     char buffer[PATH_MAX];
+
     v9fs_string_init(&fullname);
 
     v9fs_string_sprintf(&fullname, "%s/%s", dir->data, name);
+    if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+        if (flags == AT_REMOVEDIR) {
+            /*
+             * If directory remove .virtfs_metadata contained in the
+             * directory
+             */
+            sprintf(buffer, "%s/%s/%s", ctx->fs_root,
+                    fullname.data, VIRTFS_META_DIR);
+            ret = remove(buffer);
+            if (ret < 0 && errno != ENOENT) {
+                /*
+                 * We didn't had the .virtfs_metadata file. May be file created
+                 * in non-mapped mode ?. Ignore ENOENT.
+                 */
+                goto err_out;
+            }
+        }
+        /*
+         * Now remove the name from parent directory
+         * .virtfs_metadata directory.
+         */
+        ret = remove(local_mapped_attr_path(ctx, fullname.data, buffer));
+        if (ret < 0 && errno != ENOENT) {
+            /*
+             * We didn't had the .virtfs_metadata file. May be file created
+             * in non-mapped mode ?. Ignore ENOENT.
+             */
+            goto err_out;
+        }
+    }
+    /* Remove the name finally */
     ret = remove(rpath(ctx, fullname.data, buffer));
     v9fs_string_free(&fullname);
 
+err_out:
     return ret;
 }
 
@@ -736,6 +1061,19 @@ static int local_init(FsContext *ctx)
     int err = 0;
     struct statfs stbuf;
 
+    if (ctx->export_flags & V9FS_SM_PASSTHROUGH) {
+        ctx->xops = passthrough_xattr_ops;
+    } else if (ctx->export_flags & V9FS_SM_MAPPED) {
+        ctx->xops = mapped_xattr_ops;
+    } else if (ctx->export_flags & V9FS_SM_NONE) {
+        ctx->xops = none_xattr_ops;
+    } else if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+        /*
+         * xattr operation for mapped-file and passthrough
+         * remain same.
+         */
+        ctx->xops = passthrough_xattr_ops;
+    }
     ctx->export_flags |= V9FS_PATHNAME_FSCONTEXT;
 #ifdef FS_IOC_GETVERSION
     /*
@@ -770,13 +1108,17 @@ static int local_parse_opts(QemuOpts *opts, struct FsDriverEntry *fse)
 
     if (!strcmp(sec_model, "passthrough")) {
         fse->export_flags |= V9FS_SM_PASSTHROUGH;
-    } else if (!strcmp(sec_model, "mapped")) {
+    } else if (!strcmp(sec_model, "mapped") ||
+               !strcmp(sec_model, "mapped-xattr")) {
         fse->export_flags |= V9FS_SM_MAPPED;
     } else if (!strcmp(sec_model, "none")) {
         fse->export_flags |= V9FS_SM_NONE;
+    } else if (!strcmp(sec_model, "mapped-file")) {
+        fse->export_flags |= V9FS_SM_MAPPED_FILE;
     } else {
         fprintf(stderr, "Invalid security model %s specified, valid options are"
-                "\n\t [passthrough|mapped|none]\n", sec_model);
+                "\n\t [passthrough|mapped-xattr|mapped-file|none]\n",
+                sec_model);
         return -1;
     }
 
index 6295cde3517d25d1c645b677083c58c07f12a8e3..0cefd1852fe30d6d27e2b812a834649523c90fe9 100644 (file)
@@ -551,7 +551,7 @@ DEFHEADING()
 DEFHEADING(File system options:)
 
 DEF("fsdev", HAS_ARG, QEMU_OPTION_fsdev,
-    "-fsdev fsdriver,id=id[,path=path,][security_model={mapped|passthrough|none}]\n"
+    "-fsdev fsdriver,id=id[,path=path,][security_model={mapped-xattr|mapped-file|passthrough|none}]\n"
     " [,writeout=immediate][,readonly][,socket=socket|sock_fd=sock_fd]\n",
     QEMU_ARCH_ALL)
 
@@ -571,12 +571,13 @@ Specifies the export path for the file system device. Files under
 this path will be available to the 9p client on the guest.
 @item security_model=@var{security_model}
 Specifies the security model to be used for this export path.
-Supported security models are "passthrough", "mapped" and "none".
+Supported security models are "passthrough", "mapped-xattr", "mapped-file" and "none".
 In "passthrough" security model, files are stored using the same
 credentials as they are created on the guest. This requires qemu
-to run as root. In "mapped" security model, some of the file
+to run as root. In "mapped-xattr" security model, some of the file
 attributes like uid, gid, mode bits and link target are stored as
-file attributes. Directories exported by this security model cannot
+file attributes. For "mapped-file" these attributes are stored in the
+hidden .virtfs_metadata directory. Directories exported by this security model cannot
 interact with other unix tools. "none" security model is same as
 passthrough except the sever won't report failures if it fails to
 set file attributes like ownership. Security model is mandatory
@@ -616,7 +617,7 @@ DEFHEADING()
 DEFHEADING(Virtual File system pass-through options:)
 
 DEF("virtfs", HAS_ARG, QEMU_OPTION_virtfs,
-    "-virtfs local,path=path,mount_tag=tag,security_model=[mapped|passthrough|none]\n"
+    "-virtfs local,path=path,mount_tag=tag,security_model=[mapped-xattr|mapped-file|passthrough|none]\n"
     "        [,writeout=immediate][,readonly][,socket=socket|sock_fd=sock_fd]\n",
     QEMU_ARCH_ALL)
 
@@ -637,12 +638,13 @@ Specifies the export path for the file system device. Files under
 this path will be available to the 9p client on the guest.
 @item security_model=@var{security_model}
 Specifies the security model to be used for this export path.
-Supported security models are "passthrough", "mapped" and "none".
+Supported security models are "passthrough", "mapped-xattr", "mapped-file" and "none".
 In "passthrough" security model, files are stored using the same
 credentials as they are created on the guest. This requires qemu
-to run as root. In "mapped" security model, some of the file
+to run as root. In "mapped-xattr" security model, some of the file
 attributes like uid, gid, mode bits and link target are stored as
-file attributes. Directories exported by this security model cannot
+file attributes. For "mapped-file" these attributes are stored in the
+hidden .virtfs_metadata directory. Directories exported by this security model cannot
 interact with other unix tools. "none" security model is same as
 passthrough except the sever won't report failures if it fails to
 set file attributes like ownership. Security model is mandatory only