fusefs: allow opening files O_EXEC

O_EXEC is useful for fexecve(2) and fchdir(2).  Treat it as another fufh
type alongside the existing RDONLY, WRONLY, and RDWR.  Prior to r345742 this
would've caused a memory and performance penalty.

PR:		236329
Sponsored by:	The FreeBSD Foundation
This commit is contained in:
Alan Somers 2019-04-01 16:36:02 +00:00
parent 4a6d5507f7
commit 363a74163b
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/projects/fuse2/; revision=345768
9 changed files with 87 additions and 65 deletions

View File

@ -121,12 +121,8 @@ fuse_filehandle_open(struct vnode *vp, fufh_type_t fufh_type,
if (vnode_isdir(vp)) {
op = FUSE_OPENDIR;
if (fufh_type != FUFH_RDONLY) {
SDT_PROBE2(fuse, , file, trace, 1,
"non-rdonly fh requested for a directory?");
printf("FUSE:non-rdonly fh requested for a directory?\n");
fufh_type = FUFH_RDONLY;
}
/* vn_open_vnode already rejects FWRITE on directories */
MPASS(fufh_type == FUFH_RDONLY || fufh_type == FUFH_EXEC);
}
fdisp_init(&fdi, sizeof(*foi));
fdisp_make_vp(&fdi, op, vp, td, cred);

View File

@ -72,14 +72,11 @@
*/
typedef enum fufh_type {
FUFH_INVALID = -1,
FUFH_RDONLY = 0,
FUFH_WRONLY = 1,
FUFH_RDWR = 2,
/* TODO: add FUFH_EXEC */
FUFH_RDONLY = O_RDONLY,
FUFH_WRONLY = O_WRONLY,
FUFH_RDWR = O_RDWR,
FUFH_EXEC = O_EXEC,
} fufh_type_t;
_Static_assert(FUFH_RDONLY == O_RDONLY, "RDONLY");
_Static_assert(FUFH_WRONLY == O_WRONLY, "WRONLY");
_Static_assert(FUFH_RDWR == O_RDWR, "RDWR");
struct fuse_filehandle {
LIST_ENTRY(fuse_filehandle) next;
@ -110,6 +107,8 @@ fuse_filehandle_xlate_from_fflags(int fflags)
return FUFH_WRONLY;
else if (fflags & (FREAD))
return FUFH_RDONLY;
else if (fflags & (FEXEC))
return FUFH_EXEC;
else
panic("FUSE: What kind of a flag is this (%x)?", fflags);
}
@ -123,6 +122,7 @@ fuse_filehandle_xlate_to_oflags(fufh_type_t type)
case FUFH_RDONLY:
case FUFH_WRONLY:
case FUFH_RDWR:
case FUFH_EXEC:
oflags = type;
break;
default:

View File

@ -285,7 +285,8 @@ fuse_vnop_close(struct vop_close_args *ap)
if (vnode_isdir(vp)) {
struct fuse_filehandle *fufh;
if (fuse_filehandle_get(vp, O_RDONLY, &fufh) == 0)
if ((fuse_filehandle_get(vp, O_RDONLY, &fufh) == 0) ||
(fuse_filehandle_get(vp, O_EXEC, &fufh) == 0))
fuse_filehandle_close(vp, fufh, NULL, cred);
return 0;
}
@ -1201,16 +1202,12 @@ fuse_vnop_open(struct vop_open_args *ap)
return ENXIO;
if (vp->v_type == VCHR || vp->v_type == VBLK || vp->v_type == VFIFO)
return (EOPNOTSUPP);
if ((mode & (FREAD | FWRITE)) == 0)
if ((mode & (FREAD | FWRITE | FEXEC)) == 0)
return EINVAL;
fvdat = VTOFUD(vp);
if (vnode_isdir(vp)) {
fufh_type = FUFH_RDONLY;
} else {
fufh_type = fuse_filehandle_xlate_from_fflags(mode);
}
fufh_type = fuse_filehandle_xlate_from_fflags(mode);
if (fuse_filehandle_validrw(vp, fufh_type) != FUFH_INVALID) {
fuse_vnode_open(vp, 0, td);
@ -1303,7 +1300,7 @@ fuse_vnop_readdir(struct vop_readdir_args *ap)
return EINVAL;
}
if ((err = fuse_filehandle_get(vp, O_RDONLY, &fufh)) != 0) {
if ((err = fuse_filehandle_get(vp, FUFH_RDONLY, &fufh)) != 0) {
SDT_PROBE2(fuse, , vnops, trace, 1,
"calling readdir() before open()");
err = fuse_filehandle_open(vp, O_RDONLY, &fufh, NULL, cred);

View File

@ -200,7 +200,8 @@ void debug_fuseop(const mockfs_buf_in *in)
in->body.read.size);
break;
case FUSE_READDIR:
printf(" offset=%lu size=%u", in->body.readdir.offset,
printf(" fh=%#lx offset=%lu size=%u",
in->body.readdir.fh, in->body.readdir.offset,
in->body.readdir.size);
break;
case FUSE_RELEASE:

View File

@ -250,8 +250,7 @@ TEST_F(Open, o_excl)
test_ok(O_WRONLY | O_EXCL, O_WRONLY);
}
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236329 */
TEST_F(Open, DISABLED_o_exec)
TEST_F(Open, o_exec)
{
test_ok(O_EXEC, O_EXEC);
}

View File

@ -44,6 +44,29 @@ void expect_lookup(const char *relpath, uint64_t ino)
{
FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 0, 1);
}
void expect_opendir(uint64_t ino, uint32_t flags, ProcessMockerT r)
{
/* opendir(3) calls fstatfs */
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
return (in->header.opcode == FUSE_STATFS);
}, Eq(true)),
_)
).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
SET_OUT_HEADER_LEN(out, statfs);
})));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_OPENDIR &&
in->header.nodeid == ino &&
in->body.opendir.flags == flags);
}, Eq(true)),
_)
).WillOnce(Invoke(r));
}
};
@ -59,14 +82,8 @@ TEST_F(Opendir, enoent)
uint64_t ino = 42;
expect_lookup(RELPATH, ino);
expect_opendir(ino, O_RDONLY, ReturnErrno(ENOENT));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_OPENDIR &&
in->header.nodeid == ino);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnErrno(ENOENT)));
EXPECT_NE(0, open(FULLPATH, O_DIRECTORY));
EXPECT_EQ(ENOENT, errno);
}
@ -82,14 +99,7 @@ TEST_F(Opendir, eperm)
uint64_t ino = 42;
expect_lookup(RELPATH, ino);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_OPENDIR &&
in->header.nodeid == ino);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnErrno(EPERM)));
expect_opendir(ino, O_RDONLY, ReturnErrno(EPERM));
EXPECT_NE(0, open(FULLPATH, O_DIRECTORY));
EXPECT_EQ(EPERM, errno);
@ -102,20 +112,32 @@ TEST_F(Opendir, open)
uint64_t ino = 42;
expect_lookup(RELPATH, ino);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_OPENDIR &&
in->header.nodeid == ino);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
expect_opendir(ino, O_RDONLY,
ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, open);
})));
}));
EXPECT_LE(0, open(FULLPATH, O_DIRECTORY)) << strerror(errno);
}
/* Directories can be opened O_EXEC for stuff like fchdir(2) */
TEST_F(Opendir, open_exec)
{
const char FULLPATH[] = "mountpoint/some_dir";
const char RELPATH[] = "some_dir";
uint64_t ino = 42;
int fd;
expect_lookup(RELPATH, ino);
expect_opendir(ino, O_EXEC,
ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, open);
}));
fd = open(FULLPATH, O_EXEC | O_DIRECTORY);
ASSERT_LE(0, fd) << strerror(errno);
}
TEST_F(Opendir, opendir)
{
const char FULLPATH[] = "mountpoint/some_dir";
@ -123,24 +145,10 @@ TEST_F(Opendir, opendir)
uint64_t ino = 42;
expect_lookup(RELPATH, ino);
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
return (in->header.opcode == FUSE_STATFS);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, statfs);
})));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_OPENDIR &&
in->header.nodeid == ino);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
expect_opendir(ino, O_RDONLY,
ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, open);
})));
}));
errno = 0;
EXPECT_NE(NULL, opendir(FULLPATH)) << strerror(errno);

View File

@ -52,6 +52,7 @@ void expect_readdir(uint64_t ino, uint64_t off, vector<struct dirent> &ents)
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_READDIR &&
in->header.nodeid == ino &&
in->body.readdir.fh == FH &&
in->body.readdir.offset == off);
}, Eq(true)),
_)

View File

@ -30,6 +30,7 @@
extern "C" {
#include <dirent.h>
#include <fcntl.h>
}
#include "mockfs.hh"
@ -107,3 +108,21 @@ TEST_F(ReleaseDir, ok)
ASSERT_EQ(0, closedir(dir)) << strerror(errno);
}
/* Directories opened O_EXEC should be properly released, too */
TEST_F(ReleaseDir, o_exec)
{
const char FULLPATH[] = "mountpoint/some_dir";
const char RELPATH[] = "some_dir";
uint64_t ino = 42;
int fd;
expect_lookup(RELPATH, ino);
expect_opendir(ino);
expect_releasedir(ino, ReturnErrno(0));
fd = open(FULLPATH, O_EXEC | O_DIRECTORY);
EXPECT_LE(0, fd) << strerror(errno);
ASSERT_EQ(0, close(fd)) << strerror(errno);
}

View File

@ -166,6 +166,7 @@ void FuseTest::expect_open(uint64_t ino, uint32_t flags, int times)
void FuseTest::expect_opendir(uint64_t ino)
{
/* opendir(3) calls fstatfs */
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
return (in->header.opcode == FUSE_STATFS);