从 fusermount 接收到的 fd 读取数据失败
Read data from fd which was received from fusermount failed
我想测试 fuse 并从 libfuse 中复制一些代码,如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#define xlog(_fmt_, ...) \
printf("3[0;33m[%s:%s:%d]3[0m " _fmt_ "\n", __FILE__, __func__, \
__LINE__, ##__VA_ARGS__)
static int receive_fd(int fd) {
struct msghdr msg;
struct iovec iov;
char buf[1];
int rv;
size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
struct cmsghdr *cmsg;
iov.iov_base = buf;
iov.iov_len = 1;
memset(&msg, 0, sizeof(msg));
msg.msg_name = 0;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = ccmsg;
msg.msg_controllen = sizeof(ccmsg);
while (((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR) {
}
if (rv == -1) {
xlog("recvmsg: %s", strerror(errno));
return -1;
}
if (!rv) {
return -1;
}
cmsg = CMSG_FIRSTHDR(&msg);
if (cmsg->cmsg_type != SCM_RIGHTS) {
xlog("got control message of unknown type %d", cmsg->cmsg_type);
return -1;
}
return *(int *)CMSG_DATA(cmsg);
}
static int fuse_mount(const char *mountpoint) {
int fds[2];
int res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
if (res != 0) {
xlog("socketpair: %s", strerror(errno));
return -1;
}
pid_t pid = fork();
if (pid == -1) {
close(fds[0]);
close(fds[1]);
return -1;
}
if (pid == 0) {
const char *argv[32];
unsigned argc = 0;
argv[argc++] = "--";
argv[argc++] = mountpoint;
argv[argc++] = NULL;
char env[16];
snprintf(env, sizeof(env), "%i", fds[0]);
setenv("_FUSE_COMMFD", env, 1);
execvp("fusermount", (char **)argv);
xlog("exec: %s", strerror(errno));
exit(1);
}
int rv = receive_fd(fds[1]);
if (rv >= 0) {
fcntl(rv, F_SETFD, FD_CLOEXEC);
}
waitpid(pid, NULL, 0);
close(fds[0]);
close(fds[1]);
return rv;
}
#define MOUNT_POINT "/tmp/xx"
#define BUFFER_SIZE 1024
int main(int argc, char **argv) {
int fd = fuse_mount(MOUNT_POINT);
if (fd < 0) {
xlog("fuse mount fail");
return -1;
}
char buf[BUFFER_SIZE];
ssize_t n = read(fd, buf, sizeof(buf));
if (n < 0) {
xlog("read: %s", strerror(errno));
} else {
xlog("ok");
}
return 0;
}
它从 fusermount
接收到 /dev/fuse
文件句柄并从中读取数据。
代码 运行 在大多数平台上都可以,但是当我在 virtualbox 上 运行 它时,我在第 107 行收到错误 read: Invalid argument
:
ssize_t n = read(fd, buf, sizeof(buf));
然后我检查了libfuse的代码,它设置缓冲区大小是这样的:
se->bufsize = FUSE_MAX_MAX_PAGES * getpagesize() + FUSE_BUFFER_HEADER_SIZE;
所以我在我的代码中设置了一个更大的 BUFFER_SIZE
,比如 10240
,运行 没问题。我感到困惑,因为我认为 1024
足够大,而我从 fd 读取的数据大小只有 56
.
我的问题在系统调用中:
ssize_t read(int fd, void *buf, size_t count);
- 为什么count的大小会导致
Invalid argument
错误?
- 为什么我在普通平台上设置count为
1024
可以,但是在virtualbox上却失败了?
感谢@user253751 的提示,我阅读了代码 fs/fuse/dev.c
并发现了这个:
static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file,
struct fuse_copy_state *cs, size_t nbytes) {
// ...
if (nbytes < max_t(size_t, FUSE_MIN_READ_BUFFER,
sizeof(struct fuse_in_header) +
sizeof(struct fuse_write_in) + fc->max_write))
return -EINVAL;
// ...
}
自linux v5.4 起添加了 nbytes 检查,因此如果内核版本高于 v5.3.18,小的 nbytes 将导致 EINVAL
错误。
我想测试 fuse 并从 libfuse 中复制一些代码,如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#define xlog(_fmt_, ...) \
printf("3[0;33m[%s:%s:%d]3[0m " _fmt_ "\n", __FILE__, __func__, \
__LINE__, ##__VA_ARGS__)
static int receive_fd(int fd) {
struct msghdr msg;
struct iovec iov;
char buf[1];
int rv;
size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
struct cmsghdr *cmsg;
iov.iov_base = buf;
iov.iov_len = 1;
memset(&msg, 0, sizeof(msg));
msg.msg_name = 0;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = ccmsg;
msg.msg_controllen = sizeof(ccmsg);
while (((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR) {
}
if (rv == -1) {
xlog("recvmsg: %s", strerror(errno));
return -1;
}
if (!rv) {
return -1;
}
cmsg = CMSG_FIRSTHDR(&msg);
if (cmsg->cmsg_type != SCM_RIGHTS) {
xlog("got control message of unknown type %d", cmsg->cmsg_type);
return -1;
}
return *(int *)CMSG_DATA(cmsg);
}
static int fuse_mount(const char *mountpoint) {
int fds[2];
int res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
if (res != 0) {
xlog("socketpair: %s", strerror(errno));
return -1;
}
pid_t pid = fork();
if (pid == -1) {
close(fds[0]);
close(fds[1]);
return -1;
}
if (pid == 0) {
const char *argv[32];
unsigned argc = 0;
argv[argc++] = "--";
argv[argc++] = mountpoint;
argv[argc++] = NULL;
char env[16];
snprintf(env, sizeof(env), "%i", fds[0]);
setenv("_FUSE_COMMFD", env, 1);
execvp("fusermount", (char **)argv);
xlog("exec: %s", strerror(errno));
exit(1);
}
int rv = receive_fd(fds[1]);
if (rv >= 0) {
fcntl(rv, F_SETFD, FD_CLOEXEC);
}
waitpid(pid, NULL, 0);
close(fds[0]);
close(fds[1]);
return rv;
}
#define MOUNT_POINT "/tmp/xx"
#define BUFFER_SIZE 1024
int main(int argc, char **argv) {
int fd = fuse_mount(MOUNT_POINT);
if (fd < 0) {
xlog("fuse mount fail");
return -1;
}
char buf[BUFFER_SIZE];
ssize_t n = read(fd, buf, sizeof(buf));
if (n < 0) {
xlog("read: %s", strerror(errno));
} else {
xlog("ok");
}
return 0;
}
它从 fusermount
接收到 /dev/fuse
文件句柄并从中读取数据。
代码 运行 在大多数平台上都可以,但是当我在 virtualbox 上 运行 它时,我在第 107 行收到错误 read: Invalid argument
:
ssize_t n = read(fd, buf, sizeof(buf));
然后我检查了libfuse的代码,它设置缓冲区大小是这样的:
se->bufsize = FUSE_MAX_MAX_PAGES * getpagesize() + FUSE_BUFFER_HEADER_SIZE;
所以我在我的代码中设置了一个更大的 BUFFER_SIZE
,比如 10240
,运行 没问题。我感到困惑,因为我认为 1024
足够大,而我从 fd 读取的数据大小只有 56
.
我的问题在系统调用中:
ssize_t read(int fd, void *buf, size_t count);
- 为什么count的大小会导致
Invalid argument
错误? - 为什么我在普通平台上设置count为
1024
可以,但是在virtualbox上却失败了?
感谢@user253751 的提示,我阅读了代码 fs/fuse/dev.c
并发现了这个:
static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file,
struct fuse_copy_state *cs, size_t nbytes) {
// ...
if (nbytes < max_t(size_t, FUSE_MIN_READ_BUFFER,
sizeof(struct fuse_in_header) +
sizeof(struct fuse_write_in) + fc->max_write))
return -EINVAL;
// ...
}
自linux v5.4 起添加了 nbytes 检查,因此如果内核版本高于 v5.3.18,小的 nbytes 将导致 EINVAL
错误。