From owner-svn-src-projects@freebsd.org Thu Mar 14 17:20:27 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 56CE615244D9 for ; Thu, 14 Mar 2019 17:20:27 +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 057DE89BD3; Thu, 14 Mar 2019 17:20:27 +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 D06E8258CA; Thu, 14 Mar 2019 17:20:26 +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 x2EHKQVr096163; Thu, 14 Mar 2019 17:20:26 GMT (envelope-from asomers@FreeBSD.org) Received: (from asomers@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id x2EHKOo2096150; Thu, 14 Mar 2019 17:20:24 GMT (envelope-from asomers@FreeBSD.org) Message-Id: <201903141720.x2EHKOo2096150@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: asomers set sender to asomers@FreeBSD.org using -f From: Alan Somers Date: Thu, 14 Mar 2019 17:20:24 +0000 (UTC) To: src-committers@freebsd.org, svn-src-projects@freebsd.org Subject: svn commit: r345140 - projects/fuse2/tests/sys/fs/fuse X-SVN-Group: projects X-SVN-Commit-Author: asomers X-SVN-Commit-Paths: projects/fuse2/tests/sys/fs/fuse X-SVN-Commit-Revision: 345140 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Rspamd-Queue-Id: 057DE89BD3 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)[-0.998,0]; NEURAL_HAM_SHORT(-0.98)[-0.984,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: Thu, 14 Mar 2019 17:20:27 -0000 Author: asomers Date: Thu Mar 14 17:20:24 2019 New Revision: 345140 URL: https://svnweb.freebsd.org/changeset/base/345140 Log: fuse(4): add tests for FUSE_INTERRUPT This required changing the way that all operations are mocked. Previously MockFS::process had one input argument and one output argument. Now, it returns a vector of zero or more responses. This allows tests to simulate conditions where the filesystem daemon has a queue depth > 1. PR: 236530 Sponsored by: The FreeBSD Foundation Added: projects/fuse2/tests/sys/fs/fuse/interrupt.cc (contents, props changed) Modified: projects/fuse2/tests/sys/fs/fuse/Makefile projects/fuse2/tests/sys/fs/fuse/create.cc projects/fuse2/tests/sys/fs/fuse/fsync.cc projects/fuse2/tests/sys/fs/fuse/getattr.cc projects/fuse2/tests/sys/fs/fuse/link.cc projects/fuse2/tests/sys/fs/fuse/locks.cc projects/fuse2/tests/sys/fs/fuse/lookup.cc projects/fuse2/tests/sys/fs/fuse/mkdir.cc projects/fuse2/tests/sys/fs/fuse/mknod.cc projects/fuse2/tests/sys/fs/fuse/mockfs.cc projects/fuse2/tests/sys/fs/fuse/mockfs.hh projects/fuse2/tests/sys/fs/fuse/open.cc projects/fuse2/tests/sys/fs/fuse/opendir.cc projects/fuse2/tests/sys/fs/fuse/read.cc projects/fuse2/tests/sys/fs/fuse/readdir.cc projects/fuse2/tests/sys/fs/fuse/readlink.cc projects/fuse2/tests/sys/fs/fuse/releasedir.cc projects/fuse2/tests/sys/fs/fuse/rmdir.cc projects/fuse2/tests/sys/fs/fuse/setattr.cc projects/fuse2/tests/sys/fs/fuse/statfs.cc projects/fuse2/tests/sys/fs/fuse/symlink.cc projects/fuse2/tests/sys/fs/fuse/utils.cc projects/fuse2/tests/sys/fs/fuse/write.cc Modified: projects/fuse2/tests/sys/fs/fuse/Makefile ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/Makefile Thu Mar 14 17:18:00 2019 (r345139) +++ projects/fuse2/tests/sys/fs/fuse/Makefile Thu Mar 14 17:20:24 2019 (r345140) @@ -4,12 +4,16 @@ PACKAGE= tests TESTSDIR= ${TESTSBASE}/sys/fs/fuse +# We could simply link all of these files into a single executable. But since +# Kyua treats googletest programs as plain tests, it's better to separate them +# out, so we get more granular reporting. ATF_TESTS_CXX+= access ATF_TESTS_CXX+= create ATF_TESTS_CXX+= flush ATF_TESTS_CXX+= fsync ATF_TESTS_CXX+= fsyncdir ATF_TESTS_CXX+= getattr +ATF_TESTS_CXX+= interrupt ATF_TESTS_CXX+= link ATF_TESTS_CXX+= locks ATF_TESTS_CXX+= lookup @@ -59,6 +63,11 @@ SRCS.getattr+= getattr.cc SRCS.getattr+= getmntopts.c SRCS.getattr+= mockfs.cc SRCS.getattr+= utils.cc + +SRCS.interrupt+= interrupt.cc +SRCS.interrupt+= getmntopts.c +SRCS.interrupt+= mockfs.cc +SRCS.interrupt+= utils.cc SRCS.link+= getmntopts.c SRCS.link+= link.cc Modified: projects/fuse2/tests/sys/fs/fuse/create.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/create.cc Thu Mar 14 17:18:00 2019 (r345139) +++ projects/fuse2/tests/sys/fs/fuse/create.cc Thu Mar 14 17:20:24 2019 (r345140) @@ -62,14 +62,14 @@ TEST_F(Create, DISABLED_attr_cache) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -145,14 +145,14 @@ TEST_F(Create, DISABLED_Enosys) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -160,11 +160,11 @@ TEST_F(Create, DISABLED_Enosys) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); - })); + }))); /* Until the attr cache is working, we may send an additional GETATTR */ EXPECT_CALL(*m_mock, process( @@ -173,12 +173,12 @@ TEST_F(Create, DISABLED_Enosys) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; - })); + }))); fd = open(FULLPATH, O_CREAT | O_EXCL, mode); EXPECT_LE(0, fd) << strerror(errno); @@ -214,14 +214,14 @@ TEST_F(Create, DISABLED_entry_cache_negative) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; - })); + }))); /* Until the attr cache is working, we may send an additional GETATTR */ EXPECT_CALL(*m_mock, process( @@ -230,12 +230,12 @@ TEST_F(Create, DISABLED_entry_cache_negative) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; - })); + }))); fd = open(FULLPATH, O_CREAT | O_EXCL, mode); ASSERT_LE(0, fd) << strerror(errno); @@ -269,13 +269,13 @@ TEST_F(Create, DISABLED_entry_cache_negative_purge) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.attr_valid = UINT64_MAX; - })); + }))); /* Until the attr cache is working, we may send an additional GETATTR */ EXPECT_CALL(*m_mock, process( @@ -284,12 +284,12 @@ TEST_F(Create, DISABLED_entry_cache_negative_purge) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; - })); + }))); fd = open(FULLPATH, O_CREAT | O_EXCL, mode); ASSERT_LE(0, fd) << strerror(errno); @@ -344,14 +344,14 @@ TEST_F(Create, ok) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; - })); + }))); /* Until the attr cache is working, we may send an additional GETATTR */ EXPECT_CALL(*m_mock, process( @@ -360,12 +360,12 @@ TEST_F(Create, ok) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; - })); + }))); fd = open(FULLPATH, O_CREAT | O_EXCL, mode); EXPECT_LE(0, fd) << strerror(errno); Modified: projects/fuse2/tests/sys/fs/fuse/fsync.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/fsync.cc Thu Mar 14 17:18:00 2019 (r345139) +++ projects/fuse2/tests/sys/fs/fuse/fsync.cc Thu Mar 14 17:20:24 2019 (r345140) @@ -132,11 +132,11 @@ TEST_F(Fsync, close) return (in->header.opcode == FUSE_SETATTR); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_FSYNC); @@ -244,12 +244,12 @@ TEST_F(Fsync, DISABLED_fsync_metadata_only) return (in->header.opcode == FUSE_SETATTR); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | mode; - })); + }))); expect_fsync(ino, 0, 0); Modified: projects/fuse2/tests/sys/fs/fuse/getattr.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/getattr.cc Thu Mar 14 17:18:00 2019 (r345139) +++ projects/fuse2/tests/sys/fs/fuse/getattr.cc Thu Mar 14 17:20:24 2019 (r345140) @@ -47,25 +47,25 @@ TEST_F(Getattr, DISABLED_attr_cache) const uint64_t ino = 42; struct stat sb; - EXPECT_LOOKUP(1, RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_GETATTR && in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr_valid = UINT64_MAX; out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; - })); + }))); EXPECT_EQ(0, stat(FULLPATH, &sb)); /* The second stat(2) should use cached attributes */ EXPECT_EQ(0, stat(FULLPATH, &sb)); @@ -97,14 +97,14 @@ TEST_F(Getattr, attr_cache_timeout) }, Eq(true)), _) ).Times(2) - .WillRepeatedly(Invoke([=](auto in, auto out) { + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr_valid_nsec = timeout_ns; out->body.attr.attr_valid = UINT64_MAX; out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; - })); + }))); EXPECT_EQ(0, stat(FULLPATH, &sb)); usleep(2 * timeout_ns / 1000); /* Timeout has expire. stat(2) should requery the daemon */ @@ -144,7 +144,7 @@ TEST_F(Getattr, ok) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid @@ -161,7 +161,7 @@ TEST_F(Getattr, ok) out->body.attr.attr.uid = 10; out->body.attr.attr.gid = 11; out->body.attr.attr.rdev = 12; - })); + }))); ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); EXPECT_EQ(1, sb.st_size); Added: projects/fuse2/tests/sys/fs/fuse/interrupt.cc ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ projects/fuse2/tests/sys/fs/fuse/interrupt.cc Thu Mar 14 17:20:24 2019 (r345140) @@ -0,0 +1,312 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 The FreeBSD Foundation + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +/* Don't do anything; all we care about is that the syscall gets interrupted */ +void sigusr2_handler(int __unused sig) { + if (verbosity > 1) + printf("Signaled!\n"); +} + +void* killer(void* target) { + /* + * Sleep for awhile so we can be mostly confident that the main thread + * is already blocked in write(2) + */ + usleep(250'000); + if (verbosity > 1) + printf("Signalling!\n"); + pthread_kill(*(pthread_t*)target, SIGUSR2); + + return(NULL); +} + +class Interrupt: public FuseTest { +public: +pthread_t m_child; + +Interrupt(): m_child(NULL) {}; + +void expect_lookup(const char *relpath, uint64_t ino) +{ + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); +} + +/* + * Expect a FUSE_WRITE but don't reply. Instead, just record the unique value + * to the provided pointer + */ +void expect_write(uint64_t ino, uint64_t *write_unique) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_WRITE && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto &out __unused) { + *write_unique = in->header.unique; + })); +} + +void setup_interruptor(pthread_t self) +{ + ASSERT_EQ(0, signal(SIGUSR2, sigusr2_handler)) << strerror(errno); + ASSERT_EQ(0, pthread_create(&m_child, NULL, killer, (void*)self)) + << strerror(errno); +} + +void TearDown() { + if (m_child != NULL) { + pthread_join(m_child, NULL); + } + + FuseTest::TearDown(); +} +}; + +/* + * An interrupt operation that gets received after the original command is + * complete should generate an EAGAIN response. + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ +TEST_F(Interrupt, DISABLED_already_complete) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + pthread_t self; + uint64_t write_unique = 0; + + self = pthread_self(); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + 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, auto &out) { + // First complete the write request + auto out0 = new mockfs_buf_out; + out0->header.unique = write_unique; + SET_OUT_HEADER_LEN(out0, write); + out0->body.write.size = bufsize; + out.push_back(out0); + + // Then, respond EAGAIN to the interrupt request + auto out1 = new mockfs_buf_out; + out1->header.unique = in->header.unique; + out1->header.error = -EAGAIN; + out1->header.len = sizeof(out1->header); + out.push_back(out1); + })); + + fd = open(FULLPATH, O_WRONLY); + ASSERT_LE(0, fd) << strerror(errno); + + setup_interruptor(self); + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + + /* 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. + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ +TEST_F(Interrupt, DISABLED_ignore) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + pthread_t self; + uint64_t write_unique; + + self = pthread_self(); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + 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) { + // Ignore FUSE_INTERRUPT; respond to the FUSE_WRITE + auto out0 = new mockfs_buf_out; + out0->header.unique = write_unique; + SET_OUT_HEADER_LEN(out0, write); + out0->body.write.size = bufsize; + out.push_back(out0); + })); + + fd = open(FULLPATH, O_WRONLY); + ASSERT_LE(0, fd) << strerror(errno); + + setup_interruptor(self); + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * 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 + * in response to the _original_ operation. The kernel should ultimately + * return EINTR to userspace + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ +TEST_F(Interrupt, DISABLED_in_progress) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + pthread_t self; + uint64_t write_unique; + + self = pthread_self(); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + 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) { + auto out0 = new mockfs_buf_out; + out0->header.error = -EINTR; + out0->header.unique = write_unique; + out0->header.len = sizeof(out0->header); + out.push_back(out0); + })); + + fd = open(FULLPATH, O_WRONLY); + ASSERT_LE(0, fd) << strerror(errno); + + setup_interruptor(self); + ASSERT_EQ(-1, write(fd, CONTENTS, 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 + * original operation to arrive. If not, it should send EAGAIN to the + * INTERRUPT operation, and the kernel should requeue the INTERRUPT. + * + * In this test, we'll pretend that the INTERRUPT arrives too soon, gets + * EAGAINed, then the kernel requeues it, and the second time around it + * successfully interrupts the original + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ +TEST_F(Interrupt, DISABLED_too_soon) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + pthread_t self; + uint64_t write_unique; + + self = pthread_self(); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + 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(ReturnErrno(EAGAIN))) + .RetiresOnSaturation(); + + 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) { + auto out0 = new mockfs_buf_out; + out0->header.error = -EINTR; + out0->header.unique = write_unique; + out0->header.len = sizeof(out0->header); + out.push_back(out0); + })); + + fd = open(FULLPATH, O_WRONLY); + ASSERT_LE(0, fd) << strerror(errno); + + setup_interruptor(self); + ASSERT_EQ(-1, write(fd, CONTENTS, bufsize)); + EXPECT_EQ(EINTR, errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} Modified: projects/fuse2/tests/sys/fs/fuse/link.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/link.cc Thu Mar 14 17:18:00 2019 (r345139) +++ projects/fuse2/tests/sys/fs/fuse/link.cc Thu Mar 14 17:20:24 2019 (r345140) @@ -92,12 +92,12 @@ TEST_F(Link, ok) (0 == strcmp(name, RELPATH))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; - })); + }))); EXPECT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno); } Modified: projects/fuse2/tests/sys/fs/fuse/locks.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/locks.cc Thu Mar 14 17:18:00 2019 (r345139) +++ projects/fuse2/tests/sys/fs/fuse/locks.cc Thu Mar 14 17:20:24 2019 (r345140) @@ -123,12 +123,12 @@ TEST_F(Getlk, DISABLED_no_locks) in->body.getlk.lk.pid == 10); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk = in->body.getlk.lk; out->body.getlk.lk.type = F_UNLCK; - })); + }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); @@ -170,14 +170,14 @@ TEST_F(Getlk, DISABLED_lock_exists) in->body.getlk.lk.pid == 10); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk.start = 100; out->body.getlk.lk.end = 199; out->body.getlk.lk.type = F_WRLCK; out->body.getlk.lk.pid = (uint32_t)pid2;; - })); + }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); @@ -251,12 +251,12 @@ TEST_F(Setlk, DISABLED_set) in->body.getlk.lk.pid == 10); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk = in->body.getlk.lk; out->body.getlk.lk.type = F_UNLCK; - })); + }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); @@ -296,12 +296,12 @@ TEST_F(Setlk, DISABLED_set_eof) in->body.getlk.lk.pid == 10); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk = in->body.getlk.lk; out->body.getlk.lk.type = F_UNLCK; - })); + }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); @@ -414,12 +414,12 @@ TEST_F(Setlkw, DISABLED_set) in->body.getlk.lk.pid == 10); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk = in->body.getlk.lk; out->body.getlk.lk.type = F_UNLCK; - })); + }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); Modified: projects/fuse2/tests/sys/fs/fuse/lookup.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/lookup.cc Thu Mar 14 17:18:00 2019 (r345139) +++ projects/fuse2/tests/sys/fs/fuse/lookup.cc Thu Mar 14 17:20:24 2019 (r345140) @@ -52,7 +52,8 @@ TEST_F(Lookup, DISABLED_attr_cache) const uint64_t generation = 13; struct stat sb; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.nodeid = ino; @@ -72,7 +73,7 @@ TEST_F(Lookup, DISABLED_attr_cache) out->body.entry.attr.gid = 11; out->body.entry.attr.rdev = 12; out->body.entry.generation = generation; - })); + }))); /* stat(2) issues a VOP_LOOKUP followed by a VOP_GETATTR */ ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); EXPECT_EQ(1, sb.st_size); @@ -118,14 +119,15 @@ TEST_F(Lookup, attr_cache_timeout) */ long timeout_ns = 250'000'000; - EXPECT_LOOKUP(1, RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.nodeid = ino; out->body.entry.attr_valid_nsec = timeout_ns; out->body.entry.attr.ino = ino; // Must match nodeid out->body.entry.attr.mode = S_IFREG | 0644; - })); + }))); expect_getattr(ino, 0); /* access(2) will issue a VOP_LOOKUP but not a VOP_GETATTR */ @@ -154,13 +156,14 @@ TEST_F(Lookup, entry_cache) const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.entry_valid = UINT64_MAX; out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = 14; - })); + }))); ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); /* The second access(2) should use the cache */ ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); @@ -224,13 +227,13 @@ TEST_F(Lookup, DISABLED_entry_cache_timeout) long timeout_ns = 250'000'000; EXPECT_LOOKUP(1, RELPATH).Times(2) - .WillRepeatedly(Invoke([=](auto in, auto out) { + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.entry_valid_nsec = timeout_ns; out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = 14; - })); + }))); ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); usleep(2 * timeout_ns / 1000); /* The cache has timed out; VOP_LOOKUP should query the daemon*/ @@ -248,12 +251,13 @@ TEST_F(Lookup, ok) const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = 14; - })); + }))); /* * access(2) is one of the few syscalls that will not (always) follow * up a successful VOP_LOOKUP with another VOP. @@ -270,18 +274,20 @@ TEST_F(Lookup, subdir) uint64_t dir_ino = 2; uint64_t file_ino = 3; - EXPECT_LOOKUP(1, DIRPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, DIRPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFDIR | 0755; out->body.entry.nodeid = dir_ino; - })); - EXPECT_LOOKUP(dir_ino, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + }))); + EXPECT_LOOKUP(dir_ino, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = file_ino; - })); + }))); /* * access(2) is one of the few syscalls that will not (always) follow * up a successful VOP_LOOKUP with another VOP. Modified: projects/fuse2/tests/sys/fs/fuse/mkdir.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/mkdir.cc Thu Mar 14 17:18:00 2019 (r345139) +++ projects/fuse2/tests/sys/fs/fuse/mkdir.cc Thu Mar 14 17:20:24 2019 (r345140) @@ -95,14 +95,14 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.create.entry.attr.mode = S_IFDIR | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; - })); + }))); ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); } @@ -134,13 +134,13 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative_purge) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFDIR | mode; out->body.entry.nodeid = ino; out->body.entry.attr_valid = UINT64_MAX; - })); + }))); ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); @@ -168,14 +168,14 @@ TEST_F(Mkdir, ok) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.create.entry.attr.mode = S_IFDIR | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; - })); + }))); ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); } Modified: projects/fuse2/tests/sys/fs/fuse/mknod.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/mknod.cc Thu Mar 14 17:18:00 2019 (r345139) +++ projects/fuse2/tests/sys/fs/fuse/mknod.cc Thu Mar 14 17:20:24 2019 (r345140) @@ -67,7 +67,7 @@ void test_ok(mode_t mode, dev_t dev) { (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = mode; @@ -75,7 +75,7 @@ void test_ok(mode_t mode, dev_t dev) { out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; out->body.create.entry.attr.rdev = dev; - })); + }))); EXPECT_EQ(0, mknod(FULLPATH, mode, dev)) << strerror(errno); } Modified: projects/fuse2/tests/sys/fs/fuse/mockfs.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/mockfs.cc Thu Mar 14 17:18:00 2019 (r345139) +++ projects/fuse2/tests/sys/fs/fuse/mockfs.cc Thu Mar 14 17:20:24 2019 (r345140) @@ -105,31 +105,46 @@ const char* opcode2opname(uint32_t opcode) return (table[opcode]); } -std::function +ProcessMockerT ReturnErrno(int error) { - return([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -error; - out->header.len = sizeof(out->header); + return([=](auto in, auto &out) { + auto out0 = new mockfs_buf_out; + out0->header.unique = in->header.unique; + out0->header.error = -error; + out0->header.len = sizeof(out0->header); + out.push_back(out0); }); } /* Helper function used for returning negative cache entries for LOOKUP */ -std::function +ProcessMockerT ReturnNegativeCache(const struct timespec *entry_valid) { - return([=](auto in, auto out) { + return([=](auto in, auto &out) { /* nodeid means ENOENT and cache it */ - out->body.entry.nodeid = 0; - out->header.unique = in->header.unique; - out->header.error = 0; - out->body.entry.entry_valid = entry_valid->tv_sec; - out->body.entry.entry_valid_nsec = entry_valid->tv_nsec; - SET_OUT_HEADER_LEN(out, entry); + auto out0 = new mockfs_buf_out; + out0->body.entry.nodeid = 0; + out0->header.unique = in->header.unique; + out0->header.error = 0; + out0->body.entry.entry_valid = entry_valid->tv_sec; + out0->body.entry.entry_valid_nsec = entry_valid->tv_nsec; + SET_OUT_HEADER_LEN(out0, entry); + out.push_back(out0); }); } +ProcessMockerT +ReturnImmediate(std::function f) +{ + return([=](auto in, auto &out) { + auto out0 = new mockfs_buf_out; + f(in, out0); + out.push_back(out0); + }); +} + void sigint_handler(int __unused sig) { quit = 1; } @@ -309,14 +324,12 @@ void MockFS::kill_daemon() { void MockFS::loop() { mockfs_buf_in *in; - mockfs_buf_out *out; + std::vector out; in = (mockfs_buf_in*) malloc(sizeof(*in)); - out = (mockfs_buf_out*) malloc(sizeof(*out)); ASSERT_TRUE(in != NULL); while (!quit) { bzero(in, sizeof(*in)); - bzero(out, sizeof(*out)); read_request(in); if (quit) break; @@ -332,19 +345,14 @@ void MockFS::loop() { */ process_default(in, out); } - if (in->header.opcode == FUSE_FORGET) { - /*Alone among the opcodes, FORGET expects no response*/ - continue; + for (auto &it: out) { + ASSERT_TRUE(write(m_fuse_fd, it, it->header.len) > 0 || + errno == EAGAIN) + << strerror(errno); + delete it; } - if (out->header.error == FUSE_NORESPONSE) { - /* Used by tests of slow opcodes. No response ATM */ - continue; - } - ASSERT_TRUE(write(m_fuse_fd, out, out->header.len) > 0 || - errno == EAGAIN) - << strerror(errno); + out.clear(); } - free(out); free(in); } @@ -369,10 +377,14 @@ bool MockFS::pid_ok(pid_t pid) { } } -void MockFS::process_default(const mockfs_buf_in *in, mockfs_buf_out* out) { - out->header.unique = in->header.unique; - out->header.error = -EOPNOTSUPP; - out->header.len = sizeof(out->header); +void MockFS::process_default(const mockfs_buf_in *in, + std::vector &out) +{ *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***