fusefs: don't send FUSE_INTERRUPT for ops that are still in-kernel

If a pending FUSE operation hasn't yet been sent to the daemon, then there's
no reason to inform the daemon that it's been interrupted.  Instead, simply
remove it from the fuse message queue and set its status to EINTR or
ERESTART as appropriate.

PR:		346357
Sponsored by:	The FreeBSD Foundation
This commit is contained in:
Alan Somers 2019-04-19 15:05:32 +00:00
parent a154214620
commit 3d070fdc76
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/projects/fuse2/; revision=346387
2 changed files with 126 additions and 9 deletions

View File

@ -95,7 +95,7 @@ SDT_PROBE_DEFINE2(fuse, , ipc, trace, "int", "char*");
static void fdisp_make_pid(struct fuse_dispatcher *fdip, enum fuse_opcode op,
struct fuse_data *data, uint64_t nid, pid_t pid, struct ucred *cred);
static void fiov_clear(struct fuse_iov *fiov);
static void fuse_interrupt_send(struct fuse_ticket *otick);
static void fuse_interrupt_send(struct fuse_ticket *otick, int err);
static struct fuse_ticket *fticket_alloc(struct fuse_data *data);
static void fticket_refresh(struct fuse_ticket *ftick);
static void fticket_destroy(struct fuse_ticket *ftick);
@ -175,7 +175,7 @@ fuse_interrupt_callback(struct fuse_ticket *tick, struct uio *uio)
* second, we should ignore it.
*/
/* Resend */
fuse_interrupt_send(otick);
fuse_interrupt_send(otick, EINTR);
return 0;
} else {
/* Illegal FUSE_INTERRUPT response */
@ -183,16 +183,49 @@ fuse_interrupt_callback(struct fuse_ticket *tick, struct uio *uio)
}
}
/* Interrupt the operation otick. Return err as its error code */
void
fuse_interrupt_send(struct fuse_ticket *otick)
fuse_interrupt_send(struct fuse_ticket *otick, int err)
{
struct fuse_dispatcher fdi;
struct fuse_interrupt_in *fii;
struct fuse_in_header *ftick_hdr;
struct fuse_data *data = otick->tk_data;
struct fuse_ticket *tick, *xtick;
struct ucred reused_creds;
if (otick->irq_unique == 0) {
/*
* If the daemon hasn't yet received otick, then we can answer
* it ourselves and return.
*/
fuse_lck_mtx_lock(data->ms_mtx);
STAILQ_FOREACH_SAFE(tick, &otick->tk_data->ms_head, tk_ms_link,
xtick) {
if (tick == otick) {
STAILQ_REMOVE(&otick->tk_data->ms_head, tick,
fuse_ticket, tk_ms_link);
otick->tk_ms_link.stqe_next = NULL;
fuse_lck_mtx_unlock(data->ms_mtx);
fuse_lck_mtx_lock(otick->tk_aw_mtx);
if (!fticket_answered(otick)) {
fticket_set_answered(otick);
otick->tk_aw_errno = err;
wakeup(otick);
}
fuse_lck_mtx_unlock(otick->tk_aw_mtx);
fuse_ticket_drop(tick);
return;
}
}
fuse_lck_mtx_unlock(data->ms_mtx);
/*
* If the fuse daemon has already received otick, then we must
* send FUSE_INTERRUPT.
*/
ftick_hdr = fticket_in_header(otick);
reused_creds.cr_uid = ftick_hdr->uid;
reused_creds.cr_rgid = ftick_hdr->gid;
@ -400,11 +433,12 @@ fticket_wait_answer(struct fuse_ticket *ftick)
sigset_t blockedset, oldset;
int err = 0;
struct fuse_data *data;
SIGEMPTYSET(blockedset);
kern_sigprocmask(td, SIG_BLOCK, NULL, &oldset, 0);
fuse_lck_mtx_lock(ftick->tk_aw_mtx);
SIGEMPTYSET(blockedset);
kern_sigprocmask(td, SIG_BLOCK, &blockedset, &oldset, 0);
retry:
if (fticket_answered(ftick)) {
goto out;
@ -416,6 +450,7 @@ fticket_wait_answer(struct fuse_ticket *ftick)
fticket_set_answered(ftick);
goto out;
}
kern_sigprocmask(td, SIG_BLOCK, &blockedset, NULL, 0);
err = msleep(ftick, &ftick->tk_aw_mtx, PCATCH, "fu_ans",
data->daemon_timeout * hz);
kern_sigprocmask(td, SIG_SETMASK, &oldset, NULL, 0);
@ -446,22 +481,21 @@ fticket_wait_answer(struct fuse_ticket *ftick)
SDT_PROBE2(fuse, , ipc, trace, 4,
"fticket_wait_answer: interrupt");
fuse_lck_mtx_unlock(ftick->tk_aw_mtx);
fuse_interrupt_send(ftick);
fuse_lck_mtx_lock(ftick->tk_aw_mtx);
fuse_interrupt_send(ftick, err);
PROC_LOCK(td->td_proc);
mtx_lock(&td->td_proc->p_sigacts->ps_mtx);
sig = cursig(td);
mtx_unlock(&td->td_proc->p_sigacts->ps_mtx);
PROC_UNLOCK(td->td_proc);
fuse_lck_mtx_lock(ftick->tk_aw_mtx);
if (!sig_isfatal(td->td_proc, sig)) {
/*
* Block the just-delivered signal while we wait for an
* interrupt response
*/
SIGADDSET(blockedset, sig);
kern_sigprocmask(curthread, SIG_BLOCK, &blockedset,
NULL, 0);
goto retry;
} else {
/* Return immediately for fatal signals */

View File

@ -32,6 +32,7 @@ extern "C" {
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
}
@ -292,6 +293,88 @@ TEST_F(Interrupt, ignore)
/* Deliberately leak fd. close(2) will be tested in release.cc */
}
void* write0(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;
}
/*
* An operation that hasn't yet been sent to userland can be interrupted
* without sending FUSE_INTERRUPT
*/
TEST_F(Interrupt, in_kernel)
{
const char FULLPATH0[] = "mountpoint/some_file.txt";
const char RELPATH0[] = "some_file.txt";
const char FULLPATH1[] = "mountpoint/other_file.txt";
const char RELPATH1[] = "other_file.txt";
const char *CONTENTS = "ijklmnop";
ssize_t bufsize = strlen(CONTENTS);
uint64_t ino0 = 42, ino1 = 43;
int fd0, fd1;
pthread_t self, th0;
sem_t sem0, sem1;
void *thr0_value;
ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
self = pthread_self();
expect_lookup(RELPATH0, ino0);
expect_open(ino0, 0, 1);
expect_lookup(RELPATH1, ino1);
expect_open(ino1, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_WRITE &&
in->header.nodeid == ino0);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([&](auto in, auto out) {
/* Let the next write proceed */
sem_post(&sem1);
/* Pause the daemon thread so it won't read the next op */
sem_wait(&sem0);
SET_OUT_HEADER_LEN(out, write);
out->body.write.size = in->body.write.size;
})));
fd0 = open(FULLPATH0, O_WRONLY);
ASSERT_LE(0, fd0) << strerror(errno);
fd1 = open(FULLPATH1, O_WRONLY);
ASSERT_LE(0, fd1) << strerror(errno);
/* Use a separate thread for the first write */
ASSERT_EQ(0, pthread_create(&th0, NULL, write0, (void*)(intptr_t)fd0))
<< strerror(errno);
setup_interruptor(self);
sem_wait(&sem1); /* Sequence the two writes */
ASSERT_EQ(-1, write(fd1, CONTENTS, bufsize));
EXPECT_EQ(EINTR, errno);
/* Unstick the daemon */
ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
/* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
usleep(250'000);
pthread_join(th0, &thr0_value);
EXPECT_EQ(0, (intptr_t)thr0_value);
sem_destroy(&sem1);
sem_destroy(&sem0);
}
/*
* A syscall that gets interrupted while blocking on FUSE I/O should send a
* FUSE_INTERRUPT command to the fuse filesystem, which should then send EINTR