fusefs: handle ENOSYS for FUSE_INTERRUPT

Though it's not documented, Linux will interpret a FUSE_INTERRUPT response
of ENOSYS as "the file system does not support FUSE_INTERRUPT".
Subsequently it will never send FUSE_INTERRUPT again to the same mount
point.  This change matches Linux's behavior.

PR:		346357
Sponsored by:	The FreeBSD Foundation
This commit is contained in:
Alan Somers 2019-04-24 17:30:50 +00:00
parent ebbfe00ec2
commit 102c7ac083
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/projects/fuse2/; revision=346642
2 changed files with 125 additions and 53 deletions

View File

@ -130,16 +130,13 @@ static uma_zone_t ticket_zone;
/*
* TODO: figure out how to timeout INTERRUPT requests, because the daemon may
* leagally never respond
*
* TODO: remove an INTERRUPT request if the daemon responds to the original
*/
static int
fuse_interrupt_callback(struct fuse_ticket *tick, struct uio *uio)
{
struct fuse_ticket *otick, *x_tick;
struct fuse_interrupt_in *fii;
struct fuse_data *data;
data = tick->tk_data;
struct fuse_data *data = tick->tk_data;
bool found = false;
fii = (struct fuse_interrupt_in*)((char*)tick->tk_ms_fiov.base +
@ -162,7 +159,10 @@ fuse_interrupt_callback(struct fuse_ticket *tick, struct uio *uio)
/* Clear the original ticket's interrupt association */
otick->irq_unique = 0;
if (tick->tk_aw_ohead.error == EAGAIN) {
if (tick->tk_aw_ohead.error == ENOSYS) {
fsess_set_notimpl(data->mp, FUSE_INTERRUPT);
return 0;
} else if (tick->tk_aw_ohead.error == EAGAIN) {
/*
* There are two reasons we might get this:
* 1) the daemon received the INTERRUPT request before the
@ -220,6 +220,13 @@ fuse_interrupt_send(struct fuse_ticket *otick, int err)
}
fuse_lck_mtx_unlock(data->ms_mtx);
/*
* If the fuse daemon doesn't support interrupts, then there's
* nothing more that we can do
*/
if (!fsess_isimpl(data->mp, FUSE_INTERRUPT))
return;
/*
* If the fuse daemon has already received otick, then we must
* send FUSE_INTERRUPT.

View File

@ -49,6 +49,8 @@ const off_t FILESIZE = 1000;
const mode_t MODE = 0755;
const char FULLDIRPATH0[] = "mountpoint/some_dir";
const char RELDIRPATH0[] = "some_dir";
const char FULLDIRPATH1[] = "mountpoint/other_dir";
const char RELDIRPATH1[] = "other_dir";
static sem_t *signaled_semaphore;
@ -163,6 +165,29 @@ void TearDown() {
}
};
static void* mkdir0(void* arg __unused) {
ssize_t r;
r = mkdir(FULLDIRPATH0, MODE);
if (r >= 0)
return 0;
else
return (void*)(intptr_t)errno;
}
static void* read1(void* arg) {
const size_t bufsize = FILESIZE;
char buf[bufsize];
int fd = (int)(intptr_t)arg;
ssize_t r;
r = read(fd, buf, bufsize);
if (r >= 0)
return 0;
else
return (void*)(intptr_t)errno;
}
/*
* An interrupt operation that gets received after the original command is
* complete should generate an EAGAIN response.
@ -205,6 +230,89 @@ TEST_F(Interrupt, already_complete)
EXPECT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
}
/*
* If a FUSE file system returns ENOSYS for a FUSE_INTERRUPT operation, the
* kernel should not attempt to interrupt any other operations on that mount
* point.
*/
TEST_F(Interrupt, enosys)
{
uint64_t ino0 = 42, ino1 = 43;;
uint64_t mkdir_unique;
pthread_t self, th0;
sem_t sem0, sem1;
void *thr0_value;
Sequence seq;
self = pthread_self();
ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
EXPECT_LOOKUP(1, RELDIRPATH1).WillOnce(Invoke(ReturnErrno(ENOENT)));
EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_mkdir(&mkdir_unique);
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
return (in->header.opcode == FUSE_INTERRUPT &&
in->body.interrupt.unique == mkdir_unique);
}, Eq(true)),
_)
).InSequence(seq)
.WillOnce(Invoke([&](auto in, auto &out) {
// reject FUSE_INTERRUPT and respond to the FUSE_WRITE
auto out0 = new mockfs_buf_out;
auto out1 = new mockfs_buf_out;
out0->header.unique = in->header.unique;
out0->header.error = -ENOSYS;
out0->header.len = sizeof(out0->header);
out.push_back(out0);
SET_OUT_HEADER_LEN(out1, entry);
out1->body.create.entry.attr.mode = S_IFDIR | MODE;
out1->body.create.entry.nodeid = ino1;
out1->header.unique = mkdir_unique;
out.push_back(out1);
}));
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
return (in->header.opcode == FUSE_MKDIR);
}, Eq(true)),
_)
).InSequence(seq)
.WillOnce(Invoke([&](auto in, auto &out) {
auto out0 = new mockfs_buf_out;
sem_post(&sem0);
sem_wait(&sem1);
SET_OUT_HEADER_LEN(out0, entry);
out0->body.create.entry.attr.mode = S_IFDIR | MODE;
out0->body.create.entry.nodeid = ino0;
out0->header.unique = in->header.unique;
out.push_back(out0);
}));
setup_interruptor(self);
/* First mkdir operation should finish synchronously */
ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
<< strerror(errno);
sem_wait(&sem0);
/*
* th0 should be blocked waiting for the fuse daemon thread.
* Signal it. No FUSE_INTERRUPT should result
*/
pthread_kill(th0, SIGUSR1);
/* Allow the daemon thread to proceed */
sem_post(&sem1);
pthread_join(th0, &thr0_value);
/* Second mkdir should've finished without error */
EXPECT_EQ(0, (intptr_t)thr0_value);
}
/*
* Upon receipt of a fatal signal, fusefs should return ASAP after sending
* FUSE_INTERRUPT.
@ -279,7 +387,7 @@ TEST_F(Interrupt, ignore)
}, Eq(true)),
_)
).WillOnce(Invoke([&](auto in __unused, auto &out) {
// Ignore FUSE_INTERRUPT; respond to the FUSE_WRITE
// Ignore FUSE_INTERRUPT; respond to the FUSE_MKDIR
auto out0 = new mockfs_buf_out;
out0->header.unique = mkdir_unique;
SET_OUT_HEADER_LEN(out0, entry);
@ -292,42 +400,6 @@ TEST_F(Interrupt, ignore)
ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
}
void* mkdir0(void* arg __unused) {
ssize_t r;
r = mkdir(FULLDIRPATH0, MODE);
if (r >= 0)
return 0;
else
return (void*)(intptr_t)errno;
}
void* setxattr0(void* arg) {
const char *CONTENTS = "abcdefgh";
ssize_t bufsize = strlen(CONTENTS);
int fd = (int)(intptr_t)arg;
ssize_t r;
r = write(fd, CONTENTS, bufsize);
if (r >= 0)
return 0;
else
return (void*)(intptr_t)errno;
}
void* read1(void* arg) {
const size_t bufsize = FILESIZE;
char buf[bufsize];
int fd = (int)(intptr_t)arg;
ssize_t r;
r = read(fd, buf, bufsize);
if (r >= 0)
return 0;
else
return (void*)(intptr_t)errno;
}
/*
* A restartable operation (basically, anything except write or setextattr)
* that hasn't yet been sent to userland can be interrupted without sending
@ -536,15 +608,11 @@ TEST_F(Interrupt, in_progress_read)
setup_interruptor(self);
ASSERT_EQ(-1, read(fd, buf, bufsize));
EXPECT_EQ(EINTR, errno);
/* Deliberately leak fd. close(2) will be tested in release.cc */
}
/* FUSE_INTERRUPT operations should take priority over other pending ops */
TEST_F(Interrupt, priority)
{
const char FULLPATH1[] = "mountpoint/other_dir";
const char RELPATH1[] = "other_dir";
Sequence seq;
uint64_t ino1 = 43;
uint64_t mkdir_unique;
@ -556,8 +624,7 @@ TEST_F(Interrupt, priority)
self = pthread_self();
EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
EXPECT_LOOKUP(1, RELPATH1).WillOnce(Invoke(ReturnErrno(ENOENT)));
//expect_mkdir(&mkdir_unique);
EXPECT_LOOKUP(1, RELDIRPATH1).WillOnce(Invoke(ReturnErrno(ENOENT)));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_MKDIR);
@ -579,8 +646,8 @@ TEST_F(Interrupt, priority)
out->header.len = sizeof(out->header);
})));
/*
* FUSE_INTERRUPT should be received before the second FUSE_MKDIR, even
* though it was generated later
* FUSE_INTERRUPT should be received before the second FUSE_MKDIR,
* even though it was generated later
*/
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
@ -610,7 +677,7 @@ TEST_F(Interrupt, priority)
sem_wait(&sem1); /* Sequence the two mkdirs */
setup_interruptor(th0);
ASSERT_EQ(0, mkdir(FULLPATH1, MODE)) << strerror(errno);
ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
/* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
usleep(250'000);
@ -669,8 +736,6 @@ TEST_F(Interrupt, too_soon)
setup_interruptor(self);
ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
EXPECT_EQ(EINTR, errno);
/* Deliberately leak fd. close(2) will be tested in release.cc */
}