fusefs: updated cached attributes during VOP_LINK.

FUSE_LINK returns a new set of attributes.  fusefs should cache them just
like it does during other VOPs.  This is not only a matter of performance
but of correctness too; without caching the new attributes the vnode's nlink
value would be out-of-date.

Reported by:	pjdfstest
Sponsored by:	The FreeBSD Foundation
This commit is contained in:
Alan Somers 2019-05-08 18:12:38 +00:00
parent a2bdd7379b
commit 4ae3a56cb1
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/projects/fuse2/; revision=347358
3 changed files with 87 additions and 5 deletions

View File

@ -817,6 +817,9 @@ fuse_vnop_link(struct vop_link_args *ap)
feo = fdi.answ;
err = fuse_internal_checkentry(feo, vnode_vtype(vp));
if (!err)
fuse_internal_cache_attrs(vp, &feo->attr, feo->attr_valid,
feo->attr_valid_nsec, NULL);
out:
fdisp_destroy(&fdi);
return err;

View File

@ -77,26 +77,41 @@ TEST_F(Link, ok)
const char RELPATH[] = "src";
const char FULLDST[] = "mountpoint/dst";
const char RELDST[] = "dst";
uint64_t dst_ino = 42;
const uint64_t ino = 42;
mode_t mode = S_IFREG | 0644;
struct stat sb;
EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_lookup(RELDST, dst_ino);
EXPECT_LOOKUP(1, RELDST)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, entry);
out->body.entry.attr.mode = mode;
out->body.entry.nodeid = ino;
out->body.entry.attr.nlink = 1;
out->body.entry.attr_valid = UINT64_MAX;
out->body.entry.entry_valid = UINT64_MAX;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
const char *name = (const char*)in->body.bytes
+ sizeof(struct fuse_link_in);
return (in->header.opcode == FUSE_LINK &&
in->body.link.oldnodeid == dst_ino &&
in->body.link.oldnodeid == ino &&
(0 == strcmp(name, RELPATH)));
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, entry);
out->body.entry.attr.mode = S_IFREG | 0644;
out->body.entry.attr.mode = mode;
out->body.entry.nodeid = ino;
out->body.entry.attr.nlink = 2;
out->body.entry.attr_valid = UINT64_MAX;
out->body.entry.entry_valid = UINT64_MAX;
})));
EXPECT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno);
ASSERT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno);
// Check that the original file's nlink count has increased.
ASSERT_EQ(0, stat(FULLDST, &sb)) << strerror(errno);
EXPECT_EQ(2ul, sb.st_nlink);
}

View File

@ -131,6 +131,70 @@ TEST_F(Setattr, chmod)
EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
}
/*
* Chmod a multiply-linked file with cached attributes. Check that both files'
* attributes have changed.
*/
TEST_F(Setattr, chmod_multiply_linked)
{
const char FULLPATH0[] = "mountpoint/some_file.txt";
const char RELPATH0[] = "some_file.txt";
const char FULLPATH1[] = "mountpoint/other_file.txt";
const char RELPATH1[] = "other_file.txt";
struct stat sb;
const uint64_t ino = 42;
const mode_t oldmode = 0777;
const mode_t newmode = 0666;
EXPECT_LOOKUP(1, RELPATH0)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, entry);
out->body.entry.attr.mode = S_IFREG | oldmode;
out->body.entry.nodeid = ino;
out->body.entry.attr.nlink = 2;
out->body.entry.attr_valid = UINT64_MAX;
out->body.entry.entry_valid = UINT64_MAX;
})));
EXPECT_LOOKUP(1, RELPATH1)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, entry);
out->body.entry.attr.mode = S_IFREG | oldmode;
out->body.entry.nodeid = ino;
out->body.entry.attr.nlink = 2;
out->body.entry.attr_valid = UINT64_MAX;
out->body.entry.entry_valid = UINT64_MAX;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
uint32_t valid = FATTR_MODE;
return (in->header.opcode == FUSE_SETATTR &&
in->header.nodeid == ino &&
in->body.setattr.valid == valid &&
in->body.setattr.mode == newmode);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, attr);
out->body.attr.attr.ino = ino;
out->body.attr.attr.mode = S_IFREG | newmode;
out->body.attr.attr.nlink = 2;
out->body.attr.attr_valid = UINT64_MAX;
})));
/* For a lookup of the 2nd file to get it into the cache*/
ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
ASSERT_EQ(0, chmod(FULLPATH0, newmode)) << strerror(errno);
ASSERT_EQ(0, stat(FULLPATH0, &sb)) << strerror(errno);
EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
}
/* Change the owner and group of a file */
TEST_F(Setattr, chown)
{