| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright Intel Corporation, 2023 |
| * |
| * Author: Chao Peng <chao.p.peng@linux.intel.com> |
| */ |
| #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 "kvm_util.h" |
| #include "test_util.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 = BIT(0); flag; flag <<= 1) { |
| 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); |
| } |
| } |
| |
| 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"); |
| |
| close(fd2); |
| close(fd1); |
| } |
| |
| 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); |
| } |