freebsd-dev/tests/sys/file/fspacectl_test.c
Ka Ho Ng 0dc332bff2 Add fspacectl(2), vn_deallocate(9) and VOP_DEALLOCATE(9).
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
2021-08-05 23:20:42 +08:00

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();
}