diff options
author | 2023-10-10 11:40:56 +0000 | |
---|---|---|
committer | 2023-10-10 11:40:56 +0000 | |
commit | e02cda008591317b1625707ff8e115a4841aa889 (patch) | |
tree | aee302e3cf8b59ec2d32ec481be3d1afddfc8968 /fsdev | |
parent | cc668e6b7e0ffd8c9d130513d12053cf5eda1d3b (diff) |
Introduce Virtio-loopback epsilon release:
Epsilon release introduces a new compatibility layer which make virtio-loopback
design to work with QEMU and rust-vmm vhost-user backend without require any
changes.
Signed-off-by: Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>
Change-Id: I52e57563e08a7d0bdc002f8e928ee61ba0c53dd9
Diffstat (limited to 'fsdev')
-rw-r--r-- | fsdev/9p-iov-marshal.c | 306 | ||||
-rw-r--r-- | fsdev/9p-iov-marshal.h | 18 | ||||
-rw-r--r-- | fsdev/9p-marshal.c | 46 | ||||
-rw-r--r-- | fsdev/9p-marshal.h | 82 | ||||
-rw-r--r-- | fsdev/file-op-9p.h | 162 | ||||
-rw-r--r-- | fsdev/meson.build | 17 | ||||
-rw-r--r-- | fsdev/p9array.h | 160 | ||||
-rw-r--r-- | fsdev/qemu-fsdev-dummy.c | 21 | ||||
-rw-r--r-- | fsdev/qemu-fsdev-opts.c | 106 | ||||
-rw-r--r-- | fsdev/qemu-fsdev-throttle.c | 120 | ||||
-rw-r--r-- | fsdev/qemu-fsdev-throttle.h | 38 | ||||
-rw-r--r-- | fsdev/qemu-fsdev.c | 193 | ||||
-rw-r--r-- | fsdev/qemu-fsdev.h | 22 | ||||
-rw-r--r-- | fsdev/virtfs-proxy-helper.c | 1161 |
14 files changed, 2452 insertions, 0 deletions
diff --git a/fsdev/9p-iov-marshal.c b/fsdev/9p-iov-marshal.c new file mode 100644 index 000000000..a1c9beddd --- /dev/null +++ b/fsdev/9p-iov-marshal.c @@ -0,0 +1,306 @@ +/* + * 9p backend + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include "qemu/osdep.h" +#include <glib/gprintf.h> +#include <utime.h> + +#include "9p-iov-marshal.h" +#include "qemu/bswap.h" + +static ssize_t v9fs_packunpack(void *addr, struct iovec *sg, int sg_count, + size_t offset, size_t size, int pack) +{ + int i = 0; + size_t copied = 0; + size_t req_size = size; + + + for (i = 0; size && i < sg_count; i++) { + size_t len; + if (offset >= sg[i].iov_len) { + /* skip this sg */ + offset -= sg[i].iov_len; + continue; + } else { + len = MIN(sg[i].iov_len - offset, size); + if (pack) { + memcpy(sg[i].iov_base + offset, addr, len); + } else { + memcpy(addr, sg[i].iov_base + offset, len); + } + size -= len; + copied += len; + addr += len; + if (size) { + offset = 0; + continue; + } + } + } + if (copied < req_size) { + /* + * We copied less that requested size. error out + */ + return -ENOBUFS; + } + return copied; +} + +static ssize_t v9fs_unpack(void *dst, struct iovec *out_sg, int out_num, + size_t offset, size_t size) +{ + return v9fs_packunpack(dst, out_sg, out_num, offset, size, 0); +} + +ssize_t v9fs_pack(struct iovec *in_sg, int in_num, size_t offset, + const void *src, size_t size) +{ + return v9fs_packunpack((void *)src, in_sg, in_num, offset, size, 1); +} + +ssize_t v9fs_iov_vunmarshal(struct iovec *out_sg, int out_num, size_t offset, + int bswap, const char *fmt, va_list ap) +{ + int i; + ssize_t copied = 0; + size_t old_offset = offset; + + for (i = 0; fmt[i]; i++) { + switch (fmt[i]) { + case 'b': { + uint8_t *valp = va_arg(ap, uint8_t *); + copied = v9fs_unpack(valp, out_sg, out_num, offset, sizeof(*valp)); + break; + } + case 'w': { + uint16_t val, *valp; + valp = va_arg(ap, uint16_t *); + copied = v9fs_unpack(&val, out_sg, out_num, offset, sizeof(val)); + if (bswap) { + *valp = le16_to_cpu(val); + } else { + *valp = val; + } + break; + } + case 'd': { + uint32_t val, *valp; + valp = va_arg(ap, uint32_t *); + copied = v9fs_unpack(&val, out_sg, out_num, offset, sizeof(val)); + if (bswap) { + *valp = le32_to_cpu(val); + } else { + *valp = val; + } + break; + } + case 'q': { + uint64_t val, *valp; + valp = va_arg(ap, uint64_t *); + copied = v9fs_unpack(&val, out_sg, out_num, offset, sizeof(val)); + if (bswap) { + *valp = le64_to_cpu(val); + } else { + *valp = val; + } + break; + } + case 's': { + V9fsString *str = va_arg(ap, V9fsString *); + copied = v9fs_iov_unmarshal(out_sg, out_num, offset, bswap, + "w", &str->size); + if (copied > 0) { + offset += copied; + str->data = g_malloc(str->size + 1); + copied = v9fs_unpack(str->data, out_sg, out_num, offset, + str->size); + if (copied >= 0) { + str->data[str->size] = 0; + } else { + v9fs_string_free(str); + } + } + break; + } + case 'Q': { + V9fsQID *qidp = va_arg(ap, V9fsQID *); + copied = v9fs_iov_unmarshal(out_sg, out_num, offset, bswap, + "bdq", &qidp->type, &qidp->version, + &qidp->path); + break; + } + case 'S': { + V9fsStat *statp = va_arg(ap, V9fsStat *); + copied = v9fs_iov_unmarshal(out_sg, out_num, offset, bswap, + "wwdQdddqsssssddd", + &statp->size, &statp->type, + &statp->dev, &statp->qid, + &statp->mode, &statp->atime, + &statp->mtime, &statp->length, + &statp->name, &statp->uid, + &statp->gid, &statp->muid, + &statp->extension, + &statp->n_uid, &statp->n_gid, + &statp->n_muid); + break; + } + case 'I': { + V9fsIattr *iattr = va_arg(ap, V9fsIattr *); + copied = v9fs_iov_unmarshal(out_sg, out_num, offset, bswap, + "ddddqqqqq", + &iattr->valid, &iattr->mode, + &iattr->uid, &iattr->gid, + &iattr->size, &iattr->atime_sec, + &iattr->atime_nsec, + &iattr->mtime_sec, + &iattr->mtime_nsec); + break; + } + default: + g_assert_not_reached(); + } + if (copied < 0) { + return copied; + } + offset += copied; + } + + return offset - old_offset; +} + +ssize_t v9fs_iov_unmarshal(struct iovec *out_sg, int out_num, size_t offset, + int bswap, const char *fmt, ...) +{ + ssize_t ret; + va_list ap; + + va_start(ap, fmt); + ret = v9fs_iov_vunmarshal(out_sg, out_num, offset, bswap, fmt, ap); + va_end(ap); + + return ret; +} + +ssize_t v9fs_iov_vmarshal(struct iovec *in_sg, int in_num, size_t offset, + int bswap, const char *fmt, va_list ap) +{ + int i; + ssize_t copied = 0; + size_t old_offset = offset; + + for (i = 0; fmt[i]; i++) { + switch (fmt[i]) { + case 'b': { + uint8_t val = va_arg(ap, int); + copied = v9fs_pack(in_sg, in_num, offset, &val, sizeof(val)); + break; + } + case 'w': { + uint16_t val = va_arg(ap, int); + if (bswap) { + val = cpu_to_le16(val); + } + copied = v9fs_pack(in_sg, in_num, offset, &val, sizeof(val)); + break; + } + case 'd': { + uint32_t val = va_arg(ap, uint32_t); + if (bswap) { + val = cpu_to_le32(val); + } + copied = v9fs_pack(in_sg, in_num, offset, &val, sizeof(val)); + break; + } + case 'q': { + uint64_t val = va_arg(ap, uint64_t); + if (bswap) { + val = cpu_to_le64(val); + } + copied = v9fs_pack(in_sg, in_num, offset, &val, sizeof(val)); + break; + } + case 's': { + V9fsString *str = va_arg(ap, V9fsString *); + copied = v9fs_iov_marshal(in_sg, in_num, offset, bswap, + "w", str->size); + if (copied > 0) { + offset += copied; + copied = v9fs_pack(in_sg, in_num, offset, str->data, str->size); + } + break; + } + case 'Q': { + V9fsQID *qidp = va_arg(ap, V9fsQID *); + copied = v9fs_iov_marshal(in_sg, in_num, offset, bswap, "bdq", + qidp->type, qidp->version, + qidp->path); + break; + } + case 'S': { + V9fsStat *statp = va_arg(ap, V9fsStat *); + copied = v9fs_iov_marshal(in_sg, in_num, offset, bswap, + "wwdQdddqsssssddd", + statp->size, statp->type, statp->dev, + &statp->qid, statp->mode, statp->atime, + statp->mtime, statp->length, + &statp->name, + &statp->uid, &statp->gid, &statp->muid, + &statp->extension, statp->n_uid, + statp->n_gid, statp->n_muid); + break; + } + case 'A': { + V9fsStatDotl *statp = va_arg(ap, V9fsStatDotl *); + copied = v9fs_iov_marshal(in_sg, in_num, offset, bswap, + "qQdddqqqqqqqqqqqqqqq", + statp->st_result_mask, + &statp->qid, statp->st_mode, + statp->st_uid, statp->st_gid, + statp->st_nlink, statp->st_rdev, + statp->st_size, statp->st_blksize, + statp->st_blocks, statp->st_atime_sec, + statp->st_atime_nsec, + statp->st_mtime_sec, + statp->st_mtime_nsec, + statp->st_ctime_sec, + statp->st_ctime_nsec, + statp->st_btime_sec, + statp->st_btime_nsec, statp->st_gen, + statp->st_data_version); + break; + } + default: + g_assert_not_reached(); + } + if (copied < 0) { + return copied; + } + offset += copied; + } + + return offset - old_offset; +} + +ssize_t v9fs_iov_marshal(struct iovec *in_sg, int in_num, size_t offset, + int bswap, const char *fmt, ...) +{ + ssize_t ret; + va_list ap; + + va_start(ap, fmt); + ret = v9fs_iov_vmarshal(in_sg, in_num, offset, bswap, fmt, ap); + va_end(ap); + + return ret; +} diff --git a/fsdev/9p-iov-marshal.h b/fsdev/9p-iov-marshal.h new file mode 100644 index 000000000..1fb501685 --- /dev/null +++ b/fsdev/9p-iov-marshal.h @@ -0,0 +1,18 @@ +#ifndef QEMU_9P_IOV_MARSHAL_H +#define QEMU_9P_IOV_MARSHAL_H + +#include "9p-marshal.h" + + +ssize_t v9fs_pack(struct iovec *in_sg, int in_num, size_t offset, + const void *src, size_t size); +ssize_t v9fs_iov_unmarshal(struct iovec *out_sg, int out_num, size_t offset, + int bswap, const char *fmt, ...); +ssize_t v9fs_iov_marshal(struct iovec *in_sg, int in_num, size_t offset, + int bswap, const char *fmt, ...); + +ssize_t v9fs_iov_vunmarshal(struct iovec *out_sg, int out_num, size_t offset, + int bswap, const char *fmt, va_list ap); +ssize_t v9fs_iov_vmarshal(struct iovec *in_sg, int in_num, size_t offset, + int bswap, const char *fmt, va_list ap); +#endif diff --git a/fsdev/9p-marshal.c b/fsdev/9p-marshal.c new file mode 100644 index 000000000..51881fe22 --- /dev/null +++ b/fsdev/9p-marshal.c @@ -0,0 +1,46 @@ +/* + * 9p backend + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include "qemu/osdep.h" +#include <glib/gprintf.h> +#include <dirent.h> +#include <utime.h> + +#include "9p-marshal.h" + +P9ARRAY_DEFINE_TYPE(V9fsString, v9fs_string_free); + +void v9fs_string_free(V9fsString *str) +{ + g_free(str->data); + str->data = NULL; + str->size = 0; +} + +void GCC_FMT_ATTR(2, 3) +v9fs_string_sprintf(V9fsString *str, const char *fmt, ...) +{ + va_list ap; + + v9fs_string_free(str); + + va_start(ap, fmt); + str->size = g_vasprintf(&str->data, fmt, ap); + va_end(ap); +} + +void v9fs_string_copy(V9fsString *lhs, V9fsString *rhs) +{ + v9fs_string_free(lhs); + v9fs_string_sprintf(lhs, "%s", rhs->data); +} diff --git a/fsdev/9p-marshal.h b/fsdev/9p-marshal.h new file mode 100644 index 000000000..f1abbe151 --- /dev/null +++ b/fsdev/9p-marshal.h @@ -0,0 +1,82 @@ +#ifndef QEMU_9P_MARSHAL_H +#define QEMU_9P_MARSHAL_H + +#include "p9array.h" + +typedef struct V9fsString { + uint16_t size; + char *data; +} V9fsString; +P9ARRAY_DECLARE_TYPE(V9fsString); + +typedef struct V9fsQID { + uint8_t type; + uint32_t version; + uint64_t path; +} V9fsQID; + +typedef struct V9fsStat { + int16_t size; + int16_t type; + int32_t dev; + V9fsQID qid; + int32_t mode; + int32_t atime; + int32_t mtime; + int64_t length; + V9fsString name; + V9fsString uid; + V9fsString gid; + V9fsString muid; + /* 9p2000.u */ + V9fsString extension; + int32_t n_uid; + int32_t n_gid; + int32_t n_muid; +} V9fsStat; + +typedef struct V9fsIattr { + int32_t valid; + int32_t mode; + int32_t uid; + int32_t gid; + int64_t size; + int64_t atime_sec; + int64_t atime_nsec; + int64_t mtime_sec; + int64_t mtime_nsec; +} V9fsIattr; + +typedef struct V9fsStatDotl { + uint64_t st_result_mask; + V9fsQID qid; + uint32_t st_mode; + uint32_t st_uid; + uint32_t st_gid; + uint64_t st_nlink; + uint64_t st_rdev; + uint64_t st_size; + uint64_t st_blksize; + uint64_t st_blocks; + uint64_t st_atime_sec; + uint64_t st_atime_nsec; + uint64_t st_mtime_sec; + uint64_t st_mtime_nsec; + uint64_t st_ctime_sec; + uint64_t st_ctime_nsec; + uint64_t st_btime_sec; + uint64_t st_btime_nsec; + uint64_t st_gen; + uint64_t st_data_version; +} V9fsStatDotl; + +static inline void v9fs_string_init(V9fsString *str) +{ + str->data = NULL; + str->size = 0; +} +void v9fs_string_free(V9fsString *str); +void v9fs_string_sprintf(V9fsString *str, const char *fmt, ...); +void v9fs_string_copy(V9fsString *lhs, V9fsString *rhs); + +#endif diff --git a/fsdev/file-op-9p.h b/fsdev/file-op-9p.h new file mode 100644 index 000000000..8fd89f044 --- /dev/null +++ b/fsdev/file-op-9p.h @@ -0,0 +1,162 @@ +/* + * 9p + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#ifndef FILE_OP_9P_H +#define FILE_OP_9P_H + +#include <dirent.h> +#include <utime.h> +#include <sys/vfs.h> +#include "qemu-fsdev-throttle.h" +#include "p9array.h" + +#define SM_LOCAL_MODE_BITS 0600 +#define SM_LOCAL_DIR_MODE_BITS 0700 + +typedef struct FsCred { + uid_t fc_uid; + gid_t fc_gid; + mode_t fc_mode; + dev_t fc_rdev; +} FsCred; + +typedef struct FsContext FsContext; +typedef struct V9fsPath V9fsPath; + +typedef struct ExtendedOps { + int (*get_st_gen)(FsContext *, V9fsPath *, mode_t, uint64_t *); +} ExtendedOps; + +/* export flags */ +#define V9FS_IMMEDIATE_WRITEOUT 0x00000001 +#define V9FS_PATHNAME_FSCONTEXT 0x00000002 +/* + * uid/gid set on fileserver files + */ +#define V9FS_SM_PASSTHROUGH 0x00000004 +/* + * uid/gid part of xattr + */ +#define V9FS_SM_MAPPED 0x00000008 +/* + * Server will try to set uid/gid. + * On failure ignore the error. + */ +#define V9FS_SM_NONE 0x00000010 +/* + * 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 +/* + * multidevs option (either one of the two applies exclusively) + */ +#define V9FS_REMAP_INODES 0x00000200 +#define V9FS_FORBID_MULTIDEVS 0x00000400 +/* + * Disables certain performance warnings from being logged on host side. + */ +#define V9FS_NO_PERF_WARN 0x00000800 + +#define V9FS_SEC_MASK 0x0000003C + + +typedef struct FileOperations FileOperations; +typedef struct XattrOperations XattrOperations; + +/* + * Structure to store the various fsdev's passed through command line. + */ +typedef struct FsDriverEntry { + char *fsdev_id; + char *path; + int export_flags; + FileOperations *ops; + FsThrottle fst; + mode_t fmode; + mode_t dmode; +} FsDriverEntry; + +struct FsContext { + uid_t uid; + char *fs_root; + int export_flags; + XattrOperations **xops; + ExtendedOps exops; + FsThrottle *fst; + /* fs driver specific data */ + void *private; + mode_t fmode; + mode_t dmode; +}; + +struct V9fsPath { + uint16_t size; + char *data; +}; +P9ARRAY_DECLARE_TYPE(V9fsPath); + +typedef union V9fsFidOpenState V9fsFidOpenState; + +void cred_init(FsCred *); + +struct FileOperations { + int (*parse_opts)(QemuOpts *, FsDriverEntry *, Error **errp); + int (*init)(FsContext *, Error **errp); + void (*cleanup)(FsContext *); + int (*lstat)(FsContext *, V9fsPath *, struct stat *); + ssize_t (*readlink)(FsContext *, V9fsPath *, char *, size_t); + int (*chmod)(FsContext *, V9fsPath *, FsCred *); + int (*chown)(FsContext *, V9fsPath *, FsCred *); + int (*mknod)(FsContext *, V9fsPath *, const char *, FsCred *); + int (*utimensat)(FsContext *, V9fsPath *, const struct timespec *); + int (*remove)(FsContext *, const char *); + int (*symlink)(FsContext *, const char *, V9fsPath *, + const char *, FsCred *); + int (*link)(FsContext *, V9fsPath *, V9fsPath *, const char *); + int (*setuid)(FsContext *, uid_t); + int (*close)(FsContext *, V9fsFidOpenState *); + int (*closedir)(FsContext *, V9fsFidOpenState *); + int (*opendir)(FsContext *, V9fsPath *, V9fsFidOpenState *); + int (*open)(FsContext *, V9fsPath *, int, V9fsFidOpenState *); + int (*open2)(FsContext *, V9fsPath *, const char *, + int, FsCred *, V9fsFidOpenState *); + void (*rewinddir)(FsContext *, V9fsFidOpenState *); + off_t (*telldir)(FsContext *, V9fsFidOpenState *); + struct dirent * (*readdir)(FsContext *, V9fsFidOpenState *); + void (*seekdir)(FsContext *, V9fsFidOpenState *, off_t); + ssize_t (*preadv)(FsContext *, V9fsFidOpenState *, + const struct iovec *, int, off_t); + ssize_t (*pwritev)(FsContext *, V9fsFidOpenState *, + const struct iovec *, int, off_t); + int (*mkdir)(FsContext *, V9fsPath *, const char *, FsCred *); + int (*fstat)(FsContext *, int, V9fsFidOpenState *, struct stat *); + int (*rename)(FsContext *, const char *, const char *); + int (*truncate)(FsContext *, V9fsPath *, off_t); + int (*fsync)(FsContext *, int, V9fsFidOpenState *, int); + int (*statfs)(FsContext *s, V9fsPath *path, struct statfs *stbuf); + ssize_t (*lgetxattr)(FsContext *, V9fsPath *, + const char *, void *, size_t); + ssize_t (*llistxattr)(FsContext *, V9fsPath *, void *, size_t); + int (*lsetxattr)(FsContext *, V9fsPath *, + const char *, void *, size_t, int); + int (*lremovexattr)(FsContext *, V9fsPath *, const char *); + int (*name_to_path)(FsContext *, V9fsPath *, const char *, V9fsPath *); + int (*renameat)(FsContext *ctx, V9fsPath *olddir, const char *old_name, + V9fsPath *newdir, const char *new_name); + int (*unlinkat)(FsContext *ctx, V9fsPath *dir, const char *name, int flags); +}; + +#endif diff --git a/fsdev/meson.build b/fsdev/meson.build new file mode 100644 index 000000000..adf57cc43 --- /dev/null +++ b/fsdev/meson.build @@ -0,0 +1,17 @@ +fsdev_ss = ss.source_set() +fsdev_ss.add(files('qemu-fsdev-opts.c', 'qemu-fsdev-throttle.c')) +fsdev_ss.add(when: 'CONFIG_ALL', if_true: files('qemu-fsdev-dummy.c')) +fsdev_ss.add(when: ['CONFIG_FSDEV_9P'], if_true: files( + '9p-iov-marshal.c', + '9p-marshal.c', + 'qemu-fsdev.c', +), if_false: files('qemu-fsdev-dummy.c')) +softmmu_ss.add_all(when: 'CONFIG_LINUX', if_true: fsdev_ss) + +if have_virtfs_proxy_helper + executable('virtfs-proxy-helper', + files('virtfs-proxy-helper.c', '9p-marshal.c', '9p-iov-marshal.c'), + dependencies: [qemuutil, libattr, libcap_ng], + install: true, + install_dir: get_option('libexecdir')) +endif diff --git a/fsdev/p9array.h b/fsdev/p9array.h new file mode 100644 index 000000000..6aa25327c --- /dev/null +++ b/fsdev/p9array.h @@ -0,0 +1,160 @@ +/* + * P9Array - deep auto free C-array + * + * Copyright (c) 2021 Crudebyte + * + * Authors: + * Christian Schoenebeck <qemu_oss@crudebyte.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef QEMU_P9ARRAY_H +#define QEMU_P9ARRAY_H + +#include "qemu/compiler.h" + +/** + * P9Array provides a mechanism to access arrays in common C-style (e.g. by + * square bracket [] operator) in conjunction with reference variables that + * perform deep auto free of the array when leaving the scope of the auto + * reference variable. That means not only is the array itself automatically + * freed, but also memory dynamically allocated by the individual array + * elements. + * + * Example: + * + * Consider the following user struct @c Foo which shall be used as scalar + * (element) type of an array: + * @code + * typedef struct Foo { + * int i; + * char *s; + * } Foo; + * @endcode + * and assume it has the following function to free memory allocated by @c Foo + * instances: + * @code + * void free_foo(Foo *foo) { + * free(foo->s); + * } + * @endcode + * Add the following to a shared header file: + * @code + * P9ARRAY_DECLARE_TYPE(Foo); + * @endcode + * and the following to a C unit file: + * @code + * P9ARRAY_DEFINE_TYPE(Foo, free_foo); + * @endcode + * Finally the array may then be used like this: + * @code + * void doSomething(size_t n) { + * P9ARRAY_REF(Foo) foos = NULL; + * P9ARRAY_NEW(Foo, foos, n); + * for (size_t i = 0; i < n; ++i) { + * foos[i].i = i; + * foos[i].s = calloc(4096, 1); + * snprintf(foos[i].s, 4096, "foo %d", i); + * if (...) { + * return; // array auto freed here + * } + * } + * // array auto freed here + * } + * @endcode + */ + +/** + * Declares an array type for the passed @a scalar_type. + * + * This is typically used from a shared header file. + * + * @param scalar_type - type of the individual array elements + */ +#define P9ARRAY_DECLARE_TYPE(scalar_type) \ + typedef struct P9Array##scalar_type { \ + size_t len; \ + scalar_type first[]; \ + } P9Array##scalar_type; \ + \ + void p9array_new_##scalar_type(scalar_type **auto_var, size_t len); \ + void p9array_auto_free_##scalar_type(scalar_type **auto_var); \ + +/** + * Defines an array type for the passed @a scalar_type and appropriate + * @a scalar_cleanup_func. + * + * This is typically used from a C unit file. + * + * @param scalar_type - type of the individual array elements + * @param scalar_cleanup_func - appropriate function to free memory dynamically + * allocated by individual array elements before + */ +#define P9ARRAY_DEFINE_TYPE(scalar_type, scalar_cleanup_func) \ + void p9array_new_##scalar_type(scalar_type **auto_var, size_t len) \ + { \ + p9array_auto_free_##scalar_type(auto_var); \ + P9Array##scalar_type *arr = g_malloc0(sizeof(P9Array##scalar_type) + \ + len * sizeof(scalar_type)); \ + arr->len = len; \ + *auto_var = &arr->first[0]; \ + } \ + \ + void p9array_auto_free_##scalar_type(scalar_type **auto_var) \ + { \ + scalar_type *first = (*auto_var); \ + if (!first) { \ + return; \ + } \ + P9Array##scalar_type *arr = (P9Array##scalar_type *) ( \ + ((char *)first) - offsetof(P9Array##scalar_type, first) \ + ); \ + for (size_t i = 0; i < arr->len; ++i) { \ + scalar_cleanup_func(&arr->first[i]); \ + } \ + g_free(arr); \ + } \ + +/** + * Used to declare a reference variable (unique pointer) for an array. After + * leaving the scope of the reference variable, the associated array is + * automatically freed. + * + * @param scalar_type - type of the individual array elements + */ +#define P9ARRAY_REF(scalar_type) \ + __attribute((__cleanup__(p9array_auto_free_##scalar_type))) scalar_type* + +/** + * Allocates a new array of passed @a scalar_type with @a len number of array + * elements and assigns the created array to the reference variable + * @a auto_var. + * + * @param scalar_type - type of the individual array elements + * @param auto_var - destination reference variable + * @param len - amount of array elements to be allocated immediately + */ +#define P9ARRAY_NEW(scalar_type, auto_var, len) \ + QEMU_BUILD_BUG_MSG( \ + !__builtin_types_compatible_p(scalar_type, typeof(*auto_var)), \ + "P9Array scalar type mismatch" \ + ); \ + p9array_new_##scalar_type((&auto_var), len) + +#endif /* QEMU_P9ARRAY_H */ diff --git a/fsdev/qemu-fsdev-dummy.c b/fsdev/qemu-fsdev-dummy.c new file mode 100644 index 000000000..8444a254b --- /dev/null +++ b/fsdev/qemu-fsdev-dummy.c @@ -0,0 +1,21 @@ +/* + * 9p + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Gautham R Shenoy <ego@in.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include "qemu/osdep.h" +#include "qemu-fsdev.h" +#include "qemu/config-file.h" + +int qemu_fsdev_add(QemuOpts *opts, Error **errp) +{ + return 0; +} diff --git a/fsdev/qemu-fsdev-opts.c b/fsdev/qemu-fsdev-opts.c new file mode 100644 index 000000000..07a18c6e4 --- /dev/null +++ b/fsdev/qemu-fsdev-opts.c @@ -0,0 +1,106 @@ +/* + * 9p + * + * 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 "qemu/config-file.h" +#include "qemu/option.h" +#include "qemu/module.h" +#include "qemu/throttle-options.h" + +static QemuOptsList qemu_fsdev_opts = { + .name = "fsdev", + .implied_opt_name = "fsdriver", + .head = QTAILQ_HEAD_INITIALIZER(qemu_fsdev_opts.head), + .desc = { + { + .name = "fsdriver", + .type = QEMU_OPT_STRING, + }, { + .name = "path", + .type = QEMU_OPT_STRING, + }, { + .name = "security_model", + .type = QEMU_OPT_STRING, + }, { + .name = "writeout", + .type = QEMU_OPT_STRING, + }, { + .name = "readonly", + .type = QEMU_OPT_BOOL, + }, { + .name = "multidevs", + .type = QEMU_OPT_STRING, + }, { + .name = "socket", + .type = QEMU_OPT_STRING, + }, { + .name = "sock_fd", + .type = QEMU_OPT_NUMBER, + }, { + .name = "fmode", + .type = QEMU_OPT_NUMBER, + }, { + .name = "dmode", + .type = QEMU_OPT_NUMBER, + }, + + THROTTLE_OPTS, + + { /*End of list */ } + }, +}; + +static QemuOptsList qemu_virtfs_opts = { + .name = "virtfs", + .implied_opt_name = "fsdriver", + .head = QTAILQ_HEAD_INITIALIZER(qemu_virtfs_opts.head), + .desc = { + { + .name = "fsdriver", + .type = QEMU_OPT_STRING, + }, { + .name = "path", + .type = QEMU_OPT_STRING, + }, { + .name = "mount_tag", + .type = QEMU_OPT_STRING, + }, { + .name = "security_model", + .type = QEMU_OPT_STRING, + }, { + .name = "writeout", + .type = QEMU_OPT_STRING, + }, { + .name = "readonly", + .type = QEMU_OPT_BOOL, + }, { + .name = "multidevs", + .type = QEMU_OPT_STRING, + }, { + .name = "socket", + .type = QEMU_OPT_STRING, + }, { + .name = "sock_fd", + .type = QEMU_OPT_NUMBER, + }, { + .name = "fmode", + .type = QEMU_OPT_NUMBER, + }, { + .name = "dmode", + .type = QEMU_OPT_NUMBER, + }, + + { /*End of list */ } + }, +}; + +static void fsdev_register_config(void) +{ + qemu_add_opts(&qemu_fsdev_opts); + qemu_add_opts(&qemu_virtfs_opts); +} +opts_init(fsdev_register_config); diff --git a/fsdev/qemu-fsdev-throttle.c b/fsdev/qemu-fsdev-throttle.c new file mode 100644 index 000000000..5c83a1cc0 --- /dev/null +++ b/fsdev/qemu-fsdev-throttle.c @@ -0,0 +1,120 @@ +/* + * Fsdev Throttle + * + * Copyright (C) 2016 Huawei Technologies Duesseldorf GmbH + * + * Author: Pradeep Jagadeesh <pradeep.jagadeesh@huawei.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. + * + * See the COPYING file in the top-level directory for details. + * + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "qemu-fsdev-throttle.h" +#include "qemu/iov.h" +#include "qemu/main-loop.h" +#include "qemu/option.h" + +static void fsdev_throttle_read_timer_cb(void *opaque) +{ + FsThrottle *fst = opaque; + qemu_co_enter_next(&fst->throttled_reqs[false], NULL); +} + +static void fsdev_throttle_write_timer_cb(void *opaque) +{ + FsThrottle *fst = opaque; + qemu_co_enter_next(&fst->throttled_reqs[true], NULL); +} + +int fsdev_throttle_parse_opts(QemuOpts *opts, FsThrottle *fst, Error **errp) +{ + throttle_config_init(&fst->cfg); + fst->cfg.buckets[THROTTLE_BPS_TOTAL].avg = + qemu_opt_get_number(opts, "throttling.bps-total", 0); + fst->cfg.buckets[THROTTLE_BPS_READ].avg = + qemu_opt_get_number(opts, "throttling.bps-read", 0); + fst->cfg.buckets[THROTTLE_BPS_WRITE].avg = + qemu_opt_get_number(opts, "throttling.bps-write", 0); + fst->cfg.buckets[THROTTLE_OPS_TOTAL].avg = + qemu_opt_get_number(opts, "throttling.iops-total", 0); + fst->cfg.buckets[THROTTLE_OPS_READ].avg = + qemu_opt_get_number(opts, "throttling.iops-read", 0); + fst->cfg.buckets[THROTTLE_OPS_WRITE].avg = + qemu_opt_get_number(opts, "throttling.iops-write", 0); + + fst->cfg.buckets[THROTTLE_BPS_TOTAL].max = + qemu_opt_get_number(opts, "throttling.bps-total-max", 0); + fst->cfg.buckets[THROTTLE_BPS_READ].max = + qemu_opt_get_number(opts, "throttling.bps-read-max", 0); + fst->cfg.buckets[THROTTLE_BPS_WRITE].max = + qemu_opt_get_number(opts, "throttling.bps-write-max", 0); + fst->cfg.buckets[THROTTLE_OPS_TOTAL].max = + qemu_opt_get_number(opts, "throttling.iops-total-max", 0); + fst->cfg.buckets[THROTTLE_OPS_READ].max = + qemu_opt_get_number(opts, "throttling.iops-read-max", 0); + fst->cfg.buckets[THROTTLE_OPS_WRITE].max = + qemu_opt_get_number(opts, "throttling.iops-write-max", 0); + + fst->cfg.buckets[THROTTLE_BPS_TOTAL].burst_length = + qemu_opt_get_number(opts, "throttling.bps-total-max-length", 1); + fst->cfg.buckets[THROTTLE_BPS_READ].burst_length = + qemu_opt_get_number(opts, "throttling.bps-read-max-length", 1); + fst->cfg.buckets[THROTTLE_BPS_WRITE].burst_length = + qemu_opt_get_number(opts, "throttling.bps-write-max-length", 1); + fst->cfg.buckets[THROTTLE_OPS_TOTAL].burst_length = + qemu_opt_get_number(opts, "throttling.iops-total-max-length", 1); + fst->cfg.buckets[THROTTLE_OPS_READ].burst_length = + qemu_opt_get_number(opts, "throttling.iops-read-max-length", 1); + fst->cfg.buckets[THROTTLE_OPS_WRITE].burst_length = + qemu_opt_get_number(opts, "throttling.iops-write-max-length", 1); + fst->cfg.op_size = + qemu_opt_get_number(opts, "throttling.iops-size", 0); + + return throttle_is_valid(&fst->cfg, errp) ? 0 : -1; +} + +void fsdev_throttle_init(FsThrottle *fst) +{ + if (throttle_enabled(&fst->cfg)) { + throttle_init(&fst->ts); + throttle_timers_init(&fst->tt, + qemu_get_aio_context(), + QEMU_CLOCK_REALTIME, + fsdev_throttle_read_timer_cb, + fsdev_throttle_write_timer_cb, + fst); + throttle_config(&fst->ts, QEMU_CLOCK_REALTIME, &fst->cfg); + qemu_co_queue_init(&fst->throttled_reqs[0]); + qemu_co_queue_init(&fst->throttled_reqs[1]); + } +} + +void coroutine_fn fsdev_co_throttle_request(FsThrottle *fst, bool is_write, + struct iovec *iov, int iovcnt) +{ + if (throttle_enabled(&fst->cfg)) { + if (throttle_schedule_timer(&fst->ts, &fst->tt, is_write) || + !qemu_co_queue_empty(&fst->throttled_reqs[is_write])) { + qemu_co_queue_wait(&fst->throttled_reqs[is_write], NULL); + } + + throttle_account(&fst->ts, is_write, iov_size(iov, iovcnt)); + + if (!qemu_co_queue_empty(&fst->throttled_reqs[is_write]) && + !throttle_schedule_timer(&fst->ts, &fst->tt, is_write)) { + qemu_co_queue_next(&fst->throttled_reqs[is_write]); + } + } +} + +void fsdev_throttle_cleanup(FsThrottle *fst) +{ + if (throttle_enabled(&fst->cfg)) { + throttle_timers_destroy(&fst->tt); + } +} diff --git a/fsdev/qemu-fsdev-throttle.h b/fsdev/qemu-fsdev-throttle.h new file mode 100644 index 000000000..a21aecddc --- /dev/null +++ b/fsdev/qemu-fsdev-throttle.h @@ -0,0 +1,38 @@ +/* + * Fsdev Throttle + * + * Copyright (C) 2016 Huawei Technologies Duesseldorf GmbH + * + * Author: Pradeep Jagadeesh <pradeep.jagadeesh@huawei.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. + * + * See the COPYING file in the top-level directory for details. + * + */ + +#ifndef QEMU_FSDEV_THROTTLE_H +#define QEMU_FSDEV_THROTTLE_H + +#include "block/aio.h" +#include "qemu/coroutine.h" +#include "qemu/throttle.h" + +typedef struct FsThrottle { + ThrottleState ts; + ThrottleTimers tt; + ThrottleConfig cfg; + CoQueue throttled_reqs[2]; +} FsThrottle; + +int fsdev_throttle_parse_opts(QemuOpts *, FsThrottle *, Error **); + +void fsdev_throttle_init(FsThrottle *); + +void coroutine_fn fsdev_co_throttle_request(FsThrottle *, bool , + struct iovec *, int); + +void fsdev_throttle_cleanup(FsThrottle *); + +#endif /* QEMU_FSDEV_THROTTLE_H */ diff --git a/fsdev/qemu-fsdev.c b/fsdev/qemu-fsdev.c new file mode 100644 index 000000000..3da64e9f7 --- /dev/null +++ b/fsdev/qemu-fsdev.c @@ -0,0 +1,193 @@ +/* + * 9p + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Gautham R Shenoy <ego@in.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu-fsdev.h" +#include "qemu/queue.h" +#include "qemu/config-file.h" +#include "qemu/error-report.h" +#include "qemu/option.h" + +/* + * A table to store the various file systems and their callback operations. + * ----------------- + * fstype | ops + * ----------------- + * local | local_ops + * . | + * . | + * . | + * . | + * ----------------- + * etc + */ +typedef struct FsDriverTable { + const char *name; + FileOperations *ops; + const char **opts; +} FsDriverTable; + +typedef struct FsDriverListEntry { + FsDriverEntry fse; + QTAILQ_ENTRY(FsDriverListEntry) next; +} FsDriverListEntry; + +static QTAILQ_HEAD(, FsDriverListEntry) fsdriver_entries = + QTAILQ_HEAD_INITIALIZER(fsdriver_entries); + +#define COMMON_FS_DRIVER_OPTIONS "id", "fsdriver", "readonly" + +static FsDriverTable FsDrivers[] = { + { + .name = "local", + .ops = &local_ops, + .opts = (const char * []) { + COMMON_FS_DRIVER_OPTIONS, + "security_model", + "path", + "writeout", + "fmode", + "dmode", + "multidevs", + "throttling.bps-total", + "throttling.bps-read", + "throttling.bps-write", + "throttling.iops-total", + "throttling.iops-read", + "throttling.iops-write", + "throttling.bps-total-max", + "throttling.bps-read-max", + "throttling.bps-write-max", + "throttling.iops-total-max", + "throttling.iops-read-max", + "throttling.iops-write-max", + "throttling.bps-total-max-length", + "throttling.bps-read-max-length", + "throttling.bps-write-max-length", + "throttling.iops-total-max-length", + "throttling.iops-read-max-length", + "throttling.iops-write-max-length", + "throttling.iops-size", + NULL + }, + }, + { + .name = "synth", + .ops = &synth_ops, + .opts = (const char * []) { + COMMON_FS_DRIVER_OPTIONS, + NULL + }, + }, + { + .name = "proxy", + .ops = &proxy_ops, + .opts = (const char * []) { + COMMON_FS_DRIVER_OPTIONS, + "socket", + "sock_fd", + "writeout", + NULL + }, + }, +}; + +static int validate_opt(void *opaque, const char *name, const char *value, + Error **errp) +{ + FsDriverTable *drv = opaque; + const char **opt; + + for (opt = drv->opts; *opt; opt++) { + if (!strcmp(*opt, name)) { + return 0; + } + } + + error_setg(errp, "'%s' is invalid for fsdriver '%s'", name, drv->name); + return -1; +} + +int qemu_fsdev_add(QemuOpts *opts, Error **errp) +{ + int i; + struct FsDriverListEntry *fsle; + const char *fsdev_id = qemu_opts_id(opts); + const char *fsdriver = qemu_opt_get(opts, "fsdriver"); + const char *writeout = qemu_opt_get(opts, "writeout"); + bool ro = qemu_opt_get_bool(opts, "readonly", 0); + + if (!fsdev_id) { + error_setg(errp, "fsdev: No id specified"); + return -1; + } + + if (fsdriver) { + for (i = 0; i < ARRAY_SIZE(FsDrivers); i++) { + if (strcmp(FsDrivers[i].name, fsdriver) == 0) { + break; + } + } + + if (i == ARRAY_SIZE(FsDrivers)) { + error_setg(errp, "fsdev: fsdriver %s not found", fsdriver); + return -1; + } + } else { + error_setg(errp, "fsdev: No fsdriver specified"); + return -1; + } + + if (qemu_opt_foreach(opts, validate_opt, &FsDrivers[i], errp)) { + return -1; + } + + fsle = g_malloc0(sizeof(*fsle)); + fsle->fse.fsdev_id = g_strdup(fsdev_id); + fsle->fse.ops = FsDrivers[i].ops; + if (writeout) { + if (!strcmp(writeout, "immediate")) { + fsle->fse.export_flags |= V9FS_IMMEDIATE_WRITEOUT; + } + } + if (ro) { + fsle->fse.export_flags |= V9FS_RDONLY; + } else { + fsle->fse.export_flags &= ~V9FS_RDONLY; + } + + if (fsle->fse.ops->parse_opts) { + if (fsle->fse.ops->parse_opts(opts, &fsle->fse, errp)) { + g_free(fsle->fse.fsdev_id); + g_free(fsle); + return -1; + } + } + + QTAILQ_INSERT_TAIL(&fsdriver_entries, fsle, next); + return 0; +} + +FsDriverEntry *get_fsdev_fsentry(char *id) +{ + if (id) { + struct FsDriverListEntry *fsle; + + QTAILQ_FOREACH(fsle, &fsdriver_entries, next) { + if (strcmp(fsle->fse.fsdev_id, id) == 0) { + return &fsle->fse; + } + } + } + return NULL; +} diff --git a/fsdev/qemu-fsdev.h b/fsdev/qemu-fsdev.h new file mode 100644 index 000000000..52a539777 --- /dev/null +++ b/fsdev/qemu-fsdev.h @@ -0,0 +1,22 @@ +/* + * 9p + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Gautham R Shenoy <ego@in.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ +#ifndef QEMU_FSDEV_H +#define QEMU_FSDEV_H +#include "file-op-9p.h" + +int qemu_fsdev_add(QemuOpts *opts, Error **errp); +FsDriverEntry *get_fsdev_fsentry(char *id); +extern FileOperations local_ops; +extern FileOperations synth_ops; +extern FileOperations proxy_ops; +#endif diff --git a/fsdev/virtfs-proxy-helper.c b/fsdev/virtfs-proxy-helper.c new file mode 100644 index 000000000..15c0e79b0 --- /dev/null +++ b/fsdev/virtfs-proxy-helper.c @@ -0,0 +1,1161 @@ +/* + * Helper for QEMU Proxy FS Driver + * Copyright IBM, Corp. 2011 + * + * Authors: + * M. Mohan Kumar <mohan@in.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include <sys/resource.h> +#include <getopt.h> +#include <syslog.h> +#include <sys/fsuid.h> +#include <sys/vfs.h> +#include <sys/ioctl.h> +#include <linux/fs.h> +#ifdef CONFIG_LINUX_MAGIC_H +#include <linux/magic.h> +#endif +#include <cap-ng.h> +#include "qemu-common.h" +#include "qemu/sockets.h" +#include "qemu/xattr.h" +#include "9p-iov-marshal.h" +#include "hw/9pfs/9p-proxy.h" +#include "fsdev/9p-iov-marshal.h" + +#define PROGNAME "virtfs-proxy-helper" + +#ifndef XFS_SUPER_MAGIC +#define XFS_SUPER_MAGIC 0x58465342 +#endif +#ifndef EXT2_SUPER_MAGIC +#define EXT2_SUPER_MAGIC 0xEF53 +#endif +#ifndef REISERFS_SUPER_MAGIC +#define REISERFS_SUPER_MAGIC 0x52654973 +#endif +#ifndef BTRFS_SUPER_MAGIC +#define BTRFS_SUPER_MAGIC 0x9123683E +#endif + +static const struct option helper_opts[] = { + {"fd", required_argument, NULL, 'f'}, + {"path", required_argument, NULL, 'p'}, + {"nodaemon", no_argument, NULL, 'n'}, + {"socket", required_argument, NULL, 's'}, + {"uid", required_argument, NULL, 'u'}, + {"gid", required_argument, NULL, 'g'}, + {}, +}; + +static bool is_daemon; +static bool get_version; /* IOC getversion IOCTL supported */ +static char *prog_name; + +static void GCC_FMT_ATTR(2, 3) do_log(int loglevel, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + if (is_daemon) { + vsyslog(LOG_CRIT, format, ap); + } else { + vfprintf(stderr, format, ap); + } + va_end(ap); +} + +static void do_perror(const char *string) +{ + if (is_daemon) { + syslog(LOG_CRIT, "%s:%s", string, strerror(errno)); + } else { + fprintf(stderr, "%s:%s\n", string, strerror(errno)); + } +} + +static int init_capabilities(void) +{ + /* helper needs following capabilities only */ + int cap_list[] = { + CAP_CHOWN, + CAP_DAC_OVERRIDE, + CAP_FOWNER, + CAP_FSETID, + CAP_SETGID, + CAP_MKNOD, + CAP_SETUID, + }; + int i; + + capng_clear(CAPNG_SELECT_BOTH); + for (i = 0; i < ARRAY_SIZE(cap_list); i++) { + if (capng_update(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED, + cap_list[i]) < 0) { + do_perror("capng_update"); + return -1; + } + } + if (capng_apply(CAPNG_SELECT_BOTH) < 0) { + do_perror("capng_apply"); + return -1; + } + + /* Prepare effective set for setugid. */ + for (i = 0; i < ARRAY_SIZE(cap_list); i++) { + if (cap_list[i] == CAP_DAC_OVERRIDE) { + continue; + } + + if (capng_update(CAPNG_DROP, CAPNG_EFFECTIVE, + cap_list[i]) < 0) { + do_perror("capng_update"); + return -1; + } + } + return 0; +} + +static int socket_read(int sockfd, void *buff, ssize_t size) +{ + ssize_t retval, total = 0; + + while (size) { + retval = read(sockfd, buff, size); + if (retval == 0) { + return -EIO; + } + if (retval < 0) { + if (errno == EINTR) { + continue; + } + return -errno; + } + size -= retval; + buff += retval; + total += retval; + } + return total; +} + +static int socket_write(int sockfd, void *buff, ssize_t size) +{ + ssize_t retval, total = 0; + + while (size) { + retval = write(sockfd, buff, size); + if (retval < 0) { + if (errno == EINTR) { + continue; + } + return -errno; + } + size -= retval; + buff += retval; + total += retval; + } + return total; +} + +static int read_request(int sockfd, struct iovec *iovec, ProxyHeader *header) +{ + int retval; + + /* + * read the request header. + */ + iovec->iov_len = 0; + retval = socket_read(sockfd, iovec->iov_base, PROXY_HDR_SZ); + if (retval < 0) { + return retval; + } + iovec->iov_len = PROXY_HDR_SZ; + retval = proxy_unmarshal(iovec, 0, "dd", &header->type, &header->size); + if (retval < 0) { + return retval; + } + /* + * We can't process message.size > PROXY_MAX_IO_SZ. + * Treat it as fatal error + */ + if (header->size > PROXY_MAX_IO_SZ) { + return -ENOBUFS; + } + retval = socket_read(sockfd, iovec->iov_base + PROXY_HDR_SZ, header->size); + if (retval < 0) { + return retval; + } + iovec->iov_len += header->size; + return 0; +} + +static int send_fd(int sockfd, int fd) +{ + struct msghdr msg; + struct iovec iov; + int retval, data; + struct cmsghdr *cmsg; + union MsgControl msg_control; + + iov.iov_base = &data; + iov.iov_len = sizeof(data); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + /* No ancillary data on error */ + if (fd < 0) { + /* fd is really negative errno if the request failed */ + data = fd; + } else { + data = V9FS_FD_VALID; + msg.msg_control = &msg_control; + msg.msg_controllen = sizeof(msg_control); + + cmsg = &msg_control.cmsg; + cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); + } + + do { + retval = sendmsg(sockfd, &msg, 0); + } while (retval < 0 && errno == EINTR); + if (fd >= 0) { + close(fd); + } + if (retval < 0) { + return retval; + } + return 0; +} + +static int send_status(int sockfd, struct iovec *iovec, int status) +{ + ProxyHeader header; + int retval, msg_size; + + if (status < 0) { + header.type = T_ERROR; + } else { + header.type = T_SUCCESS; + } + header.size = sizeof(status); + /* + * marshal the return status. We don't check error. + * because we are sure we have enough space for the status + */ + msg_size = proxy_marshal(iovec, 0, "ddd", header.type, + header.size, status); + if (msg_size < 0) { + return msg_size; + } + retval = socket_write(sockfd, iovec->iov_base, msg_size); + if (retval < 0) { + return retval; + } + return 0; +} + +/* + * from man 7 capabilities, section + * Effect of User ID Changes on Capabilities: + * If the effective user ID is changed from nonzero to 0, then the permitted + * set is copied to the effective set. If the effective user ID is changed + * from 0 to nonzero, then all capabilities are are cleared from the effective + * set. + * + * The setfsuid/setfsgid man pages warn that changing the effective user ID may + * expose the program to unwanted signals, but this is not true anymore: for an + * unprivileged (without CAP_KILL) program to send a signal, the real or + * effective user ID of the sending process must equal the real or saved user + * ID of the target process. Even when dropping privileges, it is enough to + * keep the saved UID to a "privileged" value and virtfs-proxy-helper won't + * be exposed to signals. So just use setresuid/setresgid. + */ +static int setugid(int uid, int gid, int *suid, int *sgid) +{ + int retval; + + *suid = geteuid(); + *sgid = getegid(); + + if (setresgid(-1, gid, *sgid) == -1) { + return -errno; + } + + if (setresuid(-1, uid, *suid) == -1) { + retval = -errno; + goto err_sgid; + } + + if (uid == 0 && gid == 0) { + /* Linux has already copied the permitted set to the effective set. */ + return 0; + } + + /* + * All capabilities have been cleared from the effective set. However + * we still need DAC_OVERRIDE because we don't change supplementary + * group ids, and hence may be subject to DAC rules. init_capabilities + * left the set of capabilities that we want in libcap-ng's state. + */ + if (capng_apply(CAPNG_SELECT_CAPS) < 0) { + retval = -errno; + do_perror("capng_apply"); + goto err_suid; + } + return 0; + +err_suid: + if (setresuid(-1, *suid, *suid) == -1) { + abort(); + } +err_sgid: + if (setresgid(-1, *sgid, *sgid) == -1) { + abort(); + } + return retval; +} + +/* + * This is used to reset the ugid back with the saved values + * There is nothing much we can do checking error values here. + */ +static void resetugid(int suid, int sgid) +{ + if (setresgid(-1, sgid, sgid) == -1) { + abort(); + } + if (setresuid(-1, suid, suid) == -1) { + abort(); + } +} + +/* + * send response in two parts + * 1) ProxyHeader + * 2) Response or error status + * This function should be called with marshaled response + * send_response constructs header part and error part only. + * send response sends {ProxyHeader,Response} if the request was success + * otherwise sends {ProxyHeader,error status} + */ +static int send_response(int sock, struct iovec *iovec, int size) +{ + int retval; + ProxyHeader header; + + /* + * If response size exceeds available iovec->iov_len, + * we return ENOBUFS + */ + if (size > PROXY_MAX_IO_SZ) { + size = -ENOBUFS; + } + + if (size < 0) { + /* + * In case of error we would not have got the error encoded + * already so encode the error here. + */ + header.type = T_ERROR; + header.size = sizeof(size); + proxy_marshal(iovec, PROXY_HDR_SZ, "d", size); + } else { + header.type = T_SUCCESS; + header.size = size; + } + proxy_marshal(iovec, 0, "dd", header.type, header.size); + retval = socket_write(sock, iovec->iov_base, header.size + PROXY_HDR_SZ); + if (retval < 0) { + return retval; + } + return 0; +} + +/* + * gets generation number + * returns -errno on failure and sizeof(generation number) on success + */ +static int do_getversion(struct iovec *iovec, struct iovec *out_iovec) +{ + uint64_t version; + int retval = -ENOTTY; +#ifdef FS_IOC_GETVERSION + int fd; + V9fsString path; +#endif + + + /* no need to issue ioctl */ + if (!get_version) { + version = 0; + retval = proxy_marshal(out_iovec, PROXY_HDR_SZ, "q", version); + return retval; + } +#ifdef FS_IOC_GETVERSION + retval = proxy_unmarshal(iovec, PROXY_HDR_SZ, "s", &path); + if (retval < 0) { + return retval; + } + + fd = open(path.data, O_RDONLY); + if (fd < 0) { + retval = -errno; + goto err_out; + } + if (ioctl(fd, FS_IOC_GETVERSION, &version) < 0) { + retval = -errno; + } else { + retval = proxy_marshal(out_iovec, PROXY_HDR_SZ, "q", version); + } + close(fd); +err_out: + v9fs_string_free(&path); +#endif + return retval; +} + +static int do_getxattr(int type, struct iovec *iovec, struct iovec *out_iovec) +{ + int size = 0, offset, retval; + V9fsString path, name, xattr; + + v9fs_string_init(&xattr); + v9fs_string_init(&path); + retval = proxy_unmarshal(iovec, PROXY_HDR_SZ, "ds", &size, &path); + if (retval < 0) { + return retval; + } + offset = PROXY_HDR_SZ + retval; + + if (size) { + xattr.data = g_malloc(size); + xattr.size = size; + } + switch (type) { + case T_LGETXATTR: + v9fs_string_init(&name); + retval = proxy_unmarshal(iovec, offset, "s", &name); + if (retval > 0) { + retval = lgetxattr(path.data, name.data, xattr.data, size); + if (retval < 0) { + retval = -errno; + } else { + xattr.size = retval; + } + } + v9fs_string_free(&name); + break; + case T_LLISTXATTR: + retval = llistxattr(path.data, xattr.data, size); + if (retval < 0) { + retval = -errno; + } else { + xattr.size = retval; + } + break; + } + if (retval < 0) { + goto err_out; + } + + if (!size) { + proxy_marshal(out_iovec, PROXY_HDR_SZ, "d", retval); + retval = sizeof(retval); + } else { + retval = proxy_marshal(out_iovec, PROXY_HDR_SZ, "s", &xattr); + } +err_out: + v9fs_string_free(&xattr); + v9fs_string_free(&path); + return retval; +} + +static void stat_to_prstat(ProxyStat *pr_stat, struct stat *stat) +{ + memset(pr_stat, 0, sizeof(*pr_stat)); + pr_stat->st_dev = stat->st_dev; + pr_stat->st_ino = stat->st_ino; + pr_stat->st_nlink = stat->st_nlink; + pr_stat->st_mode = stat->st_mode; + pr_stat->st_uid = stat->st_uid; + pr_stat->st_gid = stat->st_gid; + pr_stat->st_rdev = stat->st_rdev; + pr_stat->st_size = stat->st_size; + pr_stat->st_blksize = stat->st_blksize; + pr_stat->st_blocks = stat->st_blocks; + pr_stat->st_atim_sec = stat->st_atim.tv_sec; + pr_stat->st_atim_nsec = stat->st_atim.tv_nsec; + pr_stat->st_mtim_sec = stat->st_mtim.tv_sec; + pr_stat->st_mtim_nsec = stat->st_mtim.tv_nsec; + pr_stat->st_ctim_sec = stat->st_ctim.tv_sec; + pr_stat->st_ctim_nsec = stat->st_ctim.tv_nsec; +} + +static void statfs_to_prstatfs(ProxyStatFS *pr_stfs, struct statfs *stfs) +{ + memset(pr_stfs, 0, sizeof(*pr_stfs)); + pr_stfs->f_type = stfs->f_type; + pr_stfs->f_bsize = stfs->f_bsize; + pr_stfs->f_blocks = stfs->f_blocks; + pr_stfs->f_bfree = stfs->f_bfree; + pr_stfs->f_bavail = stfs->f_bavail; + pr_stfs->f_files = stfs->f_files; + pr_stfs->f_ffree = stfs->f_ffree; + pr_stfs->f_fsid[0] = stfs->f_fsid.__val[0]; + pr_stfs->f_fsid[1] = stfs->f_fsid.__val[1]; + pr_stfs->f_namelen = stfs->f_namelen; + pr_stfs->f_frsize = stfs->f_frsize; +} + +/* + * Gets stat/statfs information and packs in out_iovec structure + * on success returns number of bytes packed in out_iovec structure + * otherwise returns -errno + */ +static int do_stat(int type, struct iovec *iovec, struct iovec *out_iovec) +{ + int retval; + V9fsString path; + ProxyStat pr_stat; + ProxyStatFS pr_stfs; + struct stat st_buf; + struct statfs stfs_buf; + + v9fs_string_init(&path); + retval = proxy_unmarshal(iovec, PROXY_HDR_SZ, "s", &path); + if (retval < 0) { + return retval; + } + + switch (type) { + case T_LSTAT: + retval = lstat(path.data, &st_buf); + if (retval < 0) { + retval = -errno; + } else { + stat_to_prstat(&pr_stat, &st_buf); + retval = proxy_marshal(out_iovec, PROXY_HDR_SZ, + "qqqdddqqqqqqqqqq", pr_stat.st_dev, + pr_stat.st_ino, pr_stat.st_nlink, + pr_stat.st_mode, pr_stat.st_uid, + pr_stat.st_gid, pr_stat.st_rdev, + pr_stat.st_size, pr_stat.st_blksize, + pr_stat.st_blocks, + pr_stat.st_atim_sec, pr_stat.st_atim_nsec, + pr_stat.st_mtim_sec, pr_stat.st_mtim_nsec, + pr_stat.st_ctim_sec, pr_stat.st_ctim_nsec); + } + break; + case T_STATFS: + retval = statfs(path.data, &stfs_buf); + if (retval < 0) { + retval = -errno; + } else { + statfs_to_prstatfs(&pr_stfs, &stfs_buf); + retval = proxy_marshal(out_iovec, PROXY_HDR_SZ, + "qqqqqqqqqqq", pr_stfs.f_type, + pr_stfs.f_bsize, pr_stfs.f_blocks, + pr_stfs.f_bfree, pr_stfs.f_bavail, + pr_stfs.f_files, pr_stfs.f_ffree, + pr_stfs.f_fsid[0], pr_stfs.f_fsid[1], + pr_stfs.f_namelen, pr_stfs.f_frsize); + } + break; + } + v9fs_string_free(&path); + return retval; +} + +static int do_readlink(struct iovec *iovec, struct iovec *out_iovec) +{ + char *buffer; + int size, retval; + V9fsString target, path; + + v9fs_string_init(&path); + retval = proxy_unmarshal(iovec, PROXY_HDR_SZ, "sd", &path, &size); + if (retval < 0) { + v9fs_string_free(&path); + return retval; + } + buffer = g_malloc(size); + v9fs_string_init(&target); + retval = readlink(path.data, buffer, size - 1); + if (retval > 0) { + buffer[retval] = '\0'; + v9fs_string_sprintf(&target, "%s", buffer); + retval = proxy_marshal(out_iovec, PROXY_HDR_SZ, "s", &target); + } else { + retval = -errno; + } + g_free(buffer); + v9fs_string_free(&target); + v9fs_string_free(&path); + return retval; +} + +/* + * create other filesystem objects and send 0 on success + * return -errno on error + */ +static int do_create_others(int type, struct iovec *iovec) +{ + dev_t rdev; + int retval = 0; + int offset = PROXY_HDR_SZ; + V9fsString oldpath, path; + int mode, uid, gid, cur_uid, cur_gid; + + v9fs_string_init(&path); + v9fs_string_init(&oldpath); + + retval = proxy_unmarshal(iovec, offset, "dd", &uid, &gid); + if (retval < 0) { + return retval; + } + offset += retval; + retval = setugid(uid, gid, &cur_uid, &cur_gid); + if (retval < 0) { + goto unmarshal_err_out; + } + switch (type) { + case T_MKNOD: + retval = proxy_unmarshal(iovec, offset, "sdq", &path, &mode, &rdev); + if (retval < 0) { + goto err_out; + } + retval = mknod(path.data, mode, rdev); + break; + case T_MKDIR: + retval = proxy_unmarshal(iovec, offset, "sd", &path, &mode); + if (retval < 0) { + goto err_out; + } + retval = mkdir(path.data, mode); + break; + case T_SYMLINK: + retval = proxy_unmarshal(iovec, offset, "ss", &oldpath, &path); + if (retval < 0) { + goto err_out; + } + retval = symlink(oldpath.data, path.data); + break; + } + if (retval < 0) { + retval = -errno; + } + +err_out: + resetugid(cur_uid, cur_gid); +unmarshal_err_out: + v9fs_string_free(&path); + v9fs_string_free(&oldpath); + return retval; +} + +/* + * create a file and send fd on success + * return -errno on error + */ +static int do_create(struct iovec *iovec) +{ + int ret; + V9fsString path; + int flags, mode, uid, gid, cur_uid, cur_gid; + + v9fs_string_init(&path); + ret = proxy_unmarshal(iovec, PROXY_HDR_SZ, "sdddd", + &path, &flags, &mode, &uid, &gid); + if (ret < 0) { + goto unmarshal_err_out; + } + ret = setugid(uid, gid, &cur_uid, &cur_gid); + if (ret < 0) { + goto unmarshal_err_out; + } + ret = open(path.data, flags, mode); + if (ret < 0) { + ret = -errno; + } + + resetugid(cur_uid, cur_gid); +unmarshal_err_out: + v9fs_string_free(&path); + return ret; +} + +/* + * open a file and send fd on success + * return -errno on error + */ +static int do_open(struct iovec *iovec) +{ + int flags, ret; + V9fsString path; + + v9fs_string_init(&path); + ret = proxy_unmarshal(iovec, PROXY_HDR_SZ, "sd", &path, &flags); + if (ret < 0) { + goto err_out; + } + ret = open(path.data, flags); + if (ret < 0) { + ret = -errno; + } +err_out: + v9fs_string_free(&path); + return ret; +} + +/* create unix domain socket and return the descriptor */ +static int proxy_socket(const char *path, uid_t uid, gid_t gid) +{ + int sock, client; + struct sockaddr_un proxy, qemu; + socklen_t size; + + /* requested socket already exists, refuse to start */ + if (!access(path, F_OK)) { + do_log(LOG_CRIT, "socket already exists\n"); + return -1; + } + + if (strlen(path) >= sizeof(proxy.sun_path)) { + do_log(LOG_CRIT, "UNIX domain socket path exceeds %zu characters\n", + sizeof(proxy.sun_path)); + return -1; + } + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + do_perror("socket"); + return -1; + } + + /* mask other part of mode bits */ + umask(7); + + proxy.sun_family = AF_UNIX; + strcpy(proxy.sun_path, path); + if (bind(sock, (struct sockaddr *)&proxy, + sizeof(struct sockaddr_un)) < 0) { + do_perror("bind"); + goto error; + } + if (chown(proxy.sun_path, uid, gid) < 0) { + do_perror("chown"); + goto error; + } + if (listen(sock, 1) < 0) { + do_perror("listen"); + goto error; + } + + size = sizeof(qemu); + client = accept(sock, (struct sockaddr *)&qemu, &size); + if (client < 0) { + do_perror("accept"); + goto error; + } + close(sock); + return client; + +error: + close(sock); + return -1; +} + +static void usage(void) +{ + fprintf(stderr, "usage: %s\n" + " -p|--path <path> 9p path to export\n" + " {-f|--fd <socket-descriptor>} socket file descriptor to be used\n" + " {-s|--socket <socketname> socket file used for communication\n" + " \t-u|--uid <uid> -g|--gid <gid>} - uid:gid combination to give " + " access to this socket\n" + " \tNote: -s & -f can not be used together\n" + " [-n|--nodaemon] Run as a normal program\n", + prog_name); +} + +static int process_reply(int sock, int type, + struct iovec *out_iovec, int retval) +{ + switch (type) { + case T_OPEN: + case T_CREATE: + if (send_fd(sock, retval) < 0) { + return -1; + } + break; + case T_MKNOD: + case T_MKDIR: + case T_SYMLINK: + case T_LINK: + case T_CHMOD: + case T_CHOWN: + case T_TRUNCATE: + case T_UTIME: + case T_RENAME: + case T_REMOVE: + case T_LSETXATTR: + case T_LREMOVEXATTR: + if (send_status(sock, out_iovec, retval) < 0) { + return -1; + } + break; + case T_LSTAT: + case T_STATFS: + case T_READLINK: + case T_LGETXATTR: + case T_LLISTXATTR: + case T_GETVERSION: + if (send_response(sock, out_iovec, retval) < 0) { + return -1; + } + break; + default: + return -1; + break; + } + return 0; +} + +static int process_requests(int sock) +{ + int flags; + int size = 0; + int retval = 0; + uint64_t offset; + ProxyHeader header; + int mode, uid, gid; + V9fsString name, value; + struct timespec spec[2]; + V9fsString oldpath, path; + struct iovec in_iovec, out_iovec; + + in_iovec.iov_base = g_malloc(PROXY_MAX_IO_SZ + PROXY_HDR_SZ); + in_iovec.iov_len = PROXY_MAX_IO_SZ + PROXY_HDR_SZ; + out_iovec.iov_base = g_malloc(PROXY_MAX_IO_SZ + PROXY_HDR_SZ); + out_iovec.iov_len = PROXY_MAX_IO_SZ + PROXY_HDR_SZ; + + while (1) { + /* + * initialize the header type, so that we send + * response to proper request type. + */ + header.type = 0; + retval = read_request(sock, &in_iovec, &header); + if (retval < 0) { + goto err_out; + } + + switch (header.type) { + case T_OPEN: + retval = do_open(&in_iovec); + break; + case T_CREATE: + retval = do_create(&in_iovec); + break; + case T_MKNOD: + case T_MKDIR: + case T_SYMLINK: + retval = do_create_others(header.type, &in_iovec); + break; + case T_LINK: + v9fs_string_init(&path); + v9fs_string_init(&oldpath); + retval = proxy_unmarshal(&in_iovec, PROXY_HDR_SZ, + "ss", &oldpath, &path); + if (retval > 0) { + retval = link(oldpath.data, path.data); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&oldpath); + v9fs_string_free(&path); + break; + case T_LSTAT: + case T_STATFS: + retval = do_stat(header.type, &in_iovec, &out_iovec); + break; + case T_READLINK: + retval = do_readlink(&in_iovec, &out_iovec); + break; + case T_CHMOD: + v9fs_string_init(&path); + retval = proxy_unmarshal(&in_iovec, PROXY_HDR_SZ, + "sd", &path, &mode); + if (retval > 0) { + retval = chmod(path.data, mode); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&path); + break; + case T_CHOWN: + v9fs_string_init(&path); + retval = proxy_unmarshal(&in_iovec, PROXY_HDR_SZ, "sdd", &path, + &uid, &gid); + if (retval > 0) { + retval = lchown(path.data, uid, gid); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&path); + break; + case T_TRUNCATE: + v9fs_string_init(&path); + retval = proxy_unmarshal(&in_iovec, PROXY_HDR_SZ, "sq", + &path, &offset); + if (retval > 0) { + retval = truncate(path.data, offset); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&path); + break; + case T_UTIME: + v9fs_string_init(&path); + retval = proxy_unmarshal(&in_iovec, PROXY_HDR_SZ, "sqqqq", &path, + &spec[0].tv_sec, &spec[0].tv_nsec, + &spec[1].tv_sec, &spec[1].tv_nsec); + if (retval > 0) { + retval = utimensat(AT_FDCWD, path.data, spec, + AT_SYMLINK_NOFOLLOW); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&path); + break; + case T_RENAME: + v9fs_string_init(&path); + v9fs_string_init(&oldpath); + retval = proxy_unmarshal(&in_iovec, PROXY_HDR_SZ, + "ss", &oldpath, &path); + if (retval > 0) { + retval = rename(oldpath.data, path.data); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&oldpath); + v9fs_string_free(&path); + break; + case T_REMOVE: + v9fs_string_init(&path); + retval = proxy_unmarshal(&in_iovec, PROXY_HDR_SZ, "s", &path); + if (retval > 0) { + retval = remove(path.data); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&path); + break; + case T_LGETXATTR: + case T_LLISTXATTR: + retval = do_getxattr(header.type, &in_iovec, &out_iovec); + break; + case T_LSETXATTR: + v9fs_string_init(&path); + v9fs_string_init(&name); + v9fs_string_init(&value); + retval = proxy_unmarshal(&in_iovec, PROXY_HDR_SZ, "sssdd", &path, + &name, &value, &size, &flags); + if (retval > 0) { + retval = lsetxattr(path.data, + name.data, value.data, size, flags); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&path); + v9fs_string_free(&name); + v9fs_string_free(&value); + break; + case T_LREMOVEXATTR: + v9fs_string_init(&path); + v9fs_string_init(&name); + retval = proxy_unmarshal(&in_iovec, + PROXY_HDR_SZ, "ss", &path, &name); + if (retval > 0) { + retval = lremovexattr(path.data, name.data); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&path); + v9fs_string_free(&name); + break; + case T_GETVERSION: + retval = do_getversion(&in_iovec, &out_iovec); + break; + default: + goto err_out; + break; + } + + if (process_reply(sock, header.type, &out_iovec, retval) < 0) { + goto err_out; + } + } +err_out: + g_free(in_iovec.iov_base); + g_free(out_iovec.iov_base); + return -1; +} + +int main(int argc, char **argv) +{ + int sock; + uid_t own_u; + gid_t own_g; + char *rpath = NULL; + char *sock_name = NULL; + struct stat stbuf; + int c, option_index; +#ifdef FS_IOC_GETVERSION + int retval; + struct statfs st_fs; +#endif + + prog_name = g_path_get_basename(argv[0]); + + is_daemon = true; + sock = -1; + own_u = own_g = -1; + while (1) { + option_index = 0; + c = getopt_long(argc, argv, "p:nh?f:s:u:g:", helper_opts, + &option_index); + if (c == -1) { + break; + } + switch (c) { + case 'p': + rpath = g_strdup(optarg); + break; + case 'n': + is_daemon = false; + break; + case 'f': + sock = atoi(optarg); + break; + case 's': + sock_name = g_strdup(optarg); + break; + case 'u': + own_u = atoi(optarg); + break; + case 'g': + own_g = atoi(optarg); + break; + case '?': + case 'h': + default: + usage(); + exit(EXIT_FAILURE); + } + } + + /* Parameter validation */ + if ((sock_name == NULL && sock == -1) || rpath == NULL) { + fprintf(stderr, "socket, socket descriptor or path not specified\n"); + usage(); + return -1; + } + + if (sock_name && sock != -1) { + fprintf(stderr, "both named socket and socket descriptor specified\n"); + usage(); + exit(EXIT_FAILURE); + } + + if (sock_name && (own_u == -1 || own_g == -1)) { + fprintf(stderr, "owner uid:gid not specified, "); + fprintf(stderr, + "owner uid:gid specifies who can access the socket file\n"); + usage(); + exit(EXIT_FAILURE); + } + + if (lstat(rpath, &stbuf) < 0) { + fprintf(stderr, "invalid path \"%s\" specified, %s\n", + rpath, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (!S_ISDIR(stbuf.st_mode)) { + fprintf(stderr, "specified path \"%s\" is not directory\n", rpath); + exit(EXIT_FAILURE); + } + + if (is_daemon) { + if (daemon(0, 0) < 0) { + fprintf(stderr, "daemon call failed\n"); + exit(EXIT_FAILURE); + } + openlog(PROGNAME, LOG_PID, LOG_DAEMON); + } + + do_log(LOG_INFO, "Started\n"); + if (sock_name) { + sock = proxy_socket(sock_name, own_u, own_g); + if (sock < 0) { + goto error; + } + } + + if (chroot(rpath) < 0) { + do_perror("chroot"); + goto error; + } + if (chdir("/") < 0) { + do_perror("chdir"); + goto error; + } + + get_version = false; +#ifdef FS_IOC_GETVERSION + /* check whether underlying FS support IOC_GETVERSION */ + retval = statfs("/", &st_fs); + if (!retval) { + switch (st_fs.f_type) { + case EXT2_SUPER_MAGIC: + case BTRFS_SUPER_MAGIC: + case REISERFS_SUPER_MAGIC: + case XFS_SUPER_MAGIC: + get_version = true; + break; + } + } +#endif + + umask(0); + if (init_capabilities() < 0) { + goto error; + } + + process_requests(sock); +error: + g_free(rpath); + g_free(sock_name); + do_log(LOG_INFO, "Done\n"); + closelog(); + return 0; +} |