【问题标题】:Read data from fd which was received from fusermount failed从 fusermount 接收的 fd 读取数据失败
【发布时间】:2020-05-21 01:40:53
【问题描述】:

我想测试 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("\033[0;33m[%s:%s:%d]\033[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);
  1. 为什么count的大小会导致Invalid argument错误?
  2. 为什么我在普通平台上将 count 设置为 1024 可以,但在 virtualbox 上却失败了?

【问题讨论】:

  • 你可以找到实现read for /dev/fuse的内核代码。

标签: c linux fuse libfuse


【解决方案1】:

感谢@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错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-06-15
    • 2011-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-10
    • 2013-03-18
    相关资源
    最近更新 更多