0dc332bff2
fspacectl(2) is a system call to provide space management support to userspace applications. VOP_DEALLOCATE(9) is a VOP call to perform the deallocation. vn_deallocate(9) is a public KPI for kmods' use. The purpose of proposing a new system call, a KPI and a VOP call is to allow bhyve or other hypervisor monitors to emulate the behavior of SCSI UNMAP/NVMe DEALLOCATE on a plain file. fspacectl(2) comprises of cmd and flags parameters to specify the space management operation to be performed. Currently cmd has to be SPACECTL_DEALLOC, and flags has to be 0. fo_fspacectl is added to fileops. VOP_DEALLOCATE(9) is added as a new VOP call. A trivial implementation of VOP_DEALLOCATE(9) is provided. Sponsored by: The FreeBSD Foundation Reviewed by: kib Differential Revision: https://reviews.freebsd.org/D28347
339 lines
8.7 KiB
C
339 lines
8.7 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2021 The FreeBSD Foundation
|
|
* All rights reserved.
|
|
*
|
|
* This software was developed by Ka Ho Ng under sponsorship from
|
|
* the FreeBSD Foundation.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <atf-c.h>
|
|
#include <fcntl.h>
|
|
#include <malloc.h>
|
|
|
|
static off_t file_max_blocks = 32;
|
|
static const char byte_to_fill = 0x5f;
|
|
|
|
static int
|
|
fill(int fd, off_t offset, off_t len)
|
|
{
|
|
int error;
|
|
size_t blen;
|
|
char *buf;
|
|
struct stat statbuf;
|
|
blksize_t blocksize;
|
|
|
|
if (fstat(fd, &statbuf) == -1)
|
|
return (1);
|
|
blocksize = statbuf.st_blksize;
|
|
error = 0;
|
|
buf = malloc(blocksize);
|
|
if (buf == NULL)
|
|
return (1);
|
|
|
|
while (len > 0) {
|
|
blen = len < (off_t)blocksize ? len : blocksize;
|
|
memset(buf, byte_to_fill, blen);
|
|
if (pwrite(fd, buf, blen, offset) != (ssize_t)blen) {
|
|
error = 1;
|
|
break;
|
|
}
|
|
len -= blen;
|
|
offset += blen;
|
|
}
|
|
|
|
free(buf);
|
|
return (error);
|
|
}
|
|
|
|
static blksize_t
|
|
fd_get_blksize(void)
|
|
{
|
|
struct statfs statfsbuf;
|
|
|
|
if (statfs(".", &statfsbuf) == -1)
|
|
return (-1);
|
|
return statfsbuf.f_iosize;
|
|
}
|
|
|
|
static int
|
|
check_content_dealloc(int fd, off_t hole_start, off_t hole_len, off_t file_sz)
|
|
{
|
|
int error;
|
|
size_t blen;
|
|
off_t offset, resid;
|
|
struct stat statbuf;
|
|
char *buf, *sblk;
|
|
blksize_t blocksize;
|
|
|
|
blocksize = fd_get_blksize();
|
|
if (blocksize == -1)
|
|
return (1);
|
|
error = 0;
|
|
buf = malloc(blocksize * 2);
|
|
if (buf == NULL)
|
|
return (1);
|
|
sblk = buf + blocksize;
|
|
|
|
memset(sblk, 0, blocksize);
|
|
|
|
if ((uint64_t)hole_start + hole_len > (uint64_t)file_sz)
|
|
hole_len = file_sz - hole_start;
|
|
|
|
/*
|
|
* Check hole is zeroed.
|
|
*/
|
|
offset = hole_start;
|
|
resid = hole_len;
|
|
while (resid > 0) {
|
|
blen = resid < (off_t)blocksize ? resid : blocksize;
|
|
if (pread(fd, buf, blen, offset) != (ssize_t)blen) {
|
|
error = 1;
|
|
break;
|
|
}
|
|
if (memcmp(buf, sblk, blen) != 0) {
|
|
error = 1;
|
|
break;
|
|
}
|
|
resid -= blen;
|
|
offset += blen;
|
|
}
|
|
|
|
memset(sblk, byte_to_fill, blocksize);
|
|
|
|
/*
|
|
* Check file region before hole is zeroed.
|
|
*/
|
|
offset = 0;
|
|
resid = hole_start;
|
|
while (resid > 0) {
|
|
blen = resid < (off_t)blocksize ? resid : blocksize;
|
|
if (pread(fd, buf, blen, offset) != (ssize_t)blen) {
|
|
error = 1;
|
|
break;
|
|
}
|
|
if (memcmp(buf, sblk, blen) != 0) {
|
|
error = 1;
|
|
break;
|
|
}
|
|
resid -= blen;
|
|
offset += blen;
|
|
}
|
|
|
|
/*
|
|
* Check file region after hole is zeroed.
|
|
*/
|
|
offset = hole_start + hole_len;
|
|
resid = file_sz - offset;
|
|
while (resid > 0) {
|
|
blen = resid < (off_t)blocksize ? resid : blocksize;
|
|
if (pread(fd, buf, blen, offset) != (ssize_t)blen) {
|
|
error = 1;
|
|
break;
|
|
}
|
|
if (memcmp(buf, sblk, blen) != 0) {
|
|
error = 1;
|
|
break;
|
|
}
|
|
resid -= blen;
|
|
offset += blen;
|
|
}
|
|
|
|
/*
|
|
* Check file size matches with expected file size.
|
|
*/
|
|
if (fstat(fd, &statbuf) == -1)
|
|
error = -1;
|
|
if (statbuf.st_size != file_sz)
|
|
error = -1;
|
|
|
|
free(buf);
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Check aligned deallocation
|
|
*/
|
|
ATF_TC_WITHOUT_HEAD(aligned_dealloc);
|
|
ATF_TC_BODY(aligned_dealloc, tc)
|
|
{
|
|
struct spacectl_range range;
|
|
off_t offset, length;
|
|
blksize_t blocksize;
|
|
int fd;
|
|
|
|
ATF_REQUIRE((blocksize = fd_get_blksize()) != -1);
|
|
range.r_offset = offset = blocksize;
|
|
range.r_len = length = (file_max_blocks - 1) * blocksize -
|
|
range.r_offset;
|
|
|
|
ATF_REQUIRE((fd = open("sys_fspacectl_testfile",
|
|
O_CREAT | O_RDWR | O_TRUNC, 0600)) != -1);
|
|
ATF_REQUIRE(fill(fd, 0, file_max_blocks * blocksize) == 0);
|
|
ATF_CHECK(fspacectl(fd, SPACECTL_DEALLOC, &range, 0, &range) == 0);
|
|
ATF_CHECK(check_content_dealloc(fd, offset, length,
|
|
file_max_blocks * blocksize) == 0);
|
|
ATF_REQUIRE(close(fd) == 0);
|
|
}
|
|
|
|
/*
|
|
* Check unaligned deallocation
|
|
*/
|
|
ATF_TC_WITHOUT_HEAD(unaligned_dealloc);
|
|
ATF_TC_BODY(unaligned_dealloc, tc)
|
|
{
|
|
struct spacectl_range range;
|
|
off_t offset, length;
|
|
blksize_t blocksize;
|
|
int fd;
|
|
|
|
ATF_REQUIRE((blocksize = fd_get_blksize()) != -1);
|
|
range.r_offset = offset = blocksize / 2;
|
|
range.r_len = length = (file_max_blocks - 1) * blocksize +
|
|
blocksize / 2 - offset;
|
|
|
|
ATF_REQUIRE((fd = open("sys_fspacectl_testfile",
|
|
O_CREAT | O_RDWR | O_TRUNC, 0600)) != -1);
|
|
ATF_REQUIRE(fill(fd, 0, file_max_blocks * blocksize) == 0);
|
|
ATF_CHECK(fspacectl(fd, SPACECTL_DEALLOC, &range, 0, &range) == 0);
|
|
ATF_CHECK(check_content_dealloc(fd, offset, length,
|
|
file_max_blocks * blocksize) == 0);
|
|
ATF_REQUIRE(close(fd) == 0);
|
|
}
|
|
|
|
/*
|
|
* Check aligned deallocation from certain offset to OFF_MAX
|
|
*/
|
|
ATF_TC_WITHOUT_HEAD(aligned_dealloc_offmax);
|
|
ATF_TC_BODY(aligned_dealloc_offmax, tc)
|
|
{
|
|
struct spacectl_range range;
|
|
off_t offset, length;
|
|
blksize_t blocksize;
|
|
int fd;
|
|
|
|
ATF_REQUIRE((blocksize = fd_get_blksize()) != -1);
|
|
range.r_offset = offset = blocksize;
|
|
range.r_len = length = OFF_MAX - offset;
|
|
|
|
ATF_REQUIRE((fd = open("sys_fspacectl_testfile",
|
|
O_CREAT | O_RDWR | O_TRUNC, 0600)) != -1);
|
|
ATF_REQUIRE(fill(fd, 0, file_max_blocks * blocksize) == 0);
|
|
ATF_CHECK(fspacectl(fd, SPACECTL_DEALLOC, &range, 0, &range) == 0);
|
|
ATF_CHECK(check_content_dealloc(fd, offset, length,
|
|
file_max_blocks * blocksize) == 0);
|
|
ATF_REQUIRE(close(fd) == 0);
|
|
}
|
|
|
|
/*
|
|
* Check unaligned deallocation from certain offset to OFF_MAX
|
|
*/
|
|
ATF_TC_WITHOUT_HEAD(unaligned_dealloc_offmax);
|
|
ATF_TC_BODY(unaligned_dealloc_offmax, tc)
|
|
{
|
|
struct spacectl_range range;
|
|
off_t offset, length;
|
|
blksize_t blocksize;
|
|
int fd;
|
|
|
|
ATF_REQUIRE((blocksize = fd_get_blksize()) != -1);
|
|
range.r_offset = offset = blocksize / 2;
|
|
range.r_len = length = OFF_MAX - offset;
|
|
|
|
ATF_REQUIRE((fd = open("sys_fspacectl_testfile",
|
|
O_CREAT | O_RDWR | O_TRUNC, 0600)) != -1);
|
|
ATF_REQUIRE(fill(fd, 0, file_max_blocks * blocksize) == 0);
|
|
ATF_CHECK(fspacectl(fd, SPACECTL_DEALLOC, &range, 0, &range) == 0);
|
|
ATF_CHECK(check_content_dealloc(fd, offset, length,
|
|
file_max_blocks * blocksize) == 0);
|
|
ATF_REQUIRE(close(fd) == 0);
|
|
}
|
|
|
|
/*
|
|
* Check aligned deallocation around EOF
|
|
*/
|
|
ATF_TC_WITHOUT_HEAD(aligned_dealloc_eof);
|
|
ATF_TC_BODY(aligned_dealloc_eof, tc)
|
|
{
|
|
struct spacectl_range range;
|
|
off_t offset, length;
|
|
blksize_t blocksize;
|
|
int fd;
|
|
|
|
ATF_REQUIRE((blocksize = fd_get_blksize()) != -1);
|
|
range.r_offset = offset = blocksize;
|
|
range.r_len = length = (file_max_blocks + 1) * blocksize -
|
|
range.r_offset;
|
|
|
|
ATF_REQUIRE((fd = open("sys_fspacectl_testfile",
|
|
O_CREAT | O_RDWR | O_TRUNC, 0600)) != -1);
|
|
ATF_REQUIRE(fill(fd, 0, file_max_blocks * blocksize) == 0);
|
|
ATF_CHECK(fspacectl(fd, SPACECTL_DEALLOC, &range, 0, &range) == 0);
|
|
ATF_CHECK(check_content_dealloc(fd, offset, length,
|
|
file_max_blocks * blocksize) == 0);
|
|
ATF_REQUIRE(close(fd) == 0);
|
|
}
|
|
|
|
/*
|
|
* Check unaligned deallocation around EOF
|
|
*/
|
|
ATF_TC_WITHOUT_HEAD(unaligned_dealloc_eof);
|
|
ATF_TC_BODY(unaligned_dealloc_eof, tc)
|
|
{
|
|
struct spacectl_range range;
|
|
off_t offset, length;
|
|
blksize_t blocksize;
|
|
int fd;
|
|
|
|
ATF_REQUIRE((blocksize = fd_get_blksize()) != -1);
|
|
range.r_offset = offset = blocksize / 2;
|
|
range.r_len = length = file_max_blocks * blocksize + blocksize / 2 -
|
|
range.r_offset;
|
|
|
|
ATF_REQUIRE((fd = open("sys_fspacectl_testfile",
|
|
O_CREAT | O_RDWR | O_TRUNC, 0600)) != -1);
|
|
ATF_REQUIRE(fill(fd, 0, file_max_blocks * blocksize) == 0);
|
|
ATF_CHECK(fspacectl(fd, SPACECTL_DEALLOC, &range, 0, &range) == 0);
|
|
ATF_CHECK(check_content_dealloc(fd, offset, length,
|
|
file_max_blocks * blocksize) == 0);
|
|
ATF_REQUIRE(close(fd) == 0);
|
|
}
|
|
|
|
ATF_TP_ADD_TCS(tp)
|
|
{
|
|
ATF_TP_ADD_TC(tp, aligned_dealloc);
|
|
ATF_TP_ADD_TC(tp, unaligned_dealloc);
|
|
ATF_TP_ADD_TC(tp, aligned_dealloc_eof);
|
|
ATF_TP_ADD_TC(tp, unaligned_dealloc_eof);
|
|
ATF_TP_ADD_TC(tp, aligned_dealloc_offmax);
|
|
ATF_TP_ADD_TC(tp, unaligned_dealloc_offmax);
|
|
|
|
return atf_no_error();
|
|
}
|