From nobody Tue Jul 09 00:03:06 2024 X-Original-To: dev-commits-src-branches@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4WJ1Qy3ZHpz5QwPy; Tue, 09 Jul 2024 00:03:06 +0000 (UTC) (envelope-from git@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) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R11" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4WJ1Qy31XJz42C0; Tue, 9 Jul 2024 00:03:06 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1720483386; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=slNKbB+O5PFeNrjjQ70h3VP+kRL+VBqKTo5/t4DP5Dw=; b=tbEnhuNV4AqzBFtHCDqR/IE/RBYb3rdBawi9+dhsJRf7DB/9EKZ6/D6KAhXJctLpNxNw0R a0p1i1qJZ6ncJEXUe2s1QlSatkHqyyW/3Xudw6Cvmy8p3GUhvaqvKpNkJl+hFKtE9N2dEK KvdOE3+HtrgZpj45E0BROrNNrrDr82J1FzAoDLVk8nf4YkR6QdbGm73aZDjMxRLSQddYQN 89yWuE2iulPITmu9qNgoZyuMQJFPJ3pAn7aRKU99OMVmLilErWxj/HIPnc6GBsSPvIyUBn txOwFEvYYgZI4PloBxeTxhhc1T3BlWLmDO28csTV3aEe5qGpTQ1Hw+zioilZpg== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1720483386; a=rsa-sha256; cv=none; b=nyyvsUKW+b0aTKN5DJeHDeKg2NmiCeAx+1LRUEtPEujQQTglE/B8ejFrYeNazP3jRcTeAP 0ZexnAac1YLkfy5t4AyNFVXQrDkYULvWl//jlZQa7jP5dmhN13QXTUQMy1UY98cohouTy/ FtMOqn+e6rXvJxFBkXWbWqoP77OctAlLGe5DKsUr3aRY6Mkur9Iz/cEqW0gKN7FH/jXDzX NrTosYSuYrXnF9J+iXAsA3RiAXYyFYO3hC2yjPqYUlpZvXWqNdXWqCWaIgtFOxi/Oq7xc9 WoRkCeH835+HFwWcTf+zR01iy4r6FhtNNM4McAxF2WGNaJVDCahUIaq/2oVw4A== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1720483386; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=slNKbB+O5PFeNrjjQ70h3VP+kRL+VBqKTo5/t4DP5Dw=; b=CKJNkldwCd++Q/iZBXyvqiqISYvpovec9w78+w6u0kkLjncJAK8aCHG+kO/kHqRvwliZrN +gY2y9DB2xPzNYtjuG6MN1phtREfcto6I9wbuG5mO2jYQ5DbtnrWInUQ1e086cxdzNvv+E BwRx14oCBQtS+zT3MXj+lkH6OpFCniPqdb5OO0w03qAF8q75tvEsrsykyrO7xipwNeu9MU slEAiiuLxYcpGSza5K4sQoScbGDGaV8qv2pQr/LPRj3fBQNKjAjlaWBJI0QLQzjmyDi3DM LuoLlXdg5EcRpjo6h39Zb7VE+XBeJhADRkgLsqy1ZuQvQVoo/K/daqrdNjfauw== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4WJ1Qy2cLtz1Bg0; Tue, 9 Jul 2024 00:03:06 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.18.1/8.18.1) with ESMTP id 469036hx084111; Tue, 9 Jul 2024 00:03:06 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.18.1/8.18.1/Submit) id 469036XY084108; Tue, 9 Jul 2024 00:03:06 GMT (envelope-from git) Date: Tue, 9 Jul 2024 00:03:06 GMT Message-Id: <202407090003.469036XY084108@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Alan Somers Subject: git: 7f936b0cba08 - stable/13 - fusefs: fix two bugs regarding _PC_MIN_HOLE_SIZE List-Id: Commits to the stable branches of the FreeBSD src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-branches List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-branches@freebsd.org Sender: owner-dev-commits-src-branches@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: asomers X-Git-Repository: src X-Git-Refname: refs/heads/stable/13 X-Git-Reftype: branch X-Git-Commit: 7f936b0cba08315c0127c425692369d0c162fbcf Auto-Submitted: auto-generated The branch stable/13 has been updated by asomers: URL: https://cgit.FreeBSD.org/src/commit/?id=7f936b0cba08315c0127c425692369d0c162fbcf commit 7f936b0cba08315c0127c425692369d0c162fbcf Author: Alan Somers AuthorDate: 2024-04-03 19:57:44 +0000 Commit: Alan Somers CommitDate: 2024-07-08 20:27:54 +0000 fusefs: fix two bugs regarding _PC_MIN_HOLE_SIZE Background: If a user does pathconf(_, _PC_MIN_HOLE_SIZE) on a fusefs file system, the kernel must actually issue a FUSE_LSEEK operation in order to determine whether the server supports it. We cache that result, so we only have to send FUSE_LSEEK the first time that _PC_MIN_HOLE_SIZE is requested on any given mountpoint. Problem 1: Unlike fpathconf, pathconf operates on files that may not be open. But FUSE_LSEEK requires the file to be open. As described in PR 278135, FUSE_LSEEK cannot be sent for unopened files, causing _PC_MIN_HOLE_size to wrongly report EINVAL. We never noticed that before because the fusefs test suite only uses fpathconf, not pathconf. Fix this bug by opening the file if necessary. Problem 2: On a completely sparse file, with no data blocks at all, FUSE_LSEEK with SEEK_DATA would fail to ENXIO. That's correct behavior, but fuse_vnop_pathconf wrongly interpreted that as "FUSE_LSEEK not supported". Fix the interpretation. PR: 278135 Sponsored by: Axcient Differential Revision: https://reviews.freebsd.org/D44618 (cherry picked from commit 6efba04df3f8c77b9b12f1df3e5124a7249b82fc) --- sys/fs/fuse/fuse_vnops.c | 48 ++++++++++++---- tests/sys/fs/fusefs/lseek.cc | 129 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 10 deletions(-) diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c index 75b83c648d38..aa44366ccb95 100644 --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -394,6 +394,9 @@ fuse_vnop_do_lseek(struct vnode *vp, struct thread *td, struct ucred *cred, err = fdisp_wait_answ(&fdi); if (err == ENOSYS) { fsess_set_notimpl(mp, FUSE_LSEEK); + } else if (err == ENXIO) { + /* Note: ENXIO means "no more hole/data regions until EOF" */ + fsess_set_impl(mp, FUSE_LSEEK); } else if (err == 0) { fsess_set_impl(mp, FUSE_LSEEK); flso = fdi.answ; @@ -1770,6 +1773,9 @@ fuse_vnop_pathconf(struct vop_pathconf_args *ap) { struct vnode *vp = ap->a_vp; struct mount *mp; + struct fuse_filehandle *fufh; + int err; + bool closefufh = false; switch (ap->a_name) { case _PC_FILESIZEBITS: @@ -1799,22 +1805,44 @@ fuse_vnop_pathconf(struct vop_pathconf_args *ap) !fsess_not_impl(mp, FUSE_LSEEK)) { off_t offset = 0; - /* Issue a FUSE_LSEEK to find out if it's implemented */ - fuse_vnop_do_lseek(vp, curthread, curthread->td_ucred, - curthread->td_proc->p_pid, &offset, SEEK_DATA); + /* + * Issue a FUSE_LSEEK to find out if it's supported. + * Use SEEK_DATA instead of SEEK_HOLE, because the + * latter generally requires sequential scans of file + * metadata, which can be slow. + */ + err = fuse_vnop_do_lseek(vp, curthread, + curthread->td_ucred, curthread->td_proc->p_pid, + &offset, SEEK_DATA); + if (err == EBADF) { + /* + * pathconf() doesn't necessarily open the + * file. So we may need to do it here. + */ + err = fuse_filehandle_open(vp, FREAD, &fufh, + curthread, curthread->td_ucred); + if (err == 0) { + closefufh = true; + err = fuse_vnop_do_lseek(vp, curthread, + curthread->td_ucred, + curthread->td_proc->p_pid, &offset, + SEEK_DATA); + } + if (closefufh) + fuse_filehandle_close(vp, fufh, + curthread, curthread->td_ucred); + } + } if (fsess_is_impl(mp, FUSE_LSEEK)) { *ap->a_retval = 1; return (0); - } else { - /* - * Probably FUSE_LSEEK is not implemented. It might - * be, if the FUSE_LSEEK above returned an error like - * EACCES, but in that case we can't tell, so it's - * safest to report EINVAL anyway. - */ + } else if (fsess_not_impl(mp, FUSE_LSEEK)) { + /* FUSE_LSEEK is not implemented */ return (EINVAL); + } else { + return (err); } default: return (vop_stdpathconf(ap)); diff --git a/tests/sys/fs/fusefs/lseek.cc b/tests/sys/fs/fusefs/lseek.cc index 5ffeb4b33cbd..2a1cb198bcce 100644 --- a/tests/sys/fs/fusefs/lseek.cc +++ b/tests/sys/fs/fusefs/lseek.cc @@ -112,6 +112,75 @@ TEST_F(LseekPathconf, already_seeked) leak(fd); } +/* + * Use pathconf on a file not already opened. The server returns EACCES when + * the kernel tries to open it. The kernel should return EACCES, and make no + * judgement about whether the server does or does not support FUSE_LSEEK. + */ +TEST_F(LseekPathconf, eacces) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + off_t fsize = 1 << 30; /* 1 GiB */ + + EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { + 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 = ino; + out.body.entry.attr.size = fsize; + }))); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_OPEN && + in.header.nodeid == ino); + }, Eq(true)), + _) + ).Times(2) + .WillRepeatedly(Invoke(ReturnErrno(EACCES))); + + EXPECT_EQ(-1, pathconf(FULLPATH, _PC_MIN_HOLE_SIZE)); + EXPECT_EQ(EACCES, errno); + /* Check again, to ensure that the kernel didn't record the response */ + EXPECT_EQ(-1, pathconf(FULLPATH, _PC_MIN_HOLE_SIZE)); + EXPECT_EQ(EACCES, errno); +} + +/* + * If the server returns some weird error when we try FUSE_LSEEK, send that to + * the caller but don't record the answer. + */ +TEST_F(LseekPathconf, eio) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + off_t fsize = 1 << 30; /* 1 GiB */ + int fd; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); + expect_open(ino, 0, 1); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_LSEEK); + }, Eq(true)), + _) + ).Times(2) + .WillRepeatedly(Invoke(ReturnErrno(EIO))); + + fd = open(FULLPATH, O_RDONLY); + + EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE)); + EXPECT_EQ(EIO, errno); + /* Check again, to ensure that the kernel didn't record the response */ + EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE)); + EXPECT_EQ(EIO, errno); + + leak(fd); +} + /* * If no FUSE_LSEEK operation has been attempted since mount, try once as soon * as a pathconf request comes in. @@ -141,6 +210,34 @@ TEST_F(LseekPathconf, enosys_now) leak(fd); } +/* + * Use pathconf, rather than fpathconf, on a file not already opened. + * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=278135 + */ +TEST_F(LseekPathconf, pathconf) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + off_t fsize = 1 << 30; /* 1 GiB */ + off_t offset_out = 1 << 29; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); + expect_open(ino, 0, 1); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_LSEEK); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { + SET_OUT_HEADER_LEN(out, lseek); + out.body.lseek.offset = offset_out; + }))); + expect_release(ino, FuseTest::FH); + + EXPECT_EQ(1, pathconf(FULLPATH, _PC_MIN_HOLE_SIZE)) << strerror(errno); +} + /* * If no FUSE_LSEEK operation has been attempted since mount, try one as soon * as a pathconf request comes in. This is the typical pattern of bsdtar. It @@ -177,6 +274,38 @@ TEST_F(LseekPathconf, seek_now) leak(fd); } +/* + * If the user calls pathconf(_, _PC_MIN_HOLE_SIZE) on a fully sparse or + * zero-length file, then SEEK_DATA will return ENXIO. That should be + * interpreted as success. + */ +TEST_F(LseekPathconf, zerolength) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + off_t fsize = 0; + int fd; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); + expect_open(ino, 0, 1); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_LSEEK && + in.header.nodeid == ino && + in.body.lseek.whence == SEEK_DATA); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(ENXIO))); + + fd = open(FULLPATH, O_RDONLY); + EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE)); + /* Check again, to ensure that the kernel recorded the response */ + EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE)); + + leak(fd); +} + /* * For servers using older protocol versions, no FUSE_LSEEK should be attempted */