-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
KVM: selftests: Add basic selftest for guest_memfd()
Add a selftest to verify the basic functionality of guest_memfd(): + file descriptor created with the guest_memfd() ioctl does not allow read/write/mmap operations + file size and block size as returned from fstat are as expected + fallocate on the fd checks that offset/length on fallocate(FALLOC_FL_PUNCH_HOLE) should be page aligned + invalid inputs (misaligned size, invalid flags) are rejected + file size and inode are unique (the innocuous-sounding anon_inode_getfile() backs all files with a single inode...) Signed-off-by: Chao Peng <[email protected]> Co-developed-by: Ackerley Tng <[email protected]> Signed-off-by: Ackerley Tng <[email protected]> Co-developed-by: Paolo Bonzini <[email protected]> Signed-off-by: Paolo Bonzini <[email protected]> Co-developed-by: Sean Christopherson <[email protected]> Signed-off-by: Sean Christopherson <[email protected]> Message-Id: <[email protected]> Reviewed-by: Fuad Tabba <[email protected]> Tested-by: Fuad Tabba <[email protected]> Signed-off-by: Paolo Bonzini <[email protected]>
- Loading branch information
Showing
2 changed files
with
208 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* | ||
* Copyright Intel Corporation, 2023 | ||
* | ||
* Author: Chao Peng <[email protected]> | ||
*/ | ||
|
||
#define _GNU_SOURCE | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <unistd.h> | ||
#include <errno.h> | ||
#include <stdio.h> | ||
#include <fcntl.h> | ||
|
||
#include <linux/bitmap.h> | ||
#include <linux/falloc.h> | ||
#include <sys/mman.h> | ||
#include <sys/types.h> | ||
#include <sys/stat.h> | ||
|
||
#include "test_util.h" | ||
#include "kvm_util_base.h" | ||
|
||
static void test_file_read_write(int fd) | ||
{ | ||
char buf[64]; | ||
|
||
TEST_ASSERT(read(fd, buf, sizeof(buf)) < 0, | ||
"read on a guest_mem fd should fail"); | ||
TEST_ASSERT(write(fd, buf, sizeof(buf)) < 0, | ||
"write on a guest_mem fd should fail"); | ||
TEST_ASSERT(pread(fd, buf, sizeof(buf), 0) < 0, | ||
"pread on a guest_mem fd should fail"); | ||
TEST_ASSERT(pwrite(fd, buf, sizeof(buf), 0) < 0, | ||
"pwrite on a guest_mem fd should fail"); | ||
} | ||
|
||
static void test_mmap(int fd, size_t page_size) | ||
{ | ||
char *mem; | ||
|
||
mem = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); | ||
TEST_ASSERT_EQ(mem, MAP_FAILED); | ||
} | ||
|
||
static void test_file_size(int fd, size_t page_size, size_t total_size) | ||
{ | ||
struct stat sb; | ||
int ret; | ||
|
||
ret = fstat(fd, &sb); | ||
TEST_ASSERT(!ret, "fstat should succeed"); | ||
TEST_ASSERT_EQ(sb.st_size, total_size); | ||
TEST_ASSERT_EQ(sb.st_blksize, page_size); | ||
} | ||
|
||
static void test_fallocate(int fd, size_t page_size, size_t total_size) | ||
{ | ||
int ret; | ||
|
||
ret = fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, total_size); | ||
TEST_ASSERT(!ret, "fallocate with aligned offset and size should succeed"); | ||
|
||
ret = fallocate(fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, | ||
page_size - 1, page_size); | ||
TEST_ASSERT(ret, "fallocate with unaligned offset should fail"); | ||
|
||
ret = fallocate(fd, FALLOC_FL_KEEP_SIZE, total_size, page_size); | ||
TEST_ASSERT(ret, "fallocate beginning at total_size should fail"); | ||
|
||
ret = fallocate(fd, FALLOC_FL_KEEP_SIZE, total_size + page_size, page_size); | ||
TEST_ASSERT(ret, "fallocate beginning after total_size should fail"); | ||
|
||
ret = fallocate(fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, | ||
total_size, page_size); | ||
TEST_ASSERT(!ret, "fallocate(PUNCH_HOLE) at total_size should succeed"); | ||
|
||
ret = fallocate(fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, | ||
total_size + page_size, page_size); | ||
TEST_ASSERT(!ret, "fallocate(PUNCH_HOLE) after total_size should succeed"); | ||
|
||
ret = fallocate(fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, | ||
page_size, page_size - 1); | ||
TEST_ASSERT(ret, "fallocate with unaligned size should fail"); | ||
|
||
ret = fallocate(fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, | ||
page_size, page_size); | ||
TEST_ASSERT(!ret, "fallocate(PUNCH_HOLE) with aligned offset and size should succeed"); | ||
|
||
ret = fallocate(fd, FALLOC_FL_KEEP_SIZE, page_size, page_size); | ||
TEST_ASSERT(!ret, "fallocate to restore punched hole should succeed"); | ||
} | ||
|
||
static void test_invalid_punch_hole(int fd, size_t page_size, size_t total_size) | ||
{ | ||
struct { | ||
off_t offset; | ||
off_t len; | ||
} testcases[] = { | ||
{0, 1}, | ||
{0, page_size - 1}, | ||
{0, page_size + 1}, | ||
|
||
{1, 1}, | ||
{1, page_size - 1}, | ||
{1, page_size}, | ||
{1, page_size + 1}, | ||
|
||
{page_size, 1}, | ||
{page_size, page_size - 1}, | ||
{page_size, page_size + 1}, | ||
}; | ||
int ret, i; | ||
|
||
for (i = 0; i < ARRAY_SIZE(testcases); i++) { | ||
ret = fallocate(fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, | ||
testcases[i].offset, testcases[i].len); | ||
TEST_ASSERT(ret == -1 && errno == EINVAL, | ||
"PUNCH_HOLE with !PAGE_SIZE offset (%lx) and/or length (%lx) should fail", | ||
testcases[i].offset, testcases[i].len); | ||
} | ||
} | ||
|
||
static void test_create_guest_memfd_invalid(struct kvm_vm *vm) | ||
{ | ||
size_t page_size = getpagesize(); | ||
uint64_t flag; | ||
size_t size; | ||
int fd; | ||
|
||
for (size = 1; size < page_size; size++) { | ||
fd = __vm_create_guest_memfd(vm, size, 0); | ||
TEST_ASSERT(fd == -1 && errno == EINVAL, | ||
"guest_memfd() with non-page-aligned page size '0x%lx' should fail with EINVAL", | ||
size); | ||
} | ||
|
||
for (flag = 1; flag; flag <<= 1) { | ||
uint64_t bit; | ||
|
||
fd = __vm_create_guest_memfd(vm, page_size, flag); | ||
TEST_ASSERT(fd == -1 && errno == EINVAL, | ||
"guest_memfd() with flag '0x%lx' should fail with EINVAL", | ||
flag); | ||
|
||
for_each_set_bit(bit, &valid_flags, 64) { | ||
fd = __vm_create_guest_memfd(vm, page_size, flag | BIT_ULL(bit)); | ||
TEST_ASSERT(fd == -1 && errno == EINVAL, | ||
"guest_memfd() with flags '0x%llx' should fail with EINVAL", | ||
flag | BIT_ULL(bit)); | ||
} | ||
} | ||
} | ||
|
||
static void test_create_guest_memfd_multiple(struct kvm_vm *vm) | ||
{ | ||
int fd1, fd2, ret; | ||
struct stat st1, st2; | ||
|
||
fd1 = __vm_create_guest_memfd(vm, 4096, 0); | ||
TEST_ASSERT(fd1 != -1, "memfd creation should succeed"); | ||
|
||
ret = fstat(fd1, &st1); | ||
TEST_ASSERT(ret != -1, "memfd fstat should succeed"); | ||
TEST_ASSERT(st1.st_size == 4096, "memfd st_size should match requested size"); | ||
|
||
fd2 = __vm_create_guest_memfd(vm, 8192, 0); | ||
TEST_ASSERT(fd2 != -1, "memfd creation should succeed"); | ||
|
||
ret = fstat(fd2, &st2); | ||
TEST_ASSERT(ret != -1, "memfd fstat should succeed"); | ||
TEST_ASSERT(st2.st_size == 8192, "second memfd st_size should match requested size"); | ||
|
||
ret = fstat(fd1, &st1); | ||
TEST_ASSERT(ret != -1, "memfd fstat should succeed"); | ||
TEST_ASSERT(st1.st_size == 4096, "first memfd st_size should still match requested size"); | ||
TEST_ASSERT(st1.st_ino != st2.st_ino, "different memfd should have different inode numbers"); | ||
} | ||
|
||
int main(int argc, char *argv[]) | ||
{ | ||
size_t page_size; | ||
size_t total_size; | ||
int fd; | ||
struct kvm_vm *vm; | ||
|
||
TEST_REQUIRE(kvm_has_cap(KVM_CAP_GUEST_MEMFD)); | ||
|
||
page_size = getpagesize(); | ||
total_size = page_size * 4; | ||
|
||
vm = vm_create_barebones(); | ||
|
||
test_create_guest_memfd_invalid(vm); | ||
test_create_guest_memfd_multiple(vm); | ||
|
||
fd = vm_create_guest_memfd(vm, total_size, 0); | ||
|
||
test_file_read_write(fd); | ||
test_mmap(fd, page_size); | ||
test_file_size(fd, page_size, total_size); | ||
test_fallocate(fd, page_size, total_size); | ||
test_invalid_punch_hole(fd, page_size, total_size); | ||
|
||
close(fd); | ||
} |