Changeset View
Changeset View
Standalone View
Standalone View
tests/sys/fs/fusefs/last_local_modify.cc
Show First 20 Lines • Show All 56 Lines • ▼ Show 20 Lines | |||||
* | * | ||||
* A few other operations like FUSE_LINK can also trigger the same race but | * A few other operations like FUSE_LINK can also trigger the same race but | ||||
* with the file's ctime instead of size. However, the consequences of an | * with the file's ctime instead of size. However, the consequences of an | ||||
* incorrect ctime are much less disastrous than an incorrect size, so fusefs | * incorrect ctime are much less disastrous than an incorrect size, so fusefs | ||||
* does not attempt to prevent such races. | * does not attempt to prevent such races. | ||||
*/ | */ | ||||
enum Mutator { | enum Mutator { | ||||
VOP_ALLOCATE, | |||||
VOP_COPY_FILE_RANGE, | |||||
VOP_SETATTR, | VOP_SETATTR, | ||||
VOP_WRITE, | VOP_WRITE, | ||||
VOP_COPY_FILE_RANGE | |||||
}; | }; | ||||
/* | /* | ||||
* Translate a poll method's string representation to the enum value. | * Translate a poll method's string representation to the enum value. | ||||
* Using strings with ::testing::Values gives better output with | * Using strings with ::testing::Values gives better output with | ||||
* --gtest_list_tests | * --gtest_list_tests | ||||
*/ | */ | ||||
enum Mutator writer_from_str(const char* s) { | enum Mutator writer_from_str(const char* s) { | ||||
if (0 == strcmp("VOP_SETATTR", s)) | if (0 == strcmp("VOP_ALLOCATE", s)) | ||||
return VOP_ALLOCATE; | |||||
else if (0 == strcmp("VOP_COPY_FILE_RANGE", s)) | |||||
return VOP_COPY_FILE_RANGE; | |||||
else if (0 == strcmp("VOP_SETATTR", s)) | |||||
return VOP_SETATTR; | return VOP_SETATTR; | ||||
else if (0 == strcmp("VOP_WRITE", s)) | |||||
return VOP_WRITE; | |||||
else | else | ||||
return VOP_COPY_FILE_RANGE; | return VOP_WRITE; | ||||
} | } | ||||
uint32_t fuse_op_from_mutator(enum Mutator mutator) { | uint32_t fuse_op_from_mutator(enum Mutator mutator) { | ||||
switch(mutator) { | switch(mutator) { | ||||
case VOP_ALLOCATE: | |||||
return(FUSE_FALLOCATE); | |||||
case VOP_COPY_FILE_RANGE: | |||||
return(FUSE_COPY_FILE_RANGE); | |||||
case VOP_SETATTR: | case VOP_SETATTR: | ||||
return(FUSE_SETATTR); | return(FUSE_SETATTR); | ||||
case VOP_WRITE: | case VOP_WRITE: | ||||
return(FUSE_WRITE); | return(FUSE_WRITE); | ||||
case VOP_COPY_FILE_RANGE: | |||||
return(FUSE_COPY_FILE_RANGE); | |||||
} | } | ||||
} | } | ||||
class LastLocalModify: public FuseTest, public WithParamInterface<const char*> { | class LastLocalModify: public FuseTest, public WithParamInterface<const char*> { | ||||
public: | public: | ||||
virtual void SetUp() { | virtual void SetUp() { | ||||
m_init_flags = FUSE_EXPORT_SUPPORT; | m_init_flags = FUSE_EXPORT_SUPPORT; | ||||
FuseTest::SetUp(); | FuseTest::SetUp(); | ||||
} | } | ||||
}; | }; | ||||
static void* allocate_th(void* arg) { | |||||
int fd; | |||||
ssize_t r; | |||||
sem_t *sem = (sem_t*) arg; | |||||
if (sem) | |||||
sem_wait(sem); | |||||
fd = open("mountpoint/some_file.txt", O_RDWR); | |||||
if (fd < 0) | |||||
return (void*)(intptr_t)errno; | |||||
r = posix_fallocate(fd, 0, 15); | |||||
if (r >= 0) | |||||
return 0; | |||||
else | |||||
return (void*)(intptr_t)errno; | |||||
} | |||||
static void* copy_file_range_th(void* arg) { | static void* copy_file_range_th(void* arg) { | ||||
ssize_t r; | ssize_t r; | ||||
int fd; | int fd; | ||||
sem_t *sem = (sem_t*) arg; | sem_t *sem = (sem_t*) arg; | ||||
off_t off_in = 0; | off_t off_in = 0; | ||||
off_t off_out = 10; | off_t off_out = 10; | ||||
ssize_t len = 5; | ssize_t len = 5; | ||||
▲ Show 20 Lines • Show All 144 Lines • ▼ Show 20 Lines | .WillOnce(Invoke([&](auto in __unused, auto& out) { | ||||
out0->body.entry.entry_valid = UINT64_MAX; | out0->body.entry.entry_valid = UINT64_MAX; | ||||
out0->body.entry.attr_valid = UINT64_MAX; | out0->body.entry.attr_valid = UINT64_MAX; | ||||
out0->body.entry.attr.size = oldsize; | out0->body.entry.attr.size = oldsize; | ||||
out.push_back(std::move(out0)); | out.push_back(std::move(out0)); | ||||
/* Then, respond to the mutator request */ | /* Then, respond to the mutator request */ | ||||
out1->header.unique = mutator_unique; | out1->header.unique = mutator_unique; | ||||
switch(mutator) { | switch(mutator) { | ||||
case VOP_ALLOCATE: | |||||
out1->header.error = 0; | |||||
out1->header.len = sizeof(out1->header); | |||||
break; | |||||
case VOP_COPY_FILE_RANGE: | |||||
SET_OUT_HEADER_LEN(*out1, write); | |||||
out1->body.write.size = mutator_size; | |||||
break; | |||||
case VOP_SETATTR: | case VOP_SETATTR: | ||||
SET_OUT_HEADER_LEN(*out1, attr); | SET_OUT_HEADER_LEN(*out1, attr); | ||||
out1->body.attr.attr.ino = ino; | out1->body.attr.attr.ino = ino; | ||||
out1->body.attr.attr.mode = S_IFREG | 0644; | out1->body.attr.attr.mode = S_IFREG | 0644; | ||||
out1->body.attr.attr.size = newsize; // Changed size | out1->body.attr.attr.size = newsize; // Changed size | ||||
out1->body.attr.attr_valid = UINT64_MAX; | out1->body.attr.attr_valid = UINT64_MAX; | ||||
break; | break; | ||||
case VOP_WRITE: | case VOP_WRITE: | ||||
SET_OUT_HEADER_LEN(*out1, write); | SET_OUT_HEADER_LEN(*out1, write); | ||||
out1->body.write.size = mutator_size; | out1->body.write.size = mutator_size; | ||||
break; | break; | ||||
case VOP_COPY_FILE_RANGE: | |||||
SET_OUT_HEADER_LEN(*out1, write); | |||||
out1->body.write.size = mutator_size; | |||||
break; | |||||
} | } | ||||
out.push_back(std::move(out1)); | out.push_back(std::move(out1)); | ||||
})); | })); | ||||
/* Start the mutator thread */ | /* Start the mutator thread */ | ||||
switch(mutator) { | switch(mutator) { | ||||
case VOP_ALLOCATE: | |||||
ASSERT_EQ(0, pthread_create(&th0, NULL, allocate_th, | |||||
NULL)) << strerror(errno); | |||||
break; | |||||
case VOP_COPY_FILE_RANGE: | |||||
ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th, | |||||
NULL)) << strerror(errno); | |||||
break; | |||||
case VOP_SETATTR: | case VOP_SETATTR: | ||||
ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th, NULL)) | ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th, NULL)) | ||||
<< strerror(errno); | << strerror(errno); | ||||
break; | break; | ||||
case VOP_WRITE: | case VOP_WRITE: | ||||
ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, NULL)) | ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, NULL)) | ||||
<< strerror(errno); | << strerror(errno); | ||||
break; | break; | ||||
case VOP_COPY_FILE_RANGE: | |||||
ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th, | |||||
NULL)) << strerror(errno); | |||||
break; | |||||
} | } | ||||
/* Wait for FUSE_SETATTR to be sent */ | /* Wait for FUSE_SETATTR to be sent */ | ||||
sem_wait(&sem); | sem_wait(&sem); | ||||
/* Lookup again, which will race with setattr */ | /* Lookup again, which will race with setattr */ | ||||
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); | ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); | ||||
▲ Show 20 Lines • Show All 105 Lines • ▼ Show 20 Lines | .WillOnce(Invoke([&](auto in __unused, auto& out) { | ||||
out0->body.entry.entry_valid = UINT64_MAX; | out0->body.entry.entry_valid = UINT64_MAX; | ||||
out0->body.entry.attr_valid = UINT64_MAX; | out0->body.entry.attr_valid = UINT64_MAX; | ||||
out0->body.entry.attr.size = oldsize; | out0->body.entry.attr.size = oldsize; | ||||
out.push_back(std::move(out0)); | out.push_back(std::move(out0)); | ||||
/* Then, respond to the mutator request */ | /* Then, respond to the mutator request */ | ||||
out1->header.unique = in.header.unique; | out1->header.unique = in.header.unique; | ||||
switch(mutator) { | switch(mutator) { | ||||
case VOP_ALLOCATE: | |||||
out1->header.error = 0; | |||||
out1->header.len = sizeof(out1->header); | |||||
break; | |||||
case VOP_COPY_FILE_RANGE: | |||||
SET_OUT_HEADER_LEN(*out1, write); | |||||
out1->body.write.size = in.body.copy_file_range.len; | |||||
break; | |||||
case VOP_SETATTR: | case VOP_SETATTR: | ||||
SET_OUT_HEADER_LEN(*out1, attr); | SET_OUT_HEADER_LEN(*out1, attr); | ||||
out1->body.attr.attr.ino = ino; | out1->body.attr.attr.ino = ino; | ||||
out1->body.attr.attr.mode = S_IFREG | 0644; | out1->body.attr.attr.mode = S_IFREG | 0644; | ||||
out1->body.attr.attr.size = newsize; // Changed size | out1->body.attr.attr.size = newsize; // Changed size | ||||
out1->body.attr.attr_valid = UINT64_MAX; | out1->body.attr.attr_valid = UINT64_MAX; | ||||
break; | break; | ||||
case VOP_WRITE: | case VOP_WRITE: | ||||
SET_OUT_HEADER_LEN(*out1, write); | SET_OUT_HEADER_LEN(*out1, write); | ||||
out1->body.write.size = in.body.write.size; | out1->body.write.size = in.body.write.size; | ||||
break; | break; | ||||
case VOP_COPY_FILE_RANGE: | |||||
SET_OUT_HEADER_LEN(*out1, write); | |||||
out1->body.write.size = in.body.copy_file_range.len; | |||||
break; | |||||
} | } | ||||
out.push_back(std::move(out1)); | out.push_back(std::move(out1)); | ||||
})); | })); | ||||
/* First get a file handle */ | /* First get a file handle */ | ||||
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno); | ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno); | ||||
/* Start the mutator thread */ | /* Start the mutator thread */ | ||||
switch(mutator) { | switch(mutator) { | ||||
case VOP_ALLOCATE: | |||||
ASSERT_EQ(0, pthread_create(&th0, NULL, allocate_th, | |||||
(void*)&sem)) << strerror(errno); | |||||
break; | |||||
case VOP_COPY_FILE_RANGE: | |||||
ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th, | |||||
(void*)&sem)) << strerror(errno); | |||||
break; | |||||
case VOP_SETATTR: | case VOP_SETATTR: | ||||
ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th, | ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th, | ||||
(void*)&sem)) << strerror(errno); | (void*)&sem)) << strerror(errno); | ||||
break; | break; | ||||
case VOP_WRITE: | case VOP_WRITE: | ||||
ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, (void*)&sem)) | ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, (void*)&sem)) | ||||
<< strerror(errno); | << strerror(errno); | ||||
break; | break; | ||||
case VOP_COPY_FILE_RANGE: | |||||
ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th, | |||||
(void*)&sem)) << strerror(errno); | |||||
break; | |||||
} | } | ||||
/* Lookup again, which will race with setattr */ | /* Lookup again, which will race with setattr */ | ||||
ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno); | ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno); | ||||
ASSERT_EQ((off_t)newsize, sb.st_size); | ASSERT_EQ((off_t)newsize, sb.st_size); | ||||
/* mutator should've completed without error */ | /* mutator should've completed without error */ | ||||
pthread_join(th0, &thr0_value); | pthread_join(th0, &thr0_value); | ||||
EXPECT_EQ(0, (intptr_t)thr0_value); | EXPECT_EQ(0, (intptr_t)thr0_value); | ||||
} | } | ||||
INSTANTIATE_TEST_CASE_P(LLM, LastLocalModify, | INSTANTIATE_TEST_CASE_P(LLM, LastLocalModify, | ||||
Values("VOP_SETATTR", "VOP_WRITE", "VOP_COPY_FILE_RANGE") | Values( | ||||
"VOP_ALLOCATE", | |||||
"VOP_COPY_FILE_RANGE", | |||||
"VOP_SETATTR", | |||||
"VOP_WRITE" | |||||
) | |||||
); | ); |