qemu/hw/9pfs/9p-proxy.c
Christian Schoenebeck 71d72ececa 9pfs: deprecate 'proxy' backend
As recent CVE-2023-2861 (fixed by f6b0de53fb) once again showed, the 9p
'proxy' fs driver is in bad shape. Using the 'proxy' backend was already
discouraged for safety reasons before and we recommended to use the
'local' backend (preferably in conjunction with its 'mapped' security
model) instead, but now it is time to officially deprecate the 'proxy'
backend.

Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
Reviewed-by: Greg Kurz <groug@kaod.org>
Message-Id: <E1qDkmw-0007M1-8f@lizzy.crudebyte.com>
2023-07-06 11:42:08 +02:00

1280 lines
36 KiB
C

/*
* 9p Proxy callback
*
* 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.
*/
/*
* Not so fast! You might want to read the 9p developer docs first:
* https://wiki.qemu.org/Documentation/9p
*/
/*
* NOTE: The 9p 'proxy' backend is deprecated (since QEMU 8.1) and will be
* removed in a future version of QEMU!
*/
#include "qemu/osdep.h"
#include <sys/socket.h>
#include <sys/un.h>
#include "9p.h"
#include "qapi/error.h"
#include "qemu/cutils.h"
#include "qemu/error-report.h"
#include "qemu/option.h"
#include "fsdev/qemu-fsdev.h"
#include "9p-proxy.h"
typedef struct V9fsProxy {
int sockfd;
QemuMutex mutex;
struct iovec in_iovec;
struct iovec out_iovec;
} V9fsProxy;
/*
* Return received file descriptor on success in *status.
* errno is also returned on *status (which will be < 0)
* return < 0 on transport error.
*/
static int v9fs_receivefd(int sockfd, int *status)
{
struct iovec iov;
struct msghdr msg;
struct cmsghdr *cmsg;
int retval, data, fd;
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;
msg.msg_control = &msg_control;
msg.msg_controllen = sizeof(msg_control);
do {
retval = recvmsg(sockfd, &msg, 0);
} while (retval < 0 && errno == EINTR);
if (retval <= 0) {
return retval;
}
/*
* data is set to V9FS_FD_VALID, if ancillary data is sent. If this
* request doesn't need ancillary data (fd) or an error occurred,
* data is set to negative errno value.
*/
if (data != V9FS_FD_VALID) {
*status = data;
return 0;
}
/*
* File descriptor (fd) is sent in the ancillary data. Check if we
* indeed received it. One of the reasons to fail to receive it is if
* we exceeded the maximum number of file descriptors!
*/
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_len != CMSG_LEN(sizeof(int)) ||
cmsg->cmsg_level != SOL_SOCKET ||
cmsg->cmsg_type != SCM_RIGHTS) {
continue;
}
fd = *((int *)CMSG_DATA(cmsg));
*status = fd;
return 0;
}
*status = -ENFILE; /* Ancillary data sent but not received */
return 0;
}
static ssize_t socket_read(int sockfd, void *buff, size_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;
}
/* Converts proxy_statfs to VFS statfs structure */
static void prstatfs_to_statfs(struct statfs *stfs, ProxyStatFS *prstfs)
{
memset(stfs, 0, sizeof(*stfs));
stfs->f_type = prstfs->f_type;
stfs->f_bsize = prstfs->f_bsize;
stfs->f_blocks = prstfs->f_blocks;
stfs->f_bfree = prstfs->f_bfree;
stfs->f_bavail = prstfs->f_bavail;
stfs->f_files = prstfs->f_files;
stfs->f_ffree = prstfs->f_ffree;
#ifdef CONFIG_DARWIN
/* f_namelen and f_frsize do not exist on Darwin */
stfs->f_fsid.val[0] = prstfs->f_fsid[0] & 0xFFFFFFFFU;
stfs->f_fsid.val[1] = prstfs->f_fsid[1] >> 32 & 0xFFFFFFFFU;
#else
stfs->f_fsid.__val[0] = prstfs->f_fsid[0] & 0xFFFFFFFFU;
stfs->f_fsid.__val[1] = prstfs->f_fsid[1] >> 32 & 0xFFFFFFFFU;
stfs->f_namelen = prstfs->f_namelen;
stfs->f_frsize = prstfs->f_frsize;
#endif
}
/* Converts proxy_stat structure to VFS stat structure */
static void prstat_to_stat(struct stat *stbuf, ProxyStat *prstat)
{
memset(stbuf, 0, sizeof(*stbuf));
stbuf->st_dev = prstat->st_dev;
stbuf->st_ino = prstat->st_ino;
stbuf->st_nlink = prstat->st_nlink;
stbuf->st_mode = prstat->st_mode;
stbuf->st_uid = prstat->st_uid;
stbuf->st_gid = prstat->st_gid;
stbuf->st_rdev = prstat->st_rdev;
stbuf->st_size = prstat->st_size;
stbuf->st_blksize = prstat->st_blksize;
stbuf->st_blocks = prstat->st_blocks;
stbuf->st_atime = prstat->st_atim_sec;
stbuf->st_mtime = prstat->st_mtim_sec;
stbuf->st_ctime = prstat->st_ctim_sec;
#ifdef CONFIG_DARWIN
stbuf->st_atimespec.tv_sec = prstat->st_atim_sec;
stbuf->st_mtimespec.tv_sec = prstat->st_mtim_sec;
stbuf->st_ctimespec.tv_sec = prstat->st_ctim_sec;
stbuf->st_atimespec.tv_nsec = prstat->st_atim_nsec;
stbuf->st_mtimespec.tv_nsec = prstat->st_mtim_nsec;
stbuf->st_ctimespec.tv_nsec = prstat->st_ctim_nsec;
#else
stbuf->st_atim.tv_sec = prstat->st_atim_sec;
stbuf->st_mtim.tv_sec = prstat->st_mtim_sec;
stbuf->st_ctim.tv_sec = prstat->st_ctim_sec;
stbuf->st_atim.tv_nsec = prstat->st_atim_nsec;
stbuf->st_mtim.tv_nsec = prstat->st_mtim_nsec;
stbuf->st_ctim.tv_nsec = prstat->st_ctim_nsec;
#endif
}
/*
* Response contains two parts
* {header, data}
* header.type == T_ERROR, data -> -errno
* header.type == T_SUCCESS, data -> response
* size of errno/response is given by header.size
* returns < 0, on transport error. response is
* valid only if status >= 0.
*/
static int v9fs_receive_response(V9fsProxy *proxy, int type,
int *status, void *response)
{
int retval;
ProxyHeader header;
struct iovec *reply = &proxy->in_iovec;
*status = 0;
reply->iov_len = 0;
retval = socket_read(proxy->sockfd, reply->iov_base, PROXY_HDR_SZ);
if (retval < 0) {
return retval;
}
reply->iov_len = PROXY_HDR_SZ;
retval = proxy_unmarshal(reply, 0, "dd", &header.type, &header.size);
assert(retval == 4 * 2);
/*
* if response size > PROXY_MAX_IO_SZ, read the response but ignore it and
* return -ENOBUFS
*/
if (header.size > PROXY_MAX_IO_SZ) {
int count;
while (header.size > 0) {
count = MIN(PROXY_MAX_IO_SZ, header.size);
count = socket_read(proxy->sockfd, reply->iov_base, count);
if (count < 0) {
return count;
}
header.size -= count;
}
*status = -ENOBUFS;
return 0;
}
retval = socket_read(proxy->sockfd,
reply->iov_base + PROXY_HDR_SZ, header.size);
if (retval < 0) {
return retval;
}
reply->iov_len += header.size;
/* there was an error during processing request */
if (header.type == T_ERROR) {
int ret;
ret = proxy_unmarshal(reply, PROXY_HDR_SZ, "d", status);
assert(ret == 4);
return 0;
}
switch (type) {
case T_LSTAT: {
ProxyStat prstat;
retval = proxy_unmarshal(reply, PROXY_HDR_SZ,
"qqqdddqqqqqqqqqq", &prstat.st_dev,
&prstat.st_ino, &prstat.st_nlink,
&prstat.st_mode, &prstat.st_uid,
&prstat.st_gid, &prstat.st_rdev,
&prstat.st_size, &prstat.st_blksize,
&prstat.st_blocks,
&prstat.st_atim_sec, &prstat.st_atim_nsec,
&prstat.st_mtim_sec, &prstat.st_mtim_nsec,
&prstat.st_ctim_sec, &prstat.st_ctim_nsec);
assert(retval == 8 * 3 + 4 * 3 + 8 * 10);
prstat_to_stat(response, &prstat);
break;
}
case T_STATFS: {
ProxyStatFS prstfs;
retval = proxy_unmarshal(reply, PROXY_HDR_SZ,
"qqqqqqqqqqq", &prstfs.f_type,
&prstfs.f_bsize, &prstfs.f_blocks,
&prstfs.f_bfree, &prstfs.f_bavail,
&prstfs.f_files, &prstfs.f_ffree,
&prstfs.f_fsid[0], &prstfs.f_fsid[1],
&prstfs.f_namelen, &prstfs.f_frsize);
assert(retval == 8 * 11);
prstatfs_to_statfs(response, &prstfs);
break;
}
case T_READLINK: {
V9fsString target;
v9fs_string_init(&target);
retval = proxy_unmarshal(reply, PROXY_HDR_SZ, "s", &target);
strcpy(response, target.data);
v9fs_string_free(&target);
break;
}
case T_LGETXATTR:
case T_LLISTXATTR: {
V9fsString xattr;
v9fs_string_init(&xattr);
retval = proxy_unmarshal(reply, PROXY_HDR_SZ, "s", &xattr);
memcpy(response, xattr.data, xattr.size);
v9fs_string_free(&xattr);
break;
}
case T_GETVERSION:
retval = proxy_unmarshal(reply, PROXY_HDR_SZ, "q", response);
assert(retval == 8);
break;
default:
return -1;
}
if (retval < 0) {
*status = retval;
}
return 0;
}
/*
* return < 0 on transport error.
* *status is valid only if return >= 0
*/
static int v9fs_receive_status(V9fsProxy *proxy,
struct iovec *reply, int *status)
{
int retval;
ProxyHeader header;
*status = 0;
reply->iov_len = 0;
retval = socket_read(proxy->sockfd, reply->iov_base, PROXY_HDR_SZ);
if (retval < 0) {
return retval;
}
reply->iov_len = PROXY_HDR_SZ;
retval = proxy_unmarshal(reply, 0, "dd", &header.type, &header.size);
assert(retval == 4 * 2);
retval = socket_read(proxy->sockfd,
reply->iov_base + PROXY_HDR_SZ, header.size);
if (retval < 0) {
return retval;
}
reply->iov_len += header.size;
retval = proxy_unmarshal(reply, PROXY_HDR_SZ, "d", status);
assert(retval == 4);
return 0;
}
/*
* Proxy->header and proxy->request written to socket by QEMU process.
* This request read by proxy helper process
* returns 0 on success and -errno on error
*/
static int v9fs_request(V9fsProxy *proxy, int type, void *response, ...)
{
dev_t rdev;
va_list ap;
int size = 0;
int retval = 0;
uint64_t offset;
ProxyHeader header = { 0, 0};
struct timespec spec[2];
int flags, mode, uid, gid;
V9fsString *name, *value;
V9fsString *path, *oldpath;
struct iovec *iovec = NULL, *reply = NULL;
qemu_mutex_lock(&proxy->mutex);
if (proxy->sockfd == -1) {
retval = -EIO;
goto err_out;
}
iovec = &proxy->out_iovec;
reply = &proxy->in_iovec;
va_start(ap, response);
switch (type) {
case T_OPEN:
path = va_arg(ap, V9fsString *);
flags = va_arg(ap, int);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sd", path, flags);
if (retval > 0) {
header.size = retval;
header.type = T_OPEN;
}
break;
case T_CREATE:
path = va_arg(ap, V9fsString *);
flags = va_arg(ap, int);
mode = va_arg(ap, int);
uid = va_arg(ap, int);
gid = va_arg(ap, int);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sdddd", path,
flags, mode, uid, gid);
if (retval > 0) {
header.size = retval;
header.type = T_CREATE;
}
break;
case T_MKNOD:
path = va_arg(ap, V9fsString *);
mode = va_arg(ap, int);
rdev = va_arg(ap, long int);
uid = va_arg(ap, int);
gid = va_arg(ap, int);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "ddsdq",
uid, gid, path, mode, rdev);
if (retval > 0) {
header.size = retval;
header.type = T_MKNOD;
}
break;
case T_MKDIR:
path = va_arg(ap, V9fsString *);
mode = va_arg(ap, int);
uid = va_arg(ap, int);
gid = va_arg(ap, int);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "ddsd",
uid, gid, path, mode);
if (retval > 0) {
header.size = retval;
header.type = T_MKDIR;
}
break;
case T_SYMLINK:
oldpath = va_arg(ap, V9fsString *);
path = va_arg(ap, V9fsString *);
uid = va_arg(ap, int);
gid = va_arg(ap, int);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "ddss",
uid, gid, oldpath, path);
if (retval > 0) {
header.size = retval;
header.type = T_SYMLINK;
}
break;
case T_LINK:
oldpath = va_arg(ap, V9fsString *);
path = va_arg(ap, V9fsString *);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "ss",
oldpath, path);
if (retval > 0) {
header.size = retval;
header.type = T_LINK;
}
break;
case T_LSTAT:
path = va_arg(ap, V9fsString *);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "s", path);
if (retval > 0) {
header.size = retval;
header.type = T_LSTAT;
}
break;
case T_READLINK:
path = va_arg(ap, V9fsString *);
size = va_arg(ap, int);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sd", path, size);
if (retval > 0) {
header.size = retval;
header.type = T_READLINK;
}
break;
case T_STATFS:
path = va_arg(ap, V9fsString *);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "s", path);
if (retval > 0) {
header.size = retval;
header.type = T_STATFS;
}
break;
case T_CHMOD:
path = va_arg(ap, V9fsString *);
mode = va_arg(ap, int);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sd", path, mode);
if (retval > 0) {
header.size = retval;
header.type = T_CHMOD;
}
break;
case T_CHOWN:
path = va_arg(ap, V9fsString *);
uid = va_arg(ap, int);
gid = va_arg(ap, int);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sdd", path, uid, gid);
if (retval > 0) {
header.size = retval;
header.type = T_CHOWN;
}
break;
case T_TRUNCATE:
path = va_arg(ap, V9fsString *);
offset = va_arg(ap, uint64_t);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sq", path, offset);
if (retval > 0) {
header.size = retval;
header.type = T_TRUNCATE;
}
break;
case T_UTIME:
path = va_arg(ap, V9fsString *);
spec[0].tv_sec = va_arg(ap, long);
spec[0].tv_nsec = va_arg(ap, long);
spec[1].tv_sec = va_arg(ap, long);
spec[1].tv_nsec = va_arg(ap, long);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sqqqq", path,
spec[0].tv_sec, spec[1].tv_nsec,
spec[1].tv_sec, spec[1].tv_nsec);
if (retval > 0) {
header.size = retval;
header.type = T_UTIME;
}
break;
case T_RENAME:
oldpath = va_arg(ap, V9fsString *);
path = va_arg(ap, V9fsString *);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "ss", oldpath, path);
if (retval > 0) {
header.size = retval;
header.type = T_RENAME;
}
break;
case T_REMOVE:
path = va_arg(ap, V9fsString *);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "s", path);
if (retval > 0) {
header.size = retval;
header.type = T_REMOVE;
}
break;
case T_LGETXATTR:
size = va_arg(ap, int);
path = va_arg(ap, V9fsString *);
name = va_arg(ap, V9fsString *);
retval = proxy_marshal(iovec, PROXY_HDR_SZ,
"dss", size, path, name);
if (retval > 0) {
header.size = retval;
header.type = T_LGETXATTR;
}
break;
case T_LLISTXATTR:
size = va_arg(ap, int);
path = va_arg(ap, V9fsString *);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "ds", size, path);
if (retval > 0) {
header.size = retval;
header.type = T_LLISTXATTR;
}
break;
case T_LSETXATTR:
path = va_arg(ap, V9fsString *);
name = va_arg(ap, V9fsString *);
value = va_arg(ap, V9fsString *);
size = va_arg(ap, int);
flags = va_arg(ap, int);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sssdd",
path, name, value, size, flags);
if (retval > 0) {
header.size = retval;
header.type = T_LSETXATTR;
}
break;
case T_LREMOVEXATTR:
path = va_arg(ap, V9fsString *);
name = va_arg(ap, V9fsString *);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "ss", path, name);
if (retval > 0) {
header.size = retval;
header.type = T_LREMOVEXATTR;
}
break;
case T_GETVERSION:
path = va_arg(ap, V9fsString *);
retval = proxy_marshal(iovec, PROXY_HDR_SZ, "s", path);
if (retval > 0) {
header.size = retval;
header.type = T_GETVERSION;
}
break;
default:
error_report("Invalid type %d", type);
retval = -EINVAL;
break;
}
va_end(ap);
if (retval < 0) {
goto err_out;
}
/* marshal the header details */
retval = proxy_marshal(iovec, 0, "dd", header.type, header.size);
assert(retval == 4 * 2);
header.size += PROXY_HDR_SZ;
retval = qemu_write_full(proxy->sockfd, iovec->iov_base, header.size);
if (retval != header.size) {
goto close_error;
}
switch (type) {
case T_OPEN:
case T_CREATE:
/*
* A file descriptor is returned as response for
* T_OPEN,T_CREATE on success
*/
if (v9fs_receivefd(proxy->sockfd, &retval) < 0) {
goto close_error;
}
break;
case T_MKNOD:
case T_MKDIR:
case T_SYMLINK:
case T_LINK:
case T_CHMOD:
case T_CHOWN:
case T_RENAME:
case T_TRUNCATE:
case T_UTIME:
case T_REMOVE:
case T_LSETXATTR:
case T_LREMOVEXATTR:
if (v9fs_receive_status(proxy, reply, &retval) < 0) {
goto close_error;
}
break;
case T_LSTAT:
case T_READLINK:
case T_STATFS:
case T_GETVERSION:
if (v9fs_receive_response(proxy, type, &retval, response) < 0) {
goto close_error;
}
break;
case T_LGETXATTR:
case T_LLISTXATTR:
if (!size) {
if (v9fs_receive_status(proxy, reply, &retval) < 0) {
goto close_error;
}
} else {
if (v9fs_receive_response(proxy, type, &retval, response) < 0) {
goto close_error;
}
}
break;
}
err_out:
qemu_mutex_unlock(&proxy->mutex);
return retval;
close_error:
close(proxy->sockfd);
proxy->sockfd = -1;
qemu_mutex_unlock(&proxy->mutex);
return -EIO;
}
static int proxy_lstat(FsContext *fs_ctx, V9fsPath *fs_path, struct stat *stbuf)
{
int retval;
retval = v9fs_request(fs_ctx->private, T_LSTAT, stbuf, fs_path);
if (retval < 0) {
errno = -retval;
return -1;
}
return retval;
}
static ssize_t proxy_readlink(FsContext *fs_ctx, V9fsPath *fs_path,
char *buf, size_t bufsz)
{
int retval;
retval = v9fs_request(fs_ctx->private, T_READLINK, buf, fs_path, bufsz);
if (retval < 0) {
errno = -retval;
return -1;
}
return strlen(buf);
}
static int proxy_close(FsContext *ctx, V9fsFidOpenState *fs)
{
return close(fs->fd);
}
static int proxy_closedir(FsContext *ctx, V9fsFidOpenState *fs)
{
return closedir(fs->dir.stream);
}
static int proxy_open(FsContext *ctx, V9fsPath *fs_path,
int flags, V9fsFidOpenState *fs)
{
fs->fd = v9fs_request(ctx->private, T_OPEN, NULL, fs_path, flags);
if (fs->fd < 0) {
errno = -fs->fd;
fs->fd = -1;
}
return fs->fd;
}
static int proxy_opendir(FsContext *ctx,
V9fsPath *fs_path, V9fsFidOpenState *fs)
{
int serrno, fd;
fs->dir.stream = NULL;
fd = v9fs_request(ctx->private, T_OPEN, NULL, fs_path, O_DIRECTORY);
if (fd < 0) {
errno = -fd;
return -1;
}
fs->dir.stream = fdopendir(fd);
if (!fs->dir.stream) {
serrno = errno;
close(fd);
errno = serrno;
return -1;
}
return 0;
}
static void proxy_rewinddir(FsContext *ctx, V9fsFidOpenState *fs)
{
rewinddir(fs->dir.stream);
}
static off_t proxy_telldir(FsContext *ctx, V9fsFidOpenState *fs)
{
return telldir(fs->dir.stream);
}
static struct dirent *proxy_readdir(FsContext *ctx, V9fsFidOpenState *fs)
{
struct dirent *entry;
entry = readdir(fs->dir.stream);
#ifdef CONFIG_DARWIN
if (!entry) {
return NULL;
}
int td;
td = telldir(fs->dir.stream);
/* If telldir fails, fail the entire readdir call */
if (td < 0) {
return NULL;
}
entry->d_seekoff = td;
#endif
return entry;
}
static void proxy_seekdir(FsContext *ctx, V9fsFidOpenState *fs, off_t off)
{
seekdir(fs->dir.stream, off);
}
static ssize_t proxy_preadv(FsContext *ctx, V9fsFidOpenState *fs,
const struct iovec *iov,
int iovcnt, off_t offset)
{
ssize_t ret;
#ifdef CONFIG_PREADV
ret = preadv(fs->fd, iov, iovcnt, offset);
#else
ret = lseek(fs->fd, offset, SEEK_SET);
if (ret >= 0) {
ret = readv(fs->fd, iov, iovcnt);
}
#endif
return ret;
}
static ssize_t proxy_pwritev(FsContext *ctx, V9fsFidOpenState *fs,
const struct iovec *iov,
int iovcnt, off_t offset)
{
ssize_t ret;
#ifdef CONFIG_PREADV
ret = pwritev(fs->fd, iov, iovcnt, offset);
#else
ret = lseek(fs->fd, offset, SEEK_SET);
if (ret >= 0) {
ret = writev(fs->fd, iov, iovcnt);
}
#endif
#ifdef CONFIG_SYNC_FILE_RANGE
if (ret > 0 && ctx->export_flags & V9FS_IMMEDIATE_WRITEOUT) {
/*
* Initiate a writeback. This is not a data integrity sync.
* We want to ensure that we don't leave dirty pages in the cache
* after write when writeout=immediate is sepcified.
*/
sync_file_range(fs->fd, offset, ret,
SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE);
}
#endif
return ret;
}
static int proxy_chmod(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
{
int retval;
retval = v9fs_request(fs_ctx->private, T_CHMOD, NULL, fs_path,
credp->fc_mode);
if (retval < 0) {
errno = -retval;
}
return retval;
}
static int proxy_mknod(FsContext *fs_ctx, V9fsPath *dir_path,
const char *name, FsCred *credp)
{
int retval;
V9fsString fullname;
v9fs_string_init(&fullname);
v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
retval = v9fs_request(fs_ctx->private, T_MKNOD, NULL, &fullname,
credp->fc_mode, credp->fc_rdev,
credp->fc_uid, credp->fc_gid);
v9fs_string_free(&fullname);
if (retval < 0) {
errno = -retval;
retval = -1;
}
return retval;
}
static int proxy_mkdir(FsContext *fs_ctx, V9fsPath *dir_path,
const char *name, FsCred *credp)
{
int retval;
V9fsString fullname;
v9fs_string_init(&fullname);
v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
retval = v9fs_request(fs_ctx->private, T_MKDIR, NULL, &fullname,
credp->fc_mode, credp->fc_uid, credp->fc_gid);
v9fs_string_free(&fullname);
if (retval < 0) {
errno = -retval;
retval = -1;
}
return retval;
}
static int proxy_fstat(FsContext *fs_ctx, int fid_type,
V9fsFidOpenState *fs, struct stat *stbuf)
{
int fd;
if (fid_type == P9_FID_DIR) {
fd = dirfd(fs->dir.stream);
} else {
fd = fs->fd;
}
return fstat(fd, stbuf);
}
static int proxy_open2(FsContext *fs_ctx, V9fsPath *dir_path, const char *name,
int flags, FsCred *credp, V9fsFidOpenState *fs)
{
V9fsString fullname;
v9fs_string_init(&fullname);
v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
fs->fd = v9fs_request(fs_ctx->private, T_CREATE, NULL, &fullname, flags,
credp->fc_mode, credp->fc_uid, credp->fc_gid);
v9fs_string_free(&fullname);
if (fs->fd < 0) {
errno = -fs->fd;
fs->fd = -1;
}
return fs->fd;
}
static int proxy_symlink(FsContext *fs_ctx, const char *oldpath,
V9fsPath *dir_path, const char *name, FsCred *credp)
{
int retval;
V9fsString fullname, target;
v9fs_string_init(&fullname);
v9fs_string_init(&target);
v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
v9fs_string_sprintf(&target, "%s", oldpath);
retval = v9fs_request(fs_ctx->private, T_SYMLINK, NULL, &target, &fullname,
credp->fc_uid, credp->fc_gid);
v9fs_string_free(&fullname);
v9fs_string_free(&target);
if (retval < 0) {
errno = -retval;
retval = -1;
}
return retval;
}
static int proxy_link(FsContext *ctx, V9fsPath *oldpath,
V9fsPath *dirpath, const char *name)
{
int retval;
V9fsString newpath;
v9fs_string_init(&newpath);
v9fs_string_sprintf(&newpath, "%s/%s", dirpath->data, name);
retval = v9fs_request(ctx->private, T_LINK, NULL, oldpath, &newpath);
v9fs_string_free(&newpath);
if (retval < 0) {
errno = -retval;
retval = -1;
}
return retval;
}
static int proxy_truncate(FsContext *ctx, V9fsPath *fs_path, off_t size)
{
int retval;
retval = v9fs_request(ctx->private, T_TRUNCATE, NULL, fs_path, size);
if (retval < 0) {
errno = -retval;
return -1;
}
return 0;
}
static int proxy_rename(FsContext *ctx, const char *oldpath,
const char *newpath)
{
int retval;
V9fsString oldname, newname;
v9fs_string_init(&oldname);
v9fs_string_init(&newname);
v9fs_string_sprintf(&oldname, "%s", oldpath);
v9fs_string_sprintf(&newname, "%s", newpath);
retval = v9fs_request(ctx->private, T_RENAME, NULL, &oldname, &newname);
v9fs_string_free(&oldname);
v9fs_string_free(&newname);
if (retval < 0) {
errno = -retval;
}
return retval;
}
static int proxy_chown(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
{
int retval;
retval = v9fs_request(fs_ctx->private, T_CHOWN, NULL, fs_path,
credp->fc_uid, credp->fc_gid);
if (retval < 0) {
errno = -retval;
}
return retval;
}
static int proxy_utimensat(FsContext *s, V9fsPath *fs_path,
const struct timespec *buf)
{
int retval;
retval = v9fs_request(s->private, T_UTIME, NULL, fs_path,
buf[0].tv_sec, buf[0].tv_nsec,
buf[1].tv_sec, buf[1].tv_nsec);
if (retval < 0) {
errno = -retval;
}
return retval;
}
static int proxy_remove(FsContext *ctx, const char *path)
{
int retval;
V9fsString name;
v9fs_string_init(&name);
v9fs_string_sprintf(&name, "%s", path);
retval = v9fs_request(ctx->private, T_REMOVE, NULL, &name);
v9fs_string_free(&name);
if (retval < 0) {
errno = -retval;
}
return retval;
}
static int proxy_fsync(FsContext *ctx, int fid_type,
V9fsFidOpenState *fs, int datasync)
{
int fd;
if (fid_type == P9_FID_DIR) {
fd = dirfd(fs->dir.stream);
} else {
fd = fs->fd;
}
if (datasync) {
return qemu_fdatasync(fd);
} else {
return fsync(fd);
}
}
static int proxy_statfs(FsContext *s, V9fsPath *fs_path, struct statfs *stbuf)
{
int retval;
retval = v9fs_request(s->private, T_STATFS, stbuf, fs_path);
if (retval < 0) {
errno = -retval;
return -1;
}
return retval;
}
static ssize_t proxy_lgetxattr(FsContext *ctx, V9fsPath *fs_path,
const char *name, void *value, size_t size)
{
int retval;
V9fsString xname;
v9fs_string_init(&xname);
v9fs_string_sprintf(&xname, "%s", name);
retval = v9fs_request(ctx->private, T_LGETXATTR, value, size, fs_path,
&xname);
v9fs_string_free(&xname);
if (retval < 0) {
errno = -retval;
}
return retval;
}
static ssize_t proxy_llistxattr(FsContext *ctx, V9fsPath *fs_path,
void *value, size_t size)
{
int retval;
retval = v9fs_request(ctx->private, T_LLISTXATTR, value, size, fs_path);
if (retval < 0) {
errno = -retval;
}
return retval;
}
static int proxy_lsetxattr(FsContext *ctx, V9fsPath *fs_path, const char *name,
void *value, size_t size, int flags)
{
int retval;
V9fsString xname, xvalue;
v9fs_string_init(&xname);
v9fs_string_sprintf(&xname, "%s", name);
v9fs_string_init(&xvalue);
xvalue.size = size;
xvalue.data = g_malloc(size);
memcpy(xvalue.data, value, size);
retval = v9fs_request(ctx->private, T_LSETXATTR, value, fs_path, &xname,
&xvalue, size, flags);
v9fs_string_free(&xname);
v9fs_string_free(&xvalue);
if (retval < 0) {
errno = -retval;
}
return retval;
}
static int proxy_lremovexattr(FsContext *ctx, V9fsPath *fs_path,
const char *name)
{
int retval;
V9fsString xname;
v9fs_string_init(&xname);
v9fs_string_sprintf(&xname, "%s", name);
retval = v9fs_request(ctx->private, T_LREMOVEXATTR, NULL, fs_path, &xname);
v9fs_string_free(&xname);
if (retval < 0) {
errno = -retval;
}
return retval;
}
static int proxy_name_to_path(FsContext *ctx, V9fsPath *dir_path,
const char *name, V9fsPath *target)
{
if (dir_path) {
v9fs_path_sprintf(target, "%s/%s", dir_path->data, name);
} else {
v9fs_path_sprintf(target, "%s", name);
}
return 0;
}
static int proxy_renameat(FsContext *ctx, V9fsPath *olddir,
const char *old_name, V9fsPath *newdir,
const char *new_name)
{
int ret;
V9fsString old_full_name, new_full_name;
v9fs_string_init(&old_full_name);
v9fs_string_init(&new_full_name);
v9fs_string_sprintf(&old_full_name, "%s/%s", olddir->data, old_name);
v9fs_string_sprintf(&new_full_name, "%s/%s", newdir->data, new_name);
ret = proxy_rename(ctx, old_full_name.data, new_full_name.data);
v9fs_string_free(&old_full_name);
v9fs_string_free(&new_full_name);
return ret;
}
static int proxy_unlinkat(FsContext *ctx, V9fsPath *dir,
const char *name, int flags)
{
int ret;
V9fsString fullname;
v9fs_string_init(&fullname);
v9fs_string_sprintf(&fullname, "%s/%s", dir->data, name);
ret = proxy_remove(ctx, fullname.data);
v9fs_string_free(&fullname);
return ret;
}
static int proxy_ioc_getversion(FsContext *fs_ctx, V9fsPath *path,
mode_t st_mode, uint64_t *st_gen)
{
int err;
/* Do not try to open special files like device nodes, fifos etc
* we can get fd for regular files and directories only
*/
if (!S_ISREG(st_mode) && !S_ISDIR(st_mode)) {
errno = ENOTTY;
return -1;
}
err = v9fs_request(fs_ctx->private, T_GETVERSION, st_gen, path);
if (err < 0) {
errno = -err;
err = -1;
}
return err;
}
static int connect_namedsocket(const char *path, Error **errp)
{
int sockfd;
struct sockaddr_un helper;
if (strlen(path) >= sizeof(helper.sun_path)) {
error_setg(errp, "socket name too long");
return -1;
}
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd < 0) {
error_setg_errno(errp, errno, "failed to create client socket");
return -1;
}
strcpy(helper.sun_path, path);
helper.sun_family = AF_UNIX;
if (connect(sockfd, (struct sockaddr *)&helper, sizeof(helper)) < 0) {
error_setg_errno(errp, errno, "failed to connect to '%s'", path);
close(sockfd);
return -1;
}
/* remove the socket for security reasons */
unlink(path);
return sockfd;
}
static void error_append_socket_sockfd_hint(Error *const *errp)
{
error_append_hint(errp, "Either specify socket=/some/path where /some/path"
" points to a listening AF_UNIX socket or sock_fd=fd"
" where fd is a file descriptor to a connected AF_UNIX"
" socket\n");
}
static int proxy_parse_opts(QemuOpts *opts, FsDriverEntry *fs, Error **errp)
{
const char *socket = qemu_opt_get(opts, "socket");
const char *sock_fd = qemu_opt_get(opts, "sock_fd");
if (!socket && !sock_fd) {
error_setg(errp, "both socket and sock_fd properties are missing");
error_append_socket_sockfd_hint(errp);
return -1;
}
if (socket && sock_fd) {
error_setg(errp, "both socket and sock_fd properties are set");
error_append_socket_sockfd_hint(errp);
return -1;
}
if (socket) {
fs->path = g_strdup(socket);
fs->export_flags |= V9FS_PROXY_SOCK_NAME;
} else {
fs->path = g_strdup(sock_fd);
fs->export_flags |= V9FS_PROXY_SOCK_FD;
}
return 0;
}
static int proxy_init(FsContext *ctx, Error **errp)
{
V9fsProxy *proxy = g_new(V9fsProxy, 1);
int sock_id;
if (ctx->export_flags & V9FS_PROXY_SOCK_NAME) {
sock_id = connect_namedsocket(ctx->fs_root, errp);
} else {
sock_id = atoi(ctx->fs_root);
if (sock_id < 0) {
error_setg(errp, "socket descriptor not initialized");
}
}
if (sock_id < 0) {
g_free(proxy);
return -1;
}
g_free(ctx->fs_root);
ctx->fs_root = NULL;
proxy->in_iovec.iov_base = g_malloc(PROXY_MAX_IO_SZ + PROXY_HDR_SZ);
proxy->in_iovec.iov_len = PROXY_MAX_IO_SZ + PROXY_HDR_SZ;
proxy->out_iovec.iov_base = g_malloc(PROXY_MAX_IO_SZ + PROXY_HDR_SZ);
proxy->out_iovec.iov_len = PROXY_MAX_IO_SZ + PROXY_HDR_SZ;
ctx->private = proxy;
proxy->sockfd = sock_id;
qemu_mutex_init(&proxy->mutex);
ctx->export_flags |= V9FS_PATHNAME_FSCONTEXT;
ctx->exops.get_st_gen = proxy_ioc_getversion;
return 0;
}
static void proxy_cleanup(FsContext *ctx)
{
V9fsProxy *proxy = ctx->private;
if (!proxy) {
return;
}
g_free(proxy->out_iovec.iov_base);
g_free(proxy->in_iovec.iov_base);
if (ctx->export_flags & V9FS_PROXY_SOCK_NAME) {
close(proxy->sockfd);
}
g_free(proxy);
}
FileOperations proxy_ops = {
.parse_opts = proxy_parse_opts,
.init = proxy_init,
.cleanup = proxy_cleanup,
.lstat = proxy_lstat,
.readlink = proxy_readlink,
.close = proxy_close,
.closedir = proxy_closedir,
.open = proxy_open,
.opendir = proxy_opendir,
.rewinddir = proxy_rewinddir,
.telldir = proxy_telldir,
.readdir = proxy_readdir,
.seekdir = proxy_seekdir,
.preadv = proxy_preadv,
.pwritev = proxy_pwritev,
.chmod = proxy_chmod,
.mknod = proxy_mknod,
.mkdir = proxy_mkdir,
.fstat = proxy_fstat,
.open2 = proxy_open2,
.symlink = proxy_symlink,
.link = proxy_link,
.truncate = proxy_truncate,
.rename = proxy_rename,
.chown = proxy_chown,
.utimensat = proxy_utimensat,
.remove = proxy_remove,
.fsync = proxy_fsync,
.statfs = proxy_statfs,
.lgetxattr = proxy_lgetxattr,
.llistxattr = proxy_llistxattr,
.lsetxattr = proxy_lsetxattr,
.lremovexattr = proxy_lremovexattr,
.name_to_path = proxy_name_to_path,
.renameat = proxy_renameat,
.unlinkat = proxy_unlinkat,
};