From owner-svn-src-projects@freebsd.org Wed Apr 24 17:30:52 2019 Return-Path: Delivered-To: svn-src-projects@mailman.ysv.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.ysv.freebsd.org (Postfix) with ESMTP id 0A656159E954 for ; Wed, 24 Apr 2019 17:30:52 +0000 (UTC) (envelope-from asomers@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (4096 bits) client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id A3F916FDC7; Wed, 24 Apr 2019 17:30:51 +0000 (UTC) (envelope-from asomers@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 7DE28F6B5; Wed, 24 Apr 2019 17:30:51 +0000 (UTC) (envelope-from asomers@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id x3OHUpIm075581; Wed, 24 Apr 2019 17:30:51 GMT (envelope-from asomers@FreeBSD.org) Received: (from asomers@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id x3OHUpbN075579; Wed, 24 Apr 2019 17:30:51 GMT (envelope-from asomers@FreeBSD.org) Message-Id: <201904241730.x3OHUpbN075579@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: asomers set sender to asomers@FreeBSD.org using -f From: Alan Somers Date: Wed, 24 Apr 2019 17:30:51 +0000 (UTC) To: src-committers@freebsd.org, svn-src-projects@freebsd.org Subject: svn commit: r346642 - in projects/fuse2: sys/fs/fuse tests/sys/fs/fusefs X-SVN-Group: projects X-SVN-Commit-Author: asomers X-SVN-Commit-Paths: in projects/fuse2: sys/fs/fuse tests/sys/fs/fusefs X-SVN-Commit-Revision: 346642 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Rspamd-Queue-Id: A3F916FDC7 X-Spamd-Bar: -- Authentication-Results: mx1.freebsd.org X-Spamd-Result: default: False [-2.98 / 15.00]; local_wl_from(0.00)[FreeBSD.org]; NEURAL_HAM_MEDIUM(-1.00)[-1.000,0]; NEURAL_HAM_SHORT(-0.98)[-0.982,0]; ASN(0.00)[asn:11403, ipnet:2610:1c1:1::/48, country:US]; NEURAL_HAM_LONG(-1.00)[-1.000,0] X-BeenThere: svn-src-projects@freebsd.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "SVN commit messages for the src " projects" tree" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 24 Apr 2019 17:30:52 -0000 Author: asomers Date: Wed Apr 24 17:30:50 2019 New Revision: 346642 URL: https://svnweb.freebsd.org/changeset/base/346642 Log: 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 Modified: projects/fuse2/sys/fs/fuse/fuse_ipc.c projects/fuse2/tests/sys/fs/fusefs/interrupt.cc Modified: projects/fuse2/sys/fs/fuse/fuse_ipc.c ============================================================================== --- projects/fuse2/sys/fs/fuse/fuse_ipc.c Wed Apr 24 16:03:35 2019 (r346641) +++ projects/fuse2/sys/fs/fuse/fuse_ipc.c Wed Apr 24 17:30:50 2019 (r346642) @@ -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, stru /* 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 @@ -219,6 +219,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 Modified: projects/fuse2/tests/sys/fs/fusefs/interrupt.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fusefs/interrupt.cc Wed Apr 24 16:03:35 2019 (r346641) +++ projects/fuse2/tests/sys/fs/fusefs/interrupt.cc Wed Apr 24 17:30:50 2019 (r346642) @@ -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. @@ -206,6 +231,89 @@ TEST_F(Interrupt, already_complete) } /* + * 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 */ }