From owner-svn-src-projects@freebsd.org Tue Sep 3 14:07:05 2019 Return-Path: Delivered-To: svn-src-projects@mailman.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.nyi.freebsd.org (Postfix) with ESMTP id EC1B2DD47B for ; Tue, 3 Sep 2019 14:07:05 +0000 (UTC) (envelope-from yuripv@freebsd.org) Received: from freefall.freebsd.org (freefall.freebsd.org [96.47.72.132]) (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 "freefall.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 46N809480Tz4QDX; Tue, 3 Sep 2019 14:07:05 +0000 (UTC) (envelope-from yuripv@freebsd.org) Received: by freefall.freebsd.org (Postfix, from userid 1452) id 03D861B088; Tue, 3 Sep 2019 14:06:28 +0000 (UTC) X-Original-To: yuripv@localmail.freebsd.org Delivered-To: yuripv@localmail.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [96.47.72.80]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) (Client CN "mx1.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by freefall.freebsd.org (Postfix) with ESMTPS id 0E8A9D12C; Thu, 18 Apr 2019 19:16:46 +0000 (UTC) (envelope-from owner-src-committers@freebsd.org) Received: from freefall.freebsd.org (freefall.freebsd.org [96.47.72.132]) (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 "freefall.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 1FA63759A2; Thu, 18 Apr 2019 19:16:44 +0000 (UTC) (envelope-from owner-src-committers@freebsd.org) Received: by freefall.freebsd.org (Postfix, from userid 538) id 0DAF5D12A; Thu, 18 Apr 2019 19:16:40 +0000 (UTC) Delivered-To: src-committers@localmail.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) (Client CN "mx1.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by freefall.freebsd.org (Postfix) with ESMTPS id E628DD063 for ; Thu, 18 Apr 2019 19:16:36 +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 A1D8575903; Thu, 18 Apr 2019 19:16:36 +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 927EA23525; Thu, 18 Apr 2019 19:16:36 +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 x3IJGaKp025543; Thu, 18 Apr 2019 19:16:36 GMT (envelope-from asomers@FreeBSD.org) Received: (from asomers@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id x3IJGZfe025435; Thu, 18 Apr 2019 19:16:35 GMT (envelope-from asomers@FreeBSD.org) Message-Id: <201904181916.x3IJGZfe025435@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: asomers set sender to asomers@FreeBSD.org using -f From: Alan Somers To: src-committers@freebsd.org, svn-src-projects@freebsd.org Subject: svn commit: r346357 - 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: 346357 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Precedence: bulk X-Loop: FreeBSD.org Sender: owner-src-committers@freebsd.org X-Rspamd-Queue-Id: 1FA63759A2 X-Spamd-Bar: -- Authentication-Results: mx1.freebsd.org X-Spamd-Result: default: False [-2.96 / 15.00]; local_wl_from(0.00)[freebsd.org]; NEURAL_HAM_MEDIUM(-1.00)[-0.999,0]; NEURAL_HAM_SHORT(-0.96)[-0.957,0]; ASN(0.00)[asn:11403, ipnet:96.47.64.0/20, country:US]; NEURAL_HAM_LONG(-1.00)[-1.000,0] Status: O X-BeenThere: svn-src-projects@freebsd.org X-Mailman-Version: 2.1.29 List-Id: "SVN commit messages for the src " projects" tree" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Date: Tue, 03 Sep 2019 14:07:06 -0000 X-Original-Date: Thu, 18 Apr 2019 19:16:35 +0000 (UTC) X-List-Received-Date: Tue, 03 Sep 2019 14:07:06 -0000 Author: asomers Date: Thu Apr 18 19:16:34 2019 New Revision: 346357 URL: https://svnweb.freebsd.org/changeset/base/346357 Log: fusefs: improvements to interruptibility * If a process receives a fatal signal while blocked on a fuse operation, return ASAP without waiting for the operation to complete. But still send the FUSE_INTERRUPT op to the daemon. * Plug memory leaks from r346339 Interruptibility is now fully functional, but it could be better: * Operations that haven't been sent to the server yet should be aborted without sending FUSE_INTERRUPT. * It would be great if write operations could be made restartable. That would require delaying uiomove until the last possible moment, which would be sometime during fuse_device_read. * It would be nice if we didn't have to guess which EAGAIN responses were for FUSE_INTERRUPT operations. PR: 236530 Sponsored by: The FreeBSD Foundation Modified: projects/fuse2/sys/fs/fuse/fuse_device.c projects/fuse2/sys/fs/fuse/fuse_ipc.c projects/fuse2/sys/fs/fuse/fuse_ipc.h projects/fuse2/tests/sys/fs/fusefs/allow_other.cc projects/fuse2/tests/sys/fs/fusefs/interrupt.cc projects/fuse2/tests/sys/fs/fusefs/open.cc projects/fuse2/tests/sys/fs/fusefs/utils.cc projects/fuse2/tests/sys/fs/fusefs/utils.hh Modified: projects/fuse2/sys/fs/fuse/fuse_device.c ============================================================================== --- projects/fuse2/sys/fs/fuse/fuse_device.c Thu Apr 18 19:04:07 2019 (r346356) +++ projects/fuse2/sys/fs/fuse/fuse_device.c Thu Apr 18 19:16:34 2019 (r346357) @@ -360,7 +360,7 @@ fuse_device_write(struct cdev *dev, struct uio *uio, i struct fuse_out_header ohead; int err = 0; struct fuse_data *data; - struct fuse_ticket *tick, *x_tick; + struct fuse_ticket *tick, *itick, *x_tick; int found = 0; err = devfs_get_cdevpriv((void **)&data); @@ -401,6 +401,20 @@ fuse_device_write(struct cdev *dev, struct uio *uio, i break; } } + if (found && tick->irq_unique > 0) { + /* + * Discard the FUSE_INTERRUPT ticket that tried to interrupt + * this operation + */ + TAILQ_FOREACH_SAFE(itick, &data->aw_head, tk_aw_link, + x_tick) { + if (itick->tk_unique == tick->irq_unique) { + fuse_aw_remove(itick); + break; + } + } + tick->irq_unique = 0; + } fuse_lck_mtx_unlock(data->aw_mtx); if (found) { @@ -433,7 +447,20 @@ fuse_device_write(struct cdev *dev, struct uio *uio, i /* no callback at all! */ SDT_PROBE1(fuse, , device, fuse_device_write_missing_ticket, ohead.unique); - err = EINVAL; + if (ohead.error == EAGAIN) { + /* + * This was probably a response to a FUSE_INTERRUPT + * operation whose original operation is already + * complete. We can't store FUSE_INTERRUPT tickets + * indefinitely because their responses are optional. + * So we delete them when the original operation + * completes. And sadly the fuse_header_out doesn't + * identify the opcode, so we have to guess. + */ + err = 0; + } else { + err = EINVAL; + } } return (err); Modified: projects/fuse2/sys/fs/fuse/fuse_ipc.c ============================================================================== --- projects/fuse2/sys/fs/fuse/fuse_ipc.c Thu Apr 18 19:04:07 2019 (r346356) +++ projects/fuse2/sys/fs/fuse/fuse_ipc.c Thu Apr 18 19:16:34 2019 (r346357) @@ -138,6 +138,32 @@ static uma_zone_t ticket_zone; 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; + bool found = false; + + fii = (struct fuse_interrupt_in*)((char*)tick->tk_ms_fiov.base + + sizeof(struct fuse_in_header)); + + fuse_lck_mtx_lock(data->aw_mtx); + TAILQ_FOREACH_SAFE(otick, &data->aw_head, tk_aw_link, x_tick) { + if (otick->tk_unique == fii->unique) { + found = true; + break; + } + } + fuse_lck_mtx_unlock(data->aw_mtx); + + if (!found) { + /* Original is already complete. Just return */ + return 0; + } + + /* Clear the original ticket's interrupt association */ + otick->irq_unique = 0; + if (tick->tk_aw_ohead.error == EAGAIN) { /* * There are two reasons we might get this: @@ -148,28 +174,8 @@ fuse_interrupt_callback(struct fuse_ticket *tick, stru * In the first case we should re-send the INTERRUPT. In the * second, we should ignore it. */ - struct fuse_interrupt_in *fii; - struct fuse_data *data; - struct fuse_ticket *otick, *x_tick; - bool found = false; - - data = tick->tk_data; - fii = (struct fuse_interrupt_in*)((char*)tick->tk_ms_fiov.base + - sizeof(struct fuse_in_header)); - fuse_lck_mtx_lock(data->aw_mtx); - TAILQ_FOREACH_SAFE(otick, &data->aw_head, tk_aw_link, x_tick) { - if (otick->tk_unique == fii->unique) { - found = true; - break; - } - } - fuse_lck_mtx_unlock(data->aw_mtx); - if (found) { - /* Resend */ - fuse_interrupt_send(otick); - } else { - /* Original is already complete; nothing to do */ - } + /* Resend */ + fuse_interrupt_send(otick); return 0; } else { /* Illegal FUSE_INTERRUPT response */ @@ -186,19 +192,24 @@ fuse_interrupt_send(struct fuse_ticket *otick) struct fuse_data *data = otick->tk_data; struct ucred reused_creds; - ftick_hdr = fticket_in_header(otick); - reused_creds.cr_uid = ftick_hdr->uid; - reused_creds.cr_rgid = ftick_hdr->gid; - fdisp_init(&fdi, sizeof(*fii)); - fdisp_make_pid(&fdi, FUSE_INTERRUPT, data, ftick_hdr->nodeid, - ftick_hdr->pid, &reused_creds); + if (otick->irq_unique == 0) { + ftick_hdr = fticket_in_header(otick); + reused_creds.cr_uid = ftick_hdr->uid; + reused_creds.cr_rgid = ftick_hdr->gid; + fdisp_init(&fdi, sizeof(*fii)); + fdisp_make_pid(&fdi, FUSE_INTERRUPT, data, ftick_hdr->nodeid, + ftick_hdr->pid, &reused_creds); - fii = fdi.indata; - fii->unique = otick->tk_unique; - fuse_insert_callback(fdi.tick, fuse_interrupt_callback); + fii = fdi.indata; + fii->unique = otick->tk_unique; + fuse_insert_callback(fdi.tick, fuse_interrupt_callback); - fuse_insert_message(fdi.tick); - fdisp_destroy(&fdi); + otick->irq_unique = fdi.tick->tk_unique; + fuse_insert_message(fdi.tick); + fdisp_destroy(&fdi); + } else { + /* This ticket has already been interrupted */ + } } void @@ -278,6 +289,8 @@ fticket_ctor(void *mem, int size, void *arg, int flags if (ftick->tk_unique == 0) ftick->tk_unique = atomic_fetchadd_long(&data->ticketer, 1); + ftick->irq_unique = 0; + refcount_init(&ftick->tk_refcount, 1); atomic_add_acq_int(&fuse_ticket_count, 1); @@ -436,20 +449,23 @@ retry: fuse_interrupt_send(ftick); fuse_lck_mtx_lock(ftick->tk_aw_mtx); - /* TODO: return, rather than retry, for fatal signals */ - - /* - * Block the just-delivered signal while we wait for an - * interrupt response - */ 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); - SIGADDSET(blockedset, sig); - kern_sigprocmask(curthread, SIG_BLOCK, &blockedset, NULL, 0); - goto retry; + 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 */ + } } else if (err) { SDT_PROBE2(fuse, , ipc, trace, 6, "fticket_wait_answer: other error"); Modified: projects/fuse2/sys/fs/fuse/fuse_ipc.h ============================================================================== --- projects/fuse2/sys/fs/fuse/fuse_ipc.h Thu Apr 18 19:04:07 2019 (r346356) +++ projects/fuse2/sys/fs/fuse/fuse_ipc.h Thu Apr 18 19:16:34 2019 (r346357) @@ -103,6 +103,12 @@ struct fuse_ticket { struct fuse_data *tk_data; int tk_flag; u_int tk_refcount; + /* + * If this ticket's operation has been interrupted, this will hold the + * unique value of the FUSE_INTERRUPT operation. Otherwise, it will be + * 0. + */ + uint64_t irq_unique; /* fields for initiating an upgoing message */ struct fuse_iov tk_ms_fiov; Modified: projects/fuse2/tests/sys/fs/fusefs/allow_other.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fusefs/allow_other.cc Thu Apr 18 19:04:07 2019 (r346356) +++ projects/fuse2/tests/sys/fs/fusefs/allow_other.cc Thu Apr 18 19:16:34 2019 (r346357) @@ -36,6 +36,7 @@ extern "C" { #include #include +#include #include #include } @@ -74,7 +75,9 @@ virtual void SetUp() { TEST_F(AllowOther, allowed) { - fork(true, [&] { + int status; + + fork(true, &status, [&] { uint64_t ino = 42; expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); @@ -92,6 +95,7 @@ TEST_F(AllowOther, allowed) return 0; } ); + ASSERT_EQ(0, WEXITSTATUS(status)); } /* @@ -104,12 +108,12 @@ TEST_F(AllowOther, privilege_escalation) { const static char FULLPATH[] = "mountpoint/some_file.txt"; const static char RELPATH[] = "some_file.txt"; - int fd1; + int fd1, status; const static uint64_t ino = 42; const static uint64_t fh = 100; /* Fork a child to open the file with different credentials */ - fork(true, [&] { + fork(true, &status, [&] { expect_lookup(RELPATH, ino, S_IFREG | 0600, 0, 2); EXPECT_CALL(*m_mock, process( @@ -156,12 +160,15 @@ TEST_F(AllowOther, privilege_escalation) return 0; } ); + ASSERT_EQ(0, WEXITSTATUS(status)); /* Deliberately leak fd1. close(2) will be tested in release.cc */ } TEST_F(NoAllowOther, disallowed) { - fork(true, [] { + int status; + + fork(true, &status, [] { }, []() { int fd; @@ -177,6 +184,7 @@ TEST_F(NoAllowOther, disallowed) return 0; } ); + ASSERT_EQ(0, WEXITSTATUS(status)); } /* @@ -191,7 +199,7 @@ TEST_F(NoAllowOther, disallowed_beneath_root) const static char RELPATH2[] = "other_dir"; const static uint64_t ino = 42; const static uint64_t ino2 = 43; - int dfd; + int dfd, status; expect_lookup(RELPATH, ino, S_IFDIR | 0755, 0, 1); EXPECT_LOOKUP(ino, RELPATH2) @@ -206,7 +214,7 @@ TEST_F(NoAllowOther, disallowed_beneath_root) dfd = open(FULLPATH, O_DIRECTORY); ASSERT_LE(0, dfd) << strerror(errno); - fork(true, [] { + fork(true, &status, [] { }, [&]() { int fd; @@ -222,6 +230,7 @@ TEST_F(NoAllowOther, disallowed_beneath_root) return 0; } ); + ASSERT_EQ(0, WEXITSTATUS(status)); } /* @@ -230,9 +239,9 @@ TEST_F(NoAllowOther, disallowed_beneath_root) */ TEST_F(NoAllowOther, setextattr) { - int ino = 42; + int ino = 42, status; - fork(true, [&] { + fork(true, &status, [&] { EXPECT_LOOKUP(1, RELPATH) .WillOnce(Invoke( ReturnImmediate([=](auto in __unused, auto out) { @@ -268,4 +277,5 @@ TEST_F(NoAllowOther, setextattr) return 0; } ); + ASSERT_EQ(0, WEXITSTATUS(status)); } Modified: projects/fuse2/tests/sys/fs/fusefs/interrupt.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fusefs/interrupt.cc Thu Apr 18 19:04:07 2019 (r346356) +++ projects/fuse2/tests/sys/fs/fusefs/interrupt.cc Thu Apr 18 19:16:34 2019 (r346357) @@ -29,6 +29,7 @@ */ extern "C" { +#include #include #include #include @@ -68,10 +69,27 @@ Interrupt(): m_child(NULL) {}; void expect_lookup(const char *relpath, uint64_t ino) { - FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1000, 1); } /* + * Expect a FUSE_READ but don't reply. Instead, just record the unique value + * to the provided pointer + */ +void expect_read(uint64_t ino, uint64_t *read_unique) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto &out __unused) { + *read_unique = in->header.unique; + })); +} + +/* * Expect a FUSE_WRITE but don't reply. Instead, just record the unique value * to the provided pointer */ @@ -90,7 +108,7 @@ void expect_write(uint64_t ino, uint64_t *write_unique void setup_interruptor(pthread_t self) { - ASSERT_EQ(0, signal(SIGUSR2, sigusr2_handler)) << strerror(errno); + ASSERT_NE(SIG_ERR, signal(SIGUSR2, sigusr2_handler)) << strerror(errno); ASSERT_EQ(0, pthread_create(&m_child, NULL, killer, (void*)self)) << strerror(errno); } @@ -162,6 +180,74 @@ TEST_F(Interrupt, already_complete) } /* + * Upon receipt of a fatal signal, fusefs should return ASAP after sending + * FUSE_INTERRUPT. + */ +TEST_F(Interrupt, fatal_signal) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char *CONTENTS = "abcdefgh"; + const char RELPATH[] = "some_file.txt"; + ssize_t bufsize = strlen(CONTENTS); + uint64_t ino = 42; + int status; + pthread_t self; + uint64_t write_unique; + + self = pthread_self(); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_write(ino, &write_unique); + EXPECT_CALL(*m_mock, process( + ResultOf([&](auto in) { + return (in->header.opcode == FUSE_INTERRUPT && + in->body.interrupt.unique == write_unique); + }, Eq(true)), + _) + ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) { + /* Don't respond. The process should exit anyway */ + })); + expect_flush(ino, 1, ReturnErrno(0)); + expect_release(ino, FH); + + fork(false, &status, [&] { + }, [&]() { + struct sigaction sa; + int fd, r; + pthread_t killer_th; + pthread_t self; + + fd = open(FULLPATH, O_WRONLY); + if (fd < 0) { + perror("open"); + return 1; + } + + /* SIGUSR2 terminates the process by default */ + bzero(&sa, sizeof(sa)); + sa.sa_handler = SIG_DFL; + r = sigaction(SIGUSR2, &sa, NULL); + if (r != 0) { + perror("sigaction"); + return 1; + } + self = pthread_self(); + r = pthread_create(&killer_th, NULL, killer, (void*)self); + if (r != 0) { + perror("pthread_create"); + return 1; + } + + write(fd, CONTENTS, bufsize); + return 1; + }); + ASSERT_EQ(SIGUSR2, WTERMSIG(status)); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* * A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and * complete the original operation whenever it damn well pleases. */ @@ -253,6 +339,47 @@ TEST_F(Interrupt, in_progress) /* Deliberately leak fd. close(2) will be tested in release.cc */ } +/* Reads should also be interruptible */ +TEST_F(Interrupt, in_progress_read) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const size_t bufsize = 80; + char buf[bufsize]; + uint64_t ino = 42; + int fd; + pthread_t self; + uint64_t read_unique; + + self = pthread_self(); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_read(ino, &read_unique); + EXPECT_CALL(*m_mock, process( + ResultOf([&](auto in) { + return (in->header.opcode == FUSE_INTERRUPT && + in->body.interrupt.unique == read_unique); + }, Eq(true)), + _) + ).WillOnce(Invoke([&](auto in __unused, auto &out) { + auto out0 = new mockfs_buf_out; + out0->header.error = -EINTR; + out0->header.unique = read_unique; + out0->header.len = sizeof(out0->header); + out.push_back(out0); + })); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + 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 */ +} + /* * If the FUSE filesystem receives the FUSE_INTERRUPT operation before * processing the original, then it should wait for "some timeout" for the @@ -320,9 +447,7 @@ TEST_F(Interrupt, too_soon) // TODO: add a test that uses siginterrupt and an interruptible signal // TODO: add a test that verifies a process can be cleanly killed even if a // FUSE_WRITE command never returns. -// TODO: write in-progress tests for read and other operations +// TODO: write in-progress tests for other operations // TODO: add a test where write returns EWOULDBLOCK -// TODO: test that if a fatal signal is received, fticket_wait_answer will -// return without waiting for a response to the interrupted operation. // TODO: test that operations that haven't been received by the server can be // interrupted without generating a FUSE_INTERRUPT. Modified: projects/fuse2/tests/sys/fs/fusefs/open.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fusefs/open.cc Thu Apr 18 19:04:07 2019 (r346356) +++ projects/fuse2/tests/sys/fs/fusefs/open.cc Thu Apr 18 19:16:34 2019 (r346357) @@ -29,6 +29,7 @@ */ extern "C" { +#include #include } @@ -166,12 +167,12 @@ TEST_F(Open, multiple_creds) { const static char FULLPATH[] = "mountpoint/some_file.txt"; const static char RELPATH[] = "some_file.txt"; - int fd1; + int fd1, status; const static uint64_t ino = 42; const static uint64_t fh0 = 100, fh1 = 200; /* Fork a child to open the file with different credentials */ - fork(false, [&] { + fork(false, &status, [&] { expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2); EXPECT_CALL(*m_mock, process( @@ -218,6 +219,7 @@ TEST_F(Open, multiple_creds) return 0; } ); + ASSERT_EQ(0, WEXITSTATUS(status)); close(fd1); } Modified: projects/fuse2/tests/sys/fs/fusefs/utils.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fusefs/utils.cc Thu Apr 18 19:04:07 2019 (r346356) +++ projects/fuse2/tests/sys/fs/fusefs/utils.cc Thu Apr 18 19:16:34 2019 (r346357) @@ -336,7 +336,8 @@ get_unprivileged_uid(uid_t *uid) } void -FuseTest::fork(bool drop_privs, std::function parent_func, +FuseTest::fork(bool drop_privs, int *child_status, + std::function parent_func, std::function child_func) { sem_t *sem; @@ -376,8 +377,6 @@ out: sem_destroy(sem); _exit(err); } else if (child > 0) { - int child_status; - /* * In parent. Cleanup must happen here, because it's still * privileged. @@ -388,12 +387,12 @@ out: /* Signal the child process to go */ ASSERT_EQ(0, sem_post(sem)) << strerror(errno); - ASSERT_LE(0, wait(&child_status)) << strerror(errno); - ASSERT_EQ(0, WEXITSTATUS(child_status)); + ASSERT_LE(0, wait(child_status)) << strerror(errno); } else { FAIL() << strerror(errno); } munmap(sem, sizeof(*sem)); + return; } static void usage(char* progname) { Modified: projects/fuse2/tests/sys/fs/fusefs/utils.hh ============================================================================== --- projects/fuse2/tests/sys/fs/fusefs/utils.hh Thu Apr 18 19:04:07 2019 (r346356) +++ projects/fuse2/tests/sys/fs/fusefs/utils.hh Thu Apr 18 19:16:34 2019 (r346357) @@ -106,7 +106,7 @@ class FuseTest : public ::testing::Test { uid_t uid = 0); /* - * Create an expectation that FUSE_GETATTR will be called for the given + * Create an expectation that FUSE_OPEN will be called for the given * inode exactly times times. It will return with open_flags flags and * file handle FH. */ @@ -162,11 +162,11 @@ class FuseTest : public ::testing::Test { * * # Returns * - * fusetest_fork will FAIL the test if child_func returns nonzero. - * It may SKIP the test, which the caller should detect with the - * IsSkipped() method. + * fusetest_fork may SKIP the test, which the caller should detect with + * the IsSkipped() method. If not, then the child's exit status will + * be returned in status. */ - void fork(bool drop_privs, + void fork(bool drop_privs, int *status, std::function parent_func, std::function child_func); };