copy_file_range: truncate write if it would exceed RLIMIT_FSIZE
PR: 266611 MFC after: 2 weeks Reviewed by: kib Differential Revision: https://reviews.freebsd.org/D36706
This commit is contained in:
parent
0b9bc97342
commit
52360ca32f
@ -811,6 +811,7 @@ fuse_vnop_copy_file_range(struct vop_copy_file_range_args *ap)
|
||||
struct thread *td;
|
||||
struct uio io;
|
||||
off_t outfilesize;
|
||||
ssize_t r = 0;
|
||||
pid_t pid;
|
||||
int err;
|
||||
|
||||
@ -858,11 +859,11 @@ fuse_vnop_copy_file_range(struct vop_copy_file_range_args *ap)
|
||||
if (err)
|
||||
goto unlock;
|
||||
|
||||
io.uio_resid = *ap->a_lenp;
|
||||
if (ap->a_fsizetd) {
|
||||
io.uio_offset = *ap->a_outoffp;
|
||||
io.uio_resid = *ap->a_lenp;
|
||||
err = vn_rlimit_fsize(outvp, &io, ap->a_fsizetd);
|
||||
if (err)
|
||||
err = vn_rlimit_fsizex(outvp, &io, 0, &r, ap->a_fsizetd);
|
||||
if (err != 0)
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
@ -871,7 +872,7 @@ fuse_vnop_copy_file_range(struct vop_copy_file_range_args *ap)
|
||||
goto unlock;
|
||||
|
||||
err = fuse_inval_buf_range(outvp, outfilesize, *ap->a_outoffp,
|
||||
*ap->a_outoffp + *ap->a_lenp);
|
||||
*ap->a_outoffp + io.uio_resid);
|
||||
if (err)
|
||||
goto unlock;
|
||||
|
||||
@ -883,7 +884,7 @@ fuse_vnop_copy_file_range(struct vop_copy_file_range_args *ap)
|
||||
fcfri->nodeid_out = VTOI(outvp);
|
||||
fcfri->fh_out = outfufh->fh_id;
|
||||
fcfri->off_out = *ap->a_outoffp;
|
||||
fcfri->len = *ap->a_lenp;
|
||||
fcfri->len = io.uio_resid;
|
||||
fcfri->flags = 0;
|
||||
|
||||
err = fdisp_wait_answ(&fdi);
|
||||
@ -915,6 +916,10 @@ fuse_vnop_copy_file_range(struct vop_copy_file_range_args *ap)
|
||||
ap->a_incred, ap->a_outcred, ap->a_fsizetd);
|
||||
}
|
||||
|
||||
/*
|
||||
* No need to call vn_rlimit_fsizex_res before return, since the uio is
|
||||
* local.
|
||||
*/
|
||||
return (err);
|
||||
}
|
||||
|
||||
|
@ -3889,6 +3889,7 @@ nfs_copy_file_range(struct vop_copy_file_range_args *ap)
|
||||
struct uio io;
|
||||
struct nfsmount *nmp;
|
||||
size_t len, len2;
|
||||
ssize_t r;
|
||||
int error, inattrflag, outattrflag, ret, ret2;
|
||||
off_t inoff, outoff;
|
||||
bool consecutive, must_commit, tryoutcred;
|
||||
@ -3937,7 +3938,12 @@ nfs_copy_file_range(struct vop_copy_file_range_args *ap)
|
||||
*/
|
||||
io.uio_offset = *ap->a_outoffp;
|
||||
io.uio_resid = *ap->a_lenp;
|
||||
error = vn_rlimit_fsize(outvp, &io, ap->a_fsizetd);
|
||||
error = vn_rlimit_fsizex(outvp, &io, 0, &r, ap->a_fsizetd);
|
||||
*ap->a_lenp = io.uio_resid;
|
||||
/*
|
||||
* No need to call vn_rlimit_fsizex_res before return, since the uio is
|
||||
* local.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Flush the input file so that the data is up to date before
|
||||
|
@ -3261,12 +3261,11 @@ vn_generic_copy_file_range(struct vnode *invp, off_t *inoffp,
|
||||
{
|
||||
struct vattr va, inva;
|
||||
struct mount *mp;
|
||||
struct uio io;
|
||||
off_t startoff, endoff, xfer, xfer2;
|
||||
u_long blksize;
|
||||
int error, interrupted;
|
||||
bool cantseek, readzeros, eof, lastblock, holetoeof;
|
||||
ssize_t aresid;
|
||||
ssize_t aresid, r = 0;
|
||||
size_t copylen, len, rem, savlen;
|
||||
char *dat;
|
||||
long holein, holeout;
|
||||
@ -3295,13 +3294,20 @@ vn_generic_copy_file_range(struct vnode *invp, off_t *inoffp,
|
||||
error = vn_lock(outvp, LK_EXCLUSIVE);
|
||||
if (error == 0) {
|
||||
/*
|
||||
* If fsize_td != NULL, do a vn_rlimit_fsize() call,
|
||||
* If fsize_td != NULL, do a vn_rlimit_fsizex() call,
|
||||
* now that outvp is locked.
|
||||
*/
|
||||
if (fsize_td != NULL) {
|
||||
struct uio io;
|
||||
|
||||
io.uio_offset = *outoffp;
|
||||
io.uio_resid = len;
|
||||
error = vn_rlimit_fsize(outvp, &io, fsize_td);
|
||||
error = vn_rlimit_fsizex(outvp, &io, 0, &r, fsize_td);
|
||||
len = savlen = io.uio_resid;
|
||||
/*
|
||||
* No need to call vn_rlimit_fsizex_res before return,
|
||||
* since the uio is local.
|
||||
*/
|
||||
}
|
||||
if (VOP_PATHCONF(outvp, _PC_MIN_HOLE_SIZE, &holeout) != 0)
|
||||
holeout = 0;
|
||||
|
@ -44,22 +44,6 @@ using namespace testing;
|
||||
|
||||
class CopyFileRange: public FuseTest {
|
||||
public:
|
||||
static sig_atomic_t s_sigxfsz;
|
||||
|
||||
void SetUp() {
|
||||
s_sigxfsz = 0;
|
||||
FuseTest::SetUp();
|
||||
}
|
||||
|
||||
void TearDown() {
|
||||
struct sigaction sa;
|
||||
|
||||
bzero(&sa, sizeof(sa));
|
||||
sa.sa_handler = SIG_DFL;
|
||||
sigaction(SIGXFSZ, &sa, NULL);
|
||||
|
||||
FuseTest::TearDown();
|
||||
}
|
||||
|
||||
void expect_maybe_lseek(uint64_t ino)
|
||||
{
|
||||
@ -114,12 +98,6 @@ void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
|
||||
|
||||
};
|
||||
|
||||
sig_atomic_t CopyFileRange::s_sigxfsz = 0;
|
||||
|
||||
void sigxfsz_handler(int __unused sig) {
|
||||
CopyFileRange::s_sigxfsz = 1;
|
||||
}
|
||||
|
||||
|
||||
class CopyFileRange_7_27: public CopyFileRange {
|
||||
public:
|
||||
@ -137,6 +115,37 @@ virtual void SetUp() {
|
||||
}
|
||||
};
|
||||
|
||||
class CopyFileRangeRlimitFsize: public CopyFileRange {
|
||||
public:
|
||||
static sig_atomic_t s_sigxfsz;
|
||||
struct rlimit m_initial_limit;
|
||||
|
||||
virtual void SetUp() {
|
||||
s_sigxfsz = 0;
|
||||
getrlimit(RLIMIT_FSIZE, &m_initial_limit);
|
||||
CopyFileRange::SetUp();
|
||||
}
|
||||
|
||||
void TearDown() {
|
||||
struct sigaction sa;
|
||||
|
||||
setrlimit(RLIMIT_FSIZE, &m_initial_limit);
|
||||
|
||||
bzero(&sa, sizeof(sa));
|
||||
sa.sa_handler = SIG_DFL;
|
||||
sigaction(SIGXFSZ, &sa, NULL);
|
||||
|
||||
FuseTest::TearDown();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
sig_atomic_t CopyFileRangeRlimitFsize::s_sigxfsz = 0;
|
||||
|
||||
void sigxfsz_handler(int __unused sig) {
|
||||
CopyFileRangeRlimitFsize::s_sigxfsz = 1;
|
||||
}
|
||||
|
||||
TEST_F(CopyFileRange, eio)
|
||||
{
|
||||
const char FULLPATH1[] = "mountpoint/src.txt";
|
||||
@ -313,8 +322,11 @@ TEST_F(CopyFileRange, fallback)
|
||||
ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
|
||||
}
|
||||
|
||||
/* fusefs should respect RLIMIT_FSIZE */
|
||||
TEST_F(CopyFileRange, rlimit_fsize)
|
||||
/*
|
||||
* copy_file_range should send SIGXFSZ and return EFBIG when the operation
|
||||
* would exceed the limit imposed by RLIMIT_FSIZE.
|
||||
*/
|
||||
TEST_F(CopyFileRangeRlimitFsize, signal)
|
||||
{
|
||||
const char FULLPATH1[] = "mountpoint/src.txt";
|
||||
const char RELPATH1[] = "src.txt";
|
||||
@ -344,7 +356,7 @@ TEST_F(CopyFileRange, rlimit_fsize)
|
||||
).Times(0);
|
||||
|
||||
rl.rlim_cur = fsize2;
|
||||
rl.rlim_max = 10 * fsize2;
|
||||
rl.rlim_max = m_initial_limit.rlim_max;
|
||||
ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
|
||||
ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
|
||||
|
||||
@ -355,6 +367,57 @@ TEST_F(CopyFileRange, rlimit_fsize)
|
||||
EXPECT_EQ(1, s_sigxfsz);
|
||||
}
|
||||
|
||||
/*
|
||||
* When crossing the RLIMIT_FSIZE boundary, writes should be truncated, not
|
||||
* aborted.
|
||||
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=266611
|
||||
*/
|
||||
TEST_F(CopyFileRangeRlimitFsize, truncate)
|
||||
{
|
||||
const char FULLPATH1[] = "mountpoint/src.txt";
|
||||
const char RELPATH1[] = "src.txt";
|
||||
const char FULLPATH2[] = "mountpoint/dst.txt";
|
||||
const char RELPATH2[] = "dst.txt";
|
||||
struct rlimit rl;
|
||||
const uint64_t ino1 = 42;
|
||||
const uint64_t ino2 = 43;
|
||||
const uint64_t fh1 = 0xdeadbeef1a7ebabe;
|
||||
const uint64_t fh2 = 0xdeadc0de88c0ffee;
|
||||
off_t fsize1 = 1 << 20; /* 1 MiB */
|
||||
off_t fsize2 = 1 << 19; /* 512 KiB */
|
||||
off_t start1 = 1 << 18;
|
||||
off_t start2 = fsize2;
|
||||
ssize_t len = 65536;
|
||||
off_t limit = start2 + len / 2;
|
||||
int fd1, fd2;
|
||||
|
||||
expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
|
||||
expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
|
||||
expect_open(ino1, 0, 1, fh1);
|
||||
expect_open(ino2, 0, 1, fh2);
|
||||
EXPECT_CALL(*m_mock, process(
|
||||
ResultOf([=](auto in) {
|
||||
return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
|
||||
(off_t)in.body.copy_file_range.off_out == start2 &&
|
||||
in.body.copy_file_range.len == (size_t)len / 2
|
||||
);
|
||||
}, Eq(true)),
|
||||
_)
|
||||
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
|
||||
SET_OUT_HEADER_LEN(out, write);
|
||||
out.body.write.size = len / 2;
|
||||
})));
|
||||
|
||||
rl.rlim_cur = limit;
|
||||
rl.rlim_max = m_initial_limit.rlim_max;
|
||||
ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
|
||||
ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
|
||||
|
||||
fd1 = open(FULLPATH1, O_RDONLY);
|
||||
fd2 = open(FULLPATH2, O_WRONLY);
|
||||
ASSERT_EQ(len / 2, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
|
||||
}
|
||||
|
||||
TEST_F(CopyFileRange, ok)
|
||||
{
|
||||
const char FULLPATH1[] = "mountpoint/src.txt";
|
||||
|
Loading…
Reference in New Issue
Block a user