113d593a5SAlan Somers /*- 24d846d26SWarner Losh * SPDX-License-Identifier: BSD-2-Clause 313d593a5SAlan Somers * 413d593a5SAlan Somers * Copyright (c) 2021 Alan Somers 513d593a5SAlan Somers * 613d593a5SAlan Somers * Redistribution and use in source and binary forms, with or without 713d593a5SAlan Somers * modification, are permitted provided that the following conditions 813d593a5SAlan Somers * are met: 913d593a5SAlan Somers * 1. Redistributions of source code must retain the above copyright 1013d593a5SAlan Somers * notice, this list of conditions and the following disclaimer. 1113d593a5SAlan Somers * 2. Redistributions in binary form must reproduce the above copyright 1213d593a5SAlan Somers * notice, this list of conditions and the following disclaimer in the 1313d593a5SAlan Somers * documentation and/or other materials provided with the distribution. 1413d593a5SAlan Somers * 1513d593a5SAlan Somers * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 1613d593a5SAlan Somers * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1713d593a5SAlan Somers * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 1813d593a5SAlan Somers * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 1913d593a5SAlan Somers * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2013d593a5SAlan Somers * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2113d593a5SAlan Somers * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2213d593a5SAlan Somers * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2313d593a5SAlan Somers * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2413d593a5SAlan Somers * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2513d593a5SAlan Somers * SUCH DAMAGE. 2613d593a5SAlan Somers */ 2713d593a5SAlan Somers 2813d593a5SAlan Somers extern "C" { 2913d593a5SAlan Somers #include <sys/param.h> 3013d593a5SAlan Somers #include <sys/mount.h> 3113d593a5SAlan Somers #include <sys/stat.h> 3213d593a5SAlan Somers 3313d593a5SAlan Somers #include <fcntl.h> 3413d593a5SAlan Somers #include <pthread.h> 3513d593a5SAlan Somers #include <semaphore.h> 3613d593a5SAlan Somers } 3713d593a5SAlan Somers 3813d593a5SAlan Somers #include "mockfs.hh" 3913d593a5SAlan Somers #include "utils.hh" 4013d593a5SAlan Somers 4113d593a5SAlan Somers using namespace testing; 4213d593a5SAlan Somers 4313d593a5SAlan Somers /* 4413d593a5SAlan Somers * "Last Local Modify" bugs 4513d593a5SAlan Somers * 4613d593a5SAlan Somers * This file tests a class of race conditions caused by one thread fetching a 4713d593a5SAlan Somers * file's size with FUSE_LOOKUP while another thread simultaneously modifies it 4813d593a5SAlan Somers * with FUSE_SETATTR, FUSE_WRITE, FUSE_COPY_FILE_RANGE or similar. It's 4913d593a5SAlan Somers * possible for the second thread to start later yet finish first. If that 5013d593a5SAlan Somers * happens, the first thread must not override the size set by the second 5113d593a5SAlan Somers * thread. 5213d593a5SAlan Somers * 5313d593a5SAlan Somers * FUSE_GETATTR is not vulnerable to the same race, because it is always called 5413d593a5SAlan Somers * with the vnode lock held. 5513d593a5SAlan Somers * 5613d593a5SAlan Somers * A few other operations like FUSE_LINK can also trigger the same race but 5713d593a5SAlan Somers * with the file's ctime instead of size. However, the consequences of an 5813d593a5SAlan Somers * incorrect ctime are much less disastrous than an incorrect size, so fusefs 5913d593a5SAlan Somers * does not attempt to prevent such races. 6013d593a5SAlan Somers */ 6113d593a5SAlan Somers 6213d593a5SAlan Somers enum Mutator { 63398c88c7SAlan Somers VOP_ALLOCATE, 64398c88c7SAlan Somers VOP_COPY_FILE_RANGE, 6513d593a5SAlan Somers VOP_SETATTR, 6613d593a5SAlan Somers VOP_WRITE, 6713d593a5SAlan Somers }; 6813d593a5SAlan Somers 6913d593a5SAlan Somers /* 7013d593a5SAlan Somers * Translate a poll method's string representation to the enum value. 7113d593a5SAlan Somers * Using strings with ::testing::Values gives better output with 7213d593a5SAlan Somers * --gtest_list_tests 7313d593a5SAlan Somers */ 7413d593a5SAlan Somers enum Mutator writer_from_str(const char* s) { 75398c88c7SAlan Somers if (0 == strcmp("VOP_ALLOCATE", s)) 76398c88c7SAlan Somers return VOP_ALLOCATE; 77398c88c7SAlan Somers else if (0 == strcmp("VOP_COPY_FILE_RANGE", s)) 7813d593a5SAlan Somers return VOP_COPY_FILE_RANGE; 79398c88c7SAlan Somers else if (0 == strcmp("VOP_SETATTR", s)) 80398c88c7SAlan Somers return VOP_SETATTR; 81398c88c7SAlan Somers else 82398c88c7SAlan Somers return VOP_WRITE; 8313d593a5SAlan Somers } 8413d593a5SAlan Somers 8513d593a5SAlan Somers uint32_t fuse_op_from_mutator(enum Mutator mutator) { 8613d593a5SAlan Somers switch(mutator) { 87398c88c7SAlan Somers case VOP_ALLOCATE: 88398c88c7SAlan Somers return(FUSE_FALLOCATE); 89398c88c7SAlan Somers case VOP_COPY_FILE_RANGE: 90398c88c7SAlan Somers return(FUSE_COPY_FILE_RANGE); 9113d593a5SAlan Somers case VOP_SETATTR: 9213d593a5SAlan Somers return(FUSE_SETATTR); 9313d593a5SAlan Somers case VOP_WRITE: 9413d593a5SAlan Somers return(FUSE_WRITE); 9513d593a5SAlan Somers } 9613d593a5SAlan Somers } 9713d593a5SAlan Somers 9813d593a5SAlan Somers class LastLocalModify: public FuseTest, public WithParamInterface<const char*> { 9913d593a5SAlan Somers public: 10013d593a5SAlan Somers virtual void SetUp() { 10113d593a5SAlan Somers m_init_flags = FUSE_EXPORT_SUPPORT; 10213d593a5SAlan Somers 10313d593a5SAlan Somers FuseTest::SetUp(); 10413d593a5SAlan Somers } 10513d593a5SAlan Somers }; 10613d593a5SAlan Somers 107398c88c7SAlan Somers static void* allocate_th(void* arg) { 108398c88c7SAlan Somers int fd; 109398c88c7SAlan Somers ssize_t r; 110398c88c7SAlan Somers sem_t *sem = (sem_t*) arg; 111398c88c7SAlan Somers 112398c88c7SAlan Somers if (sem) 113398c88c7SAlan Somers sem_wait(sem); 114398c88c7SAlan Somers 115398c88c7SAlan Somers fd = open("mountpoint/some_file.txt", O_RDWR); 116398c88c7SAlan Somers if (fd < 0) 117398c88c7SAlan Somers return (void*)(intptr_t)errno; 118398c88c7SAlan Somers 119398c88c7SAlan Somers r = posix_fallocate(fd, 0, 15); 1204ac4b126SAlan Somers LastLocalModify::leak(fd); 121398c88c7SAlan Somers if (r >= 0) 122398c88c7SAlan Somers return 0; 123398c88c7SAlan Somers else 124398c88c7SAlan Somers return (void*)(intptr_t)errno; 125398c88c7SAlan Somers } 126398c88c7SAlan Somers 12713d593a5SAlan Somers static void* copy_file_range_th(void* arg) { 12813d593a5SAlan Somers ssize_t r; 12913d593a5SAlan Somers int fd; 13013d593a5SAlan Somers sem_t *sem = (sem_t*) arg; 13113d593a5SAlan Somers off_t off_in = 0; 13213d593a5SAlan Somers off_t off_out = 10; 13313d593a5SAlan Somers ssize_t len = 5; 13413d593a5SAlan Somers 13513d593a5SAlan Somers if (sem) 13613d593a5SAlan Somers sem_wait(sem); 13713d593a5SAlan Somers fd = open("mountpoint/some_file.txt", O_RDWR); 13813d593a5SAlan Somers if (fd < 0) 13913d593a5SAlan Somers return (void*)(intptr_t)errno; 14013d593a5SAlan Somers 14113d593a5SAlan Somers r = copy_file_range(fd, &off_in, fd, &off_out, len, 0); 14213d593a5SAlan Somers if (r >= 0) { 14313d593a5SAlan Somers LastLocalModify::leak(fd); 14413d593a5SAlan Somers return 0; 14513d593a5SAlan Somers } else 14613d593a5SAlan Somers return (void*)(intptr_t)errno; 14713d593a5SAlan Somers } 14813d593a5SAlan Somers 14913d593a5SAlan Somers static void* setattr_th(void* arg) { 15013d593a5SAlan Somers int fd; 15113d593a5SAlan Somers ssize_t r; 15213d593a5SAlan Somers sem_t *sem = (sem_t*) arg; 15313d593a5SAlan Somers 15413d593a5SAlan Somers if (sem) 15513d593a5SAlan Somers sem_wait(sem); 15613d593a5SAlan Somers 15713d593a5SAlan Somers fd = open("mountpoint/some_file.txt", O_RDWR); 15813d593a5SAlan Somers if (fd < 0) 15913d593a5SAlan Somers return (void*)(intptr_t)errno; 16013d593a5SAlan Somers 16113d593a5SAlan Somers r = ftruncate(fd, 15); 1624ac4b126SAlan Somers LastLocalModify::leak(fd); 16313d593a5SAlan Somers if (r >= 0) 16413d593a5SAlan Somers return 0; 16513d593a5SAlan Somers else 16613d593a5SAlan Somers return (void*)(intptr_t)errno; 16713d593a5SAlan Somers } 16813d593a5SAlan Somers 16913d593a5SAlan Somers static void* write_th(void* arg) { 17013d593a5SAlan Somers ssize_t r; 17113d593a5SAlan Somers int fd; 17213d593a5SAlan Somers sem_t *sem = (sem_t*) arg; 17313d593a5SAlan Somers const char BUF[] = "abcdefghijklmn"; 17413d593a5SAlan Somers 17513d593a5SAlan Somers if (sem) 17613d593a5SAlan Somers sem_wait(sem); 17713d593a5SAlan Somers fd = open("mountpoint/some_file.txt", O_RDWR); 17813d593a5SAlan Somers if (fd < 0) 17913d593a5SAlan Somers return (void*)(intptr_t)errno; 18013d593a5SAlan Somers 18113d593a5SAlan Somers r = write(fd, BUF, sizeof(BUF)); 18213d593a5SAlan Somers if (r >= 0) { 18313d593a5SAlan Somers LastLocalModify::leak(fd); 18413d593a5SAlan Somers return 0; 18513d593a5SAlan Somers } else 18613d593a5SAlan Somers return (void*)(intptr_t)errno; 18713d593a5SAlan Somers } 18813d593a5SAlan Somers 18913d593a5SAlan Somers /* 19013d593a5SAlan Somers * VOP_LOOKUP should discard attributes returned by the server if they were 19113d593a5SAlan Somers * modified by another VOP while the VOP_LOOKUP was in progress. 19213d593a5SAlan Somers * 19313d593a5SAlan Somers * Sequence of operations: 19413d593a5SAlan Somers * * Thread 1 calls a mutator like ftruncate, which acquires the vnode lock 19513d593a5SAlan Somers * exclusively. 19613d593a5SAlan Somers * * Thread 2 calls stat, which does VOP_LOOKUP, which sends FUSE_LOOKUP to the 19713d593a5SAlan Somers * server. The server replies with the old file length. Thread 2 blocks 19813d593a5SAlan Somers * waiting for the vnode lock. 19913d593a5SAlan Somers * * Thread 1 sends the mutator operation like FUSE_SETATTR that changes the 20013d593a5SAlan Somers * file's size and updates the attribute cache. Then it releases the vnode 20113d593a5SAlan Somers * lock. 20213d593a5SAlan Somers * * Thread 2 acquires the vnode lock. At this point it must not add the 20313d593a5SAlan Somers * now-stale file size to the attribute cache. 20413d593a5SAlan Somers * 20513d593a5SAlan Somers * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=259071 20613d593a5SAlan Somers */ 20713d593a5SAlan Somers TEST_P(LastLocalModify, lookup) 20813d593a5SAlan Somers { 20913d593a5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 21013d593a5SAlan Somers const char RELPATH[] = "some_file.txt"; 21113d593a5SAlan Somers Sequence seq; 21213d593a5SAlan Somers uint64_t ino = 3; 21313d593a5SAlan Somers uint64_t mutator_unique; 21413d593a5SAlan Somers const uint64_t oldsize = 10; 21513d593a5SAlan Somers const uint64_t newsize = 15; 21613d593a5SAlan Somers pthread_t th0; 21713d593a5SAlan Somers void *thr0_value; 21813d593a5SAlan Somers struct stat sb; 21913d593a5SAlan Somers static sem_t sem; 22013d593a5SAlan Somers Mutator mutator; 22113d593a5SAlan Somers uint32_t mutator_op; 22213d593a5SAlan Somers size_t mutator_size; 22313d593a5SAlan Somers 22413d593a5SAlan Somers mutator = writer_from_str(GetParam()); 22513d593a5SAlan Somers mutator_op = fuse_op_from_mutator(mutator); 22613d593a5SAlan Somers 22713d593a5SAlan Somers ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 22813d593a5SAlan Somers 22913d593a5SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 23013d593a5SAlan Somers .InSequence(seq) 23113d593a5SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 23213d593a5SAlan Somers /* Called by the mutator, caches attributes but not entries */ 23313d593a5SAlan Somers SET_OUT_HEADER_LEN(out, entry); 23413d593a5SAlan Somers out.body.entry.nodeid = ino; 23513d593a5SAlan Somers out.body.entry.attr.size = oldsize; 23613d593a5SAlan Somers out.body.entry.nodeid = ino; 23713d593a5SAlan Somers out.body.entry.attr_valid_nsec = NAP_NS / 2; 23813d593a5SAlan Somers out.body.entry.attr.ino = ino; 23913d593a5SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644; 24013d593a5SAlan Somers }))); 24113d593a5SAlan Somers expect_open(ino, 0, 1); 24213d593a5SAlan Somers EXPECT_CALL(*m_mock, process( 24313d593a5SAlan Somers ResultOf([=](auto in) { 24413d593a5SAlan Somers return (in.header.opcode == mutator_op && 24513d593a5SAlan Somers in.header.nodeid == ino); 24613d593a5SAlan Somers }, Eq(true)), 24713d593a5SAlan Somers _) 24813d593a5SAlan Somers ).InSequence(seq) 24913d593a5SAlan Somers .WillOnce(Invoke([&](auto in, auto &out __unused) { 25013d593a5SAlan Somers /* 25113d593a5SAlan Somers * The mutator changes the file size, but in order to simulate 25213d593a5SAlan Somers * a race, don't reply. Instead, just save the unique for 25313d593a5SAlan Somers * later. 25413d593a5SAlan Somers */ 25513d593a5SAlan Somers mutator_unique = in.header.unique; 25613d593a5SAlan Somers switch(mutator) { 25713d593a5SAlan Somers case VOP_WRITE: 25813d593a5SAlan Somers mutator_size = in.body.write.size; 25913d593a5SAlan Somers break; 26013d593a5SAlan Somers case VOP_COPY_FILE_RANGE: 26113d593a5SAlan Somers mutator_size = in.body.copy_file_range.len; 26213d593a5SAlan Somers break; 26313d593a5SAlan Somers default: 26413d593a5SAlan Somers break; 26513d593a5SAlan Somers } 26613d593a5SAlan Somers /* Allow the lookup thread to proceed */ 26713d593a5SAlan Somers sem_post(&sem); 26813d593a5SAlan Somers })); 26913d593a5SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 27013d593a5SAlan Somers .InSequence(seq) 27113d593a5SAlan Somers .WillOnce(Invoke([&](auto in __unused, auto& out) { 27213d593a5SAlan Somers std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 27313d593a5SAlan Somers std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out); 27413d593a5SAlan Somers 27513d593a5SAlan Somers /* First complete the lookup request, returning the old size */ 27613d593a5SAlan Somers out0->header.unique = in.header.unique; 27713d593a5SAlan Somers SET_OUT_HEADER_LEN(*out0, entry); 27813d593a5SAlan Somers out0->body.entry.attr.mode = S_IFREG | 0644; 27913d593a5SAlan Somers out0->body.entry.nodeid = ino; 28013d593a5SAlan Somers out0->body.entry.entry_valid = UINT64_MAX; 28113d593a5SAlan Somers out0->body.entry.attr_valid = UINT64_MAX; 28213d593a5SAlan Somers out0->body.entry.attr.size = oldsize; 28313d593a5SAlan Somers out.push_back(std::move(out0)); 28413d593a5SAlan Somers 28513d593a5SAlan Somers /* Then, respond to the mutator request */ 28613d593a5SAlan Somers out1->header.unique = mutator_unique; 28713d593a5SAlan Somers switch(mutator) { 288398c88c7SAlan Somers case VOP_ALLOCATE: 289398c88c7SAlan Somers out1->header.error = 0; 290398c88c7SAlan Somers out1->header.len = sizeof(out1->header); 291398c88c7SAlan Somers break; 292398c88c7SAlan Somers case VOP_COPY_FILE_RANGE: 293398c88c7SAlan Somers SET_OUT_HEADER_LEN(*out1, write); 294398c88c7SAlan Somers out1->body.write.size = mutator_size; 295398c88c7SAlan Somers break; 29613d593a5SAlan Somers case VOP_SETATTR: 29713d593a5SAlan Somers SET_OUT_HEADER_LEN(*out1, attr); 29813d593a5SAlan Somers out1->body.attr.attr.ino = ino; 29913d593a5SAlan Somers out1->body.attr.attr.mode = S_IFREG | 0644; 30013d593a5SAlan Somers out1->body.attr.attr.size = newsize; // Changed size 30113d593a5SAlan Somers out1->body.attr.attr_valid = UINT64_MAX; 30213d593a5SAlan Somers break; 30313d593a5SAlan Somers case VOP_WRITE: 30413d593a5SAlan Somers SET_OUT_HEADER_LEN(*out1, write); 30513d593a5SAlan Somers out1->body.write.size = mutator_size; 30613d593a5SAlan Somers break; 30713d593a5SAlan Somers } 30813d593a5SAlan Somers out.push_back(std::move(out1)); 30913d593a5SAlan Somers })); 31013d593a5SAlan Somers 31113d593a5SAlan Somers /* Start the mutator thread */ 31213d593a5SAlan Somers switch(mutator) { 313398c88c7SAlan Somers case VOP_ALLOCATE: 314398c88c7SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, allocate_th, 315398c88c7SAlan Somers NULL)) << strerror(errno); 316398c88c7SAlan Somers break; 317398c88c7SAlan Somers case VOP_COPY_FILE_RANGE: 318398c88c7SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th, 319398c88c7SAlan Somers NULL)) << strerror(errno); 320398c88c7SAlan Somers break; 32113d593a5SAlan Somers case VOP_SETATTR: 32213d593a5SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th, NULL)) 32313d593a5SAlan Somers << strerror(errno); 32413d593a5SAlan Somers break; 32513d593a5SAlan Somers case VOP_WRITE: 32613d593a5SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, NULL)) 32713d593a5SAlan Somers << strerror(errno); 32813d593a5SAlan Somers break; 32913d593a5SAlan Somers } 33013d593a5SAlan Somers 33113d593a5SAlan Somers 33213d593a5SAlan Somers /* Wait for FUSE_SETATTR to be sent */ 33313d593a5SAlan Somers sem_wait(&sem); 33413d593a5SAlan Somers 33513d593a5SAlan Somers /* Lookup again, which will race with setattr */ 33613d593a5SAlan Somers ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 33713d593a5SAlan Somers ASSERT_EQ((off_t)newsize, sb.st_size); 33813d593a5SAlan Somers 33913d593a5SAlan Somers /* ftruncate should've completed without error */ 34013d593a5SAlan Somers pthread_join(th0, &thr0_value); 34113d593a5SAlan Somers EXPECT_EQ(0, (intptr_t)thr0_value); 34213d593a5SAlan Somers } 34313d593a5SAlan Somers 34413d593a5SAlan Somers /* 34513d593a5SAlan Somers * VFS_VGET should discard attributes returned by the server if they were 34613d593a5SAlan Somers * modified by another VOP while the VFS_VGET was in progress. 34713d593a5SAlan Somers * 34813d593a5SAlan Somers * Sequence of operations: 34913d593a5SAlan Somers * * Thread 1 calls fhstat, entering VFS_VGET, and issues FUSE_LOOKUP 35013d593a5SAlan Somers * * Thread 2 calls a mutator like ftruncate, which acquires the vnode lock 35113d593a5SAlan Somers * exclusively and issues a FUSE op like FUSE_SETATTR. 35213d593a5SAlan Somers * * Thread 1's FUSE_LOOKUP returns with the old size, but the thread blocks 35313d593a5SAlan Somers * waiting for the vnode lock. 35413d593a5SAlan Somers * * Thread 2's FUSE op returns, and that thread sets the file's new size 35513d593a5SAlan Somers * in the attribute cache. Finally it releases the vnode lock. 35613d593a5SAlan Somers * * The vnode lock acquired, thread 1 must not overwrite the attr cache's size 35713d593a5SAlan Somers * with the old value. 35813d593a5SAlan Somers * 35913d593a5SAlan Somers * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=259071 36013d593a5SAlan Somers */ 36113d593a5SAlan Somers TEST_P(LastLocalModify, vfs_vget) 36213d593a5SAlan Somers { 36313d593a5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 36413d593a5SAlan Somers const char RELPATH[] = "some_file.txt"; 36513d593a5SAlan Somers Sequence seq; 36613d593a5SAlan Somers uint64_t ino = 3; 36713d593a5SAlan Somers uint64_t lookup_unique; 36813d593a5SAlan Somers const uint64_t oldsize = 10; 36913d593a5SAlan Somers const uint64_t newsize = 15; 37013d593a5SAlan Somers pthread_t th0; 37113d593a5SAlan Somers void *thr0_value; 37213d593a5SAlan Somers struct stat sb; 37313d593a5SAlan Somers static sem_t sem; 37413d593a5SAlan Somers fhandle_t fhp; 37513d593a5SAlan Somers Mutator mutator; 37613d593a5SAlan Somers uint32_t mutator_op; 37713d593a5SAlan Somers 37813d593a5SAlan Somers if (geteuid() != 0) 37913d593a5SAlan Somers GTEST_SKIP() << "This test requires a privileged user"; 38013d593a5SAlan Somers 38113d593a5SAlan Somers mutator = writer_from_str(GetParam()); 38213d593a5SAlan Somers mutator_op = fuse_op_from_mutator(mutator); 38313d593a5SAlan Somers 38413d593a5SAlan Somers ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 38513d593a5SAlan Somers 38613d593a5SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 38713d593a5SAlan Somers .Times(1) 38813d593a5SAlan Somers .InSequence(seq) 38913d593a5SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) 39013d593a5SAlan Somers { 39113d593a5SAlan Somers /* Called by getfh, caches attributes but not entries */ 39213d593a5SAlan Somers SET_OUT_HEADER_LEN(out, entry); 39313d593a5SAlan Somers out.body.entry.nodeid = ino; 39413d593a5SAlan Somers out.body.entry.attr.size = oldsize; 39513d593a5SAlan Somers out.body.entry.nodeid = ino; 39613d593a5SAlan Somers out.body.entry.attr_valid_nsec = NAP_NS / 2; 39713d593a5SAlan Somers out.body.entry.attr.ino = ino; 39813d593a5SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644; 39913d593a5SAlan Somers }))); 40013d593a5SAlan Somers EXPECT_LOOKUP(ino, ".") 40113d593a5SAlan Somers .InSequence(seq) 40213d593a5SAlan Somers .WillOnce(Invoke([&](auto in, auto &out __unused) { 40313d593a5SAlan Somers /* Called by fhstat. Block to simulate a race */ 40413d593a5SAlan Somers lookup_unique = in.header.unique; 40513d593a5SAlan Somers sem_post(&sem); 40613d593a5SAlan Somers })); 40713d593a5SAlan Somers 40813d593a5SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 40913d593a5SAlan Somers .Times(1) 41013d593a5SAlan Somers .InSequence(seq) 41113d593a5SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) 41213d593a5SAlan Somers { 41313d593a5SAlan Somers /* Called by VOP_SETATTR, caches attributes but not entries */ 41413d593a5SAlan Somers SET_OUT_HEADER_LEN(out, entry); 41513d593a5SAlan Somers out.body.entry.nodeid = ino; 41613d593a5SAlan Somers out.body.entry.attr.size = oldsize; 41713d593a5SAlan Somers out.body.entry.nodeid = ino; 41813d593a5SAlan Somers out.body.entry.attr_valid_nsec = NAP_NS / 2; 41913d593a5SAlan Somers out.body.entry.attr.ino = ino; 42013d593a5SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644; 42113d593a5SAlan Somers }))); 42213d593a5SAlan Somers 42313d593a5SAlan Somers /* Called by the mutator thread */ 42413d593a5SAlan Somers expect_open(ino, 0, 1); 42513d593a5SAlan Somers 42613d593a5SAlan Somers EXPECT_CALL(*m_mock, process( 42713d593a5SAlan Somers ResultOf([=](auto in) { 42813d593a5SAlan Somers return (in.header.opcode == mutator_op && 42913d593a5SAlan Somers in.header.nodeid == ino); 43013d593a5SAlan Somers }, Eq(true)), 43113d593a5SAlan Somers _) 43213d593a5SAlan Somers ).InSequence(seq) 43313d593a5SAlan Somers .WillOnce(Invoke([&](auto in __unused, auto& out) { 43413d593a5SAlan Somers std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 43513d593a5SAlan Somers std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out); 43613d593a5SAlan Somers 43713d593a5SAlan Somers /* First complete the lookup request, returning the old size */ 43813d593a5SAlan Somers out0->header.unique = lookup_unique; 43913d593a5SAlan Somers SET_OUT_HEADER_LEN(*out0, entry); 44013d593a5SAlan Somers out0->body.entry.attr.mode = S_IFREG | 0644; 44113d593a5SAlan Somers out0->body.entry.nodeid = ino; 44213d593a5SAlan Somers out0->body.entry.entry_valid = UINT64_MAX; 44313d593a5SAlan Somers out0->body.entry.attr_valid = UINT64_MAX; 44413d593a5SAlan Somers out0->body.entry.attr.size = oldsize; 44513d593a5SAlan Somers out.push_back(std::move(out0)); 44613d593a5SAlan Somers 44713d593a5SAlan Somers /* Then, respond to the mutator request */ 44813d593a5SAlan Somers out1->header.unique = in.header.unique; 44913d593a5SAlan Somers switch(mutator) { 450398c88c7SAlan Somers case VOP_ALLOCATE: 451398c88c7SAlan Somers out1->header.error = 0; 452398c88c7SAlan Somers out1->header.len = sizeof(out1->header); 453398c88c7SAlan Somers break; 454398c88c7SAlan Somers case VOP_COPY_FILE_RANGE: 455398c88c7SAlan Somers SET_OUT_HEADER_LEN(*out1, write); 456398c88c7SAlan Somers out1->body.write.size = in.body.copy_file_range.len; 457398c88c7SAlan Somers break; 45813d593a5SAlan Somers case VOP_SETATTR: 45913d593a5SAlan Somers SET_OUT_HEADER_LEN(*out1, attr); 46013d593a5SAlan Somers out1->body.attr.attr.ino = ino; 46113d593a5SAlan Somers out1->body.attr.attr.mode = S_IFREG | 0644; 46213d593a5SAlan Somers out1->body.attr.attr.size = newsize; // Changed size 46313d593a5SAlan Somers out1->body.attr.attr_valid = UINT64_MAX; 46413d593a5SAlan Somers break; 46513d593a5SAlan Somers case VOP_WRITE: 46613d593a5SAlan Somers SET_OUT_HEADER_LEN(*out1, write); 46713d593a5SAlan Somers out1->body.write.size = in.body.write.size; 46813d593a5SAlan Somers break; 46913d593a5SAlan Somers } 47013d593a5SAlan Somers out.push_back(std::move(out1)); 47113d593a5SAlan Somers })); 47213d593a5SAlan Somers 47313d593a5SAlan Somers /* First get a file handle */ 47413d593a5SAlan Somers ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno); 47513d593a5SAlan Somers 47613d593a5SAlan Somers /* Start the mutator thread */ 47713d593a5SAlan Somers switch(mutator) { 478398c88c7SAlan Somers case VOP_ALLOCATE: 479398c88c7SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, allocate_th, 480398c88c7SAlan Somers (void*)&sem)) << strerror(errno); 481398c88c7SAlan Somers break; 482398c88c7SAlan Somers case VOP_COPY_FILE_RANGE: 483398c88c7SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th, 484398c88c7SAlan Somers (void*)&sem)) << strerror(errno); 485398c88c7SAlan Somers break; 48613d593a5SAlan Somers case VOP_SETATTR: 48713d593a5SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th, 48813d593a5SAlan Somers (void*)&sem)) << strerror(errno); 48913d593a5SAlan Somers break; 49013d593a5SAlan Somers case VOP_WRITE: 49113d593a5SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, (void*)&sem)) 49213d593a5SAlan Somers << strerror(errno); 49313d593a5SAlan Somers break; 49413d593a5SAlan Somers } 49513d593a5SAlan Somers 49613d593a5SAlan Somers /* Lookup again, which will race with setattr */ 49713d593a5SAlan Somers ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno); 49813d593a5SAlan Somers 49913d593a5SAlan Somers ASSERT_EQ((off_t)newsize, sb.st_size); 50013d593a5SAlan Somers 50113d593a5SAlan Somers /* mutator should've completed without error */ 50213d593a5SAlan Somers pthread_join(th0, &thr0_value); 50313d593a5SAlan Somers EXPECT_EQ(0, (intptr_t)thr0_value); 50413d593a5SAlan Somers } 50513d593a5SAlan Somers 50613d593a5SAlan Somers 507*811e0a31SEnji Cooper INSTANTIATE_TEST_SUITE_P(LLM, LastLocalModify, 508398c88c7SAlan Somers Values( 509398c88c7SAlan Somers "VOP_ALLOCATE", 510398c88c7SAlan Somers "VOP_COPY_FILE_RANGE", 511398c88c7SAlan Somers "VOP_SETATTR", 512398c88c7SAlan Somers "VOP_WRITE" 513398c88c7SAlan Somers ) 51413d593a5SAlan Somers ); 515