#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;
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;
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) {
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)
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);
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,
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);
&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;
}
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);
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));
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;
}
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));
}
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;
}
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,
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;
}
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
/*
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;
}
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)
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
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)
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