DragonOS/user/apps/c_unitest/test_fallocate.c

854 lines
29 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
// 系统调用号定义 (x86_64)
#ifndef __NR_fallocate
#define __NR_fallocate 285
#endif
// fallocate mode flags (来自 linux/falloc.h)
#ifndef FALLOC_FL_KEEP_SIZE
#define FALLOC_FL_KEEP_SIZE 0x01
#endif
#ifndef FALLOC_FL_PUNCH_HOLE
#define FALLOC_FL_PUNCH_HOLE 0x02
#endif
#ifndef FALLOC_FL_ZERO_RANGE
#define FALLOC_FL_ZERO_RANGE 0x10
#endif
// 测试文件路径
#define TEST_FILE "/tmp/test_fallocate.txt"
#define TEST_DIR "/tmp/test_fallocate_dir"
#define TEST_SYMLINK "/tmp/test_fallocate_symlink"
#define TEST_LARGE_FILE "/tmp/test_fallocate_large.txt"
// 测试辅助宏
#define TEST_ASSERT(cond, msg) do { \
if (!(cond)) { \
printf("FAIL: %s (line %d)\n", msg, __LINE__); \
return -1; \
} \
} while(0)
#define TEST_PASS(msg) do { \
printf("PASS: %s\n", msg); \
return 0; \
} while(0)
#define TEST_SKIP(msg) do { \
printf("SKIP: %s\n", msg); \
return 77; \
} while(0)
// 使用 syscall 包装调用 fallocate
static int fallocate_wrapper(int fd, int mode, off_t offset, off_t len) {
return syscall(__NR_fallocate, fd, mode, offset, len);
}
// 获取文件大小
static off_t get_file_size(const char *path) {
struct stat st;
if (stat(path, &st) != 0) {
return -1;
}
return st.st_size;
}
// 获取文件描述符对应文件大小
static off_t get_fd_size(int fd) {
struct stat st;
if (fstat(fd, &st) != 0) {
return -1;
}
return st.st_size;
}
// 清理测试文件
static void cleanup_test_file(const char *path) {
unlink(path);
}
static int write_pattern_fd(int fd, off_t offset, size_t len, unsigned char pattern) {
unsigned char buf[512];
size_t remaining = len;
memset(buf, pattern, sizeof(buf));
while (remaining > 0) {
size_t chunk = remaining < sizeof(buf) ? remaining : sizeof(buf);
ssize_t written = pwrite(fd, buf, chunk, offset);
if (written < 0)
return -1;
if ((size_t)written != chunk)
return -1;
offset += written;
remaining -= written;
}
return 0;
}
static int read_full_fd(int fd, off_t offset, unsigned char *buf, size_t len) {
size_t remaining = len;
while (remaining > 0) {
ssize_t readn = pread(fd, buf, remaining, offset);
if (readn < 0)
return -1;
if (readn == 0)
return -1;
offset += readn;
buf += readn;
remaining -= readn;
}
return 0;
}
// 检查当前文件系统是否支持 fallocate
static int check_fallocate_supported(int fd) {
int ret;
errno = 0;
ret = fallocate_wrapper(fd, 0, 0, 1);
if (ret == 0) {
if (ftruncate(fd, 0) != 0)
return -1;
return 1;
}
if (errno == EOPNOTSUPP)
return 0;
return -1;
}
// ==================== 基本功能测试 ====================
// 测试默认模式 (mode=0) 的基本空间分配
static int test_basic_fallocate(void) {
printf("\n--- test_basic_fallocate ---\n");
// 创建测试文件并写入初始数据
int fd = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd >= 0, "打开测试文件");
int support = check_fallocate_supported(fd);
if (support == 0) {
close(fd);
cleanup_test_file(TEST_FILE);
TEST_SKIP("fallocate 不支持");
}
TEST_ASSERT(support > 0, "fallocate 探测失败");
const char *initial_data = "Hello, World! This is initial data.";
size_t initial_len = strlen(initial_data);
ssize_t written = write(fd, initial_data, initial_len);
TEST_ASSERT(written == (ssize_t)initial_len, "写入初始数据");
off_t initial_size = get_fd_size(fd);
printf("初始文件大小: %ld bytes\n", initial_size);
TEST_ASSERT(initial_size == (off_t)initial_len, "初始文件大小正确");
// fallocate 扩展文件到 10000 字节
off_t new_size = 10000;
int result = fallocate_wrapper(fd, 0, 0, new_size);
printf("fallocate(0, 0, %ld) 返回: %d, errno: %d\n", new_size, result, result == -1 ? errno : 0);
TEST_ASSERT(result == 0, "fallocate 扩展文件成功");
off_t allocated_size = get_fd_size(fd);
printf("fallocate 后文件大小: %ld bytes\n", allocated_size);
TEST_ASSERT(allocated_size == new_size, "文件大小正确扩展");
// 验证可以在扩展区域写入数据
char buffer[128];
memset(buffer, 0, sizeof(buffer));
// 读取初始数据应该仍然存在
lseek(fd, 0, SEEK_SET);
ssize_t nread = read(fd, buffer, sizeof(buffer) - 1);
// fallocate 扩展文件后,新空间被零填充,所以 read 会读取整个文件
// 我们只需要验证前 initial_len 字节是否匹配
TEST_ASSERT(nread >= (ssize_t)initial_len, "读取初始数据");
TEST_ASSERT(memcmp(buffer, initial_data, initial_len) == 0, "初始数据正确");
// 在扩展区域写入数据
const char *new_data = "Data written to extended area";
off_t write_offset = 9000;
lseek(fd, write_offset, SEEK_SET);
written = write(fd, new_data, strlen(new_data));
TEST_ASSERT(written == (ssize_t)strlen(new_data), "写入扩展区域");
// 读取写入的数据验证
lseek(fd, write_offset, SEEK_SET);
memset(buffer, 0, sizeof(buffer));
nread = read(fd, buffer, strlen(new_data));
TEST_ASSERT(nread == (ssize_t)strlen(new_data), "读取扩展区域数据");
TEST_ASSERT(memcmp(buffer, new_data, strlen(new_data)) == 0, "扩展区域数据正确");
close(fd);
cleanup_test_file(TEST_FILE);
TEST_PASS("基本功能测试");
}
// 测试在现有数据后追加分配
static int test_append_to_existing_data(void) {
printf("\n--- test_append_to_existing_data ---\n");
int fd = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd >= 0, "打开测试文件");
int support = check_fallocate_supported(fd);
if (support == 0) {
close(fd);
cleanup_test_file(TEST_FILE);
TEST_SKIP("fallocate 不支持");
}
TEST_ASSERT(support > 0, "fallocate 探测失败");
// 写入初始数据
const char *data1 = "Initial data block - ";
ssize_t written = write(fd, data1, strlen(data1));
TEST_ASSERT(written == (ssize_t)strlen(data1), "写入初始数据");
off_t size1 = get_fd_size(fd);
printf("第一次写入后大小: %ld bytes\n", size1);
// fallocate 在文件末尾追加空间
off_t append_len = 5000;
int result = fallocate_wrapper(fd, 0, size1, append_len);
printf("fallocate(0, %ld, %ld) 返回: %d\n", size1, append_len, result);
TEST_ASSERT(result == 0, "追加分配成功");
off_t size2 = get_fd_size(fd);
printf("追加分配后大小: %ld bytes\n", size2);
TEST_ASSERT(size2 == size1 + append_len, "追加分配大小正确");
// 验证原数据未受影响
lseek(fd, 0, SEEK_SET);
char buffer[128];
ssize_t nread = read(fd, buffer, strlen(data1));
TEST_ASSERT(nread == (ssize_t)strlen(data1), "读取原数据");
buffer[strlen(data1)] = '\0';
TEST_ASSERT(strcmp(buffer, data1) == 0, "原数据未受影响");
close(fd);
cleanup_test_file(TEST_FILE);
TEST_PASS("追加分配测试");
}
// 测试多次连续 fallocate 调用
static int test_multiple_allocations(void) {
printf("\n--- test_multiple_allocations ---\n");
int fd = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd >= 0, "打开测试文件");
int support = check_fallocate_supported(fd);
if (support == 0) {
close(fd);
cleanup_test_file(TEST_FILE);
TEST_SKIP("fallocate 不支持");
}
TEST_ASSERT(support > 0, "fallocate 探测失败");
off_t current_size = 0;
// 第一次分配
off_t alloc1 = 1000;
int result = fallocate_wrapper(fd, 0, 0, alloc1);
TEST_ASSERT(result == 0, "第一次分配成功");
current_size = get_fd_size(fd);
printf("第一次分配后大小: %ld bytes\n", current_size);
TEST_ASSERT(current_size == alloc1, "第一次分配大小正确");
// 第二次分配(继续扩展)
off_t alloc2 = 5000;
result = fallocate_wrapper(fd, 0, 0, alloc2);
TEST_ASSERT(result == 0, "第二次分配成功");
current_size = get_fd_size(fd);
printf("第二次分配后大小: %ld bytes\n", current_size);
TEST_ASSERT(current_size == alloc2, "第二次分配大小正确");
// 第三次分配(更大)
off_t alloc3 = 20000;
result = fallocate_wrapper(fd, 0, 0, alloc3);
TEST_ASSERT(result == 0, "第三次分配成功");
current_size = get_fd_size(fd);
printf("第三次分配后大小: %ld bytes\n", current_size);
TEST_ASSERT(current_size == alloc3, "第三次分配大小正确");
close(fd);
cleanup_test_file(TEST_FILE);
TEST_PASS("多次分配测试");
}
// ==================== 错误条件测试 ====================
// 测试无效的文件描述符
static int test_invalid_fd(void) {
printf("\n--- test_invalid_fd ---\n");
int result = fallocate_wrapper(-1, 0, 0, 1000);
TEST_ASSERT(result == -1, "无效 fd 应返回错误");
TEST_ASSERT(errno == EBADF, "无效 fd 应返回 EBADF");
// 测试一个不存在的 fd
result = fallocate_wrapper(9999, 0, 0, 1000);
TEST_ASSERT(result == -1, "不存在的 fd 应返回错误");
TEST_ASSERT(errno == EBADF, "不存在的 fd 应返回 EBADF");
TEST_PASS("无效 fd 测试");
}
// 测试只读文件描述符
static int test_readonly_fd(void) {
printf("\n--- test_readonly_fd ---\n");
// 创建文件
int fd_wr = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd_wr >= 0, "创建测试文件");
ssize_t written = write(fd_wr, "test", 4);
TEST_ASSERT(written == 4, "写入测试数据");
close(fd_wr);
// 以只读方式打开
int fd_rd = open(TEST_FILE, O_RDONLY);
TEST_ASSERT(fd_rd >= 0, "以只读方式打开文件");
int result = fallocate_wrapper(fd_rd, 0, 0, 1000);
TEST_ASSERT(result == -1, "只读 fd 应返回错误");
TEST_ASSERT(errno == EBADF, "只读 fd 应返回 EBADF");
printf("只读 fd fallocate 返回: %d, errno: %d (EBADF=%d)\n", result, errno, EBADF);
close(fd_rd);
cleanup_test_file(TEST_FILE);
TEST_PASS("只读 fd 测试");
}
// 测试对目录调用 fallocate
static int test_directory(void) {
printf("\n--- test_directory ---\n");
// 确保目录不存在
rmdir(TEST_DIR);
// 创建测试目录
int result = mkdir(TEST_DIR, 0755);
TEST_ASSERT(result == 0, "创建测试目录");
int fd = open(TEST_DIR, O_RDONLY);
TEST_ASSERT(fd >= 0, "打开目录");
errno = 0;
result = fallocate_wrapper(fd, 0, 0, 1000);
TEST_ASSERT(result == -1, "目录 fallocate 应失败");
TEST_ASSERT(errno == EBADF, "目录应返回 EBADF只读 fd");
printf("目录 fallocate 返回: %d, errno: %d (EBADF=%d)\n",
result, errno, EBADF);
close(fd);
rmdir(TEST_DIR);
TEST_PASS("目录测试");
}
// 测试 len=0 的情况
static int test_zero_length(void) {
printf("\n--- test_zero_length ---\n");
int fd = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd >= 0, "打开测试文件");
int result = fallocate_wrapper(fd, 0, 0, 0);
TEST_ASSERT(result == -1, "len=0 应返回错误");
TEST_ASSERT(errno == EINVAL, "len=0 应返回 EINVAL");
printf("len=0 fallocate 返回: %d, errno: %d (EINVAL=%d)\n", result, errno, EINVAL);
close(fd);
cleanup_test_file(TEST_FILE);
TEST_PASS("零长度测试");
}
// 测试无效的 offset 和 length负值转为大正数超过 isize::MAX
static int test_invalid_offset_length(void) {
printf("\n--- test_invalid_offset_length ---\n");
int fd = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd >= 0, "打开测试文件");
// 测试负 offset转换为无符号大数
// 在 x86_64 上,-1 作为 off_t 传递,但由于参数类型转换
// 我们需要测试超过 isize::MAX 的值
int result = fallocate_wrapper(fd, 0, (off_t)-1, 1000);
TEST_ASSERT(result == -1, "负 offset 应返回错误");
TEST_ASSERT(errno == EINVAL, "负 offset 应返回 EINVAL");
printf("负 offset fallocate 返回: %d, errno: %d\n", result, errno);
// 测试负 len
result = fallocate_wrapper(fd, 0, 0, (off_t)-1);
TEST_ASSERT(result == -1, "负 len 应返回错误");
TEST_ASSERT(errno == EINVAL, "负 len 应返回 EINVAL");
printf("负 len fallocate 返回: %d, errno: %d\n", result, errno);
close(fd);
cleanup_test_file(TEST_FILE);
TEST_PASS("无效 offset/length 测试");
}
// 测试 offset + len 溢出
static int test_offset_overflow(void) {
printf("\n--- test_offset_overflow ---\n");
int fd = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd >= 0, "打开测试文件");
// 设置 offset 和 len 使得相加溢出
// 使用接近 SIZE_MAX 的值
off_t huge_offset = (off_t)LLONG_MAX - 1000;
off_t len = 2000; // offset + len 会溢出
int result = fallocate_wrapper(fd, 0, huge_offset, len);
// Linux 返回 EINVAL不是 EFBIG
TEST_ASSERT(result == -1, "溢出应返回错误");
TEST_ASSERT(errno == EINVAL || errno == EFBIG, "溢出应返回 EINVAL 或 EFBIG");
printf("溢出测试 fallocate 返回: %d, errno: %d (EINVAL=%d, EFBIG=%d)\n", result, errno, EINVAL, EFBIG);
close(fd);
cleanup_test_file(TEST_FILE);
TEST_PASS("溢出测试");
}
// 测试 FALLOC_FL_KEEP_SIZELinux 支持)
static int test_keep_size_mode(void) {
printf("\n--- test_keep_size_mode ---\n");
int fd = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd >= 0, "打开测试文件");
int support = check_fallocate_supported(fd);
if (support == 0) {
close(fd);
cleanup_test_file(TEST_FILE);
TEST_SKIP("fallocate 不支持");
}
TEST_ASSERT(support > 0, "fallocate 探测失败");
// 写入一些初始数据
const char *data = "Hello";
ssize_t written = write(fd, data, strlen(data));
TEST_ASSERT(written == (ssize_t)strlen(data), "写入初始数据");
off_t initial_size = get_fd_size(fd);
printf("初始文件大小: %ld bytes\n", initial_size);
// FALLOC_FL_KEEP_SIZE: 分配空间但不改变文件大小
int result = fallocate_wrapper(fd, FALLOC_FL_KEEP_SIZE, 0, 10000);
if (result == -1 && errno == EOPNOTSUPP) {
close(fd);
cleanup_test_file(TEST_FILE);
TEST_SKIP("FALLOC_FL_KEEP_SIZE 不支持");
}
TEST_ASSERT(result == 0, "FALLOC_FL_KEEP_SIZE 调用成功");
off_t size_after = get_fd_size(fd);
printf("FALLOC_FL_KEEP_SIZE 后大小: %ld bytes\n", size_after);
TEST_ASSERT(size_after == initial_size, "文件大小应保持不变");
close(fd);
cleanup_test_file(TEST_FILE);
return 0;
}
// 测试 FALLOC_FL_PUNCH_HOLE
static int test_punch_hole_mode(void) {
printf("\n--- test_punch_hole_mode ---\n");
int fd = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd >= 0, "打开测试文件");
int support = check_fallocate_supported(fd);
if (support == 0) {
close(fd);
cleanup_test_file(TEST_FILE);
TEST_SKIP("fallocate 不支持");
}
TEST_ASSERT(support > 0, "fallocate 探测失败");
TEST_ASSERT(write_pattern_fd(fd, 0, 8192, 0x5a) == 0, "写入初始数据");
int result = fallocate_wrapper(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 4096, 4096);
if (result == -1 && errno == EOPNOTSUPP) {
close(fd);
cleanup_test_file(TEST_FILE);
TEST_SKIP("FALLOC_FL_PUNCH_HOLE 不支持");
}
TEST_ASSERT(result == 0, "punch hole 成功");
unsigned char buf[8192];
TEST_ASSERT(read_full_fd(fd, 0, buf, sizeof(buf)) == 0, "读取数据");
for (size_t i = 0; i < 4096; i++) {
TEST_ASSERT(buf[i] == 0x5a, "punch hole 前半段保持不变");
}
for (size_t i = 4096; i < 8192; i++) {
TEST_ASSERT(buf[i] == 0, "punch hole 区域应为 0");
}
close(fd);
cleanup_test_file(TEST_FILE);
TEST_PASS("FALLOC_FL_PUNCH_HOLE 测试");
}
// 测试 FALLOC_FL_ZERO_RANGE
static int test_zero_range_mode(void) {
printf("\n--- test_zero_range_mode ---\n");
int fd = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd >= 0, "打开测试文件");
int support = check_fallocate_supported(fd);
if (support == 0) {
close(fd);
cleanup_test_file(TEST_FILE);
TEST_SKIP("fallocate 不支持");
}
TEST_ASSERT(support > 0, "fallocate 探测失败");
TEST_ASSERT(write_pattern_fd(fd, 0, 8192, 0xa5) == 0, "写入初始数据");
int result = fallocate_wrapper(fd, FALLOC_FL_ZERO_RANGE, 4096, 4096);
if (result == -1 && errno == EOPNOTSUPP) {
close(fd);
cleanup_test_file(TEST_FILE);
TEST_SKIP("FALLOC_FL_ZERO_RANGE 不支持");
}
TEST_ASSERT(result == 0, "zero range 成功");
unsigned char buf[8192];
TEST_ASSERT(read_full_fd(fd, 0, buf, sizeof(buf)) == 0, "读取数据");
for (size_t i = 0; i < 4096; i++) {
TEST_ASSERT(buf[i] == 0xa5, "zero range 前半段保持不变");
}
for (size_t i = 4096; i < 8192; i++) {
TEST_ASSERT(buf[i] == 0, "zero range 区域应为 0");
}
close(fd);
cleanup_test_file(TEST_FILE);
TEST_PASS("FALLOC_FL_ZERO_RANGE 测试");
}
// 测试收缩操作
static int test_shrink_file(void) {
printf("\n--- test_shrink_file ---\n");
int fd = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd >= 0, "打开测试文件");
int support = check_fallocate_supported(fd);
if (support == 0) {
close(fd);
cleanup_test_file(TEST_FILE);
TEST_SKIP("fallocate 不支持");
}
TEST_ASSERT(support > 0, "fallocate 探测失败");
// 创建一个有数据的文件
off_t initial_size = 5000;
int result = fallocate_wrapper(fd, 0, 0, initial_size);
TEST_ASSERT(result == 0, "初始分配成功");
off_t current_size = get_fd_size(fd);
printf("当前文件大小: %ld bytes\n", current_size);
// Linux: mode=0 的 fallocate 确保文件至少有指定大小
// 如果指定的大小小于当前文件大小,什么都不会发生(不收缩)
off_t smaller_size = 1000;
result = fallocate_wrapper(fd, 0, 0, smaller_size);
// 应该成功,但文件大小不变
if (result == 0) {
off_t size_after = get_fd_size(fd);
printf("fallocate(0, 0, %ld) 后大小: %ld bytes\n", smaller_size, size_after);
TEST_ASSERT(size_after == current_size, "文件大小应保持不变fallocate mode=0 不收缩)");
printf("收缩操作返回: %d (不收缩,这是正确行为)\n", result);
TEST_PASS("收缩文件测试");
} else {
printf("fallocate 返回: %d, errno: %d\n", result, errno);
close(fd);
cleanup_test_file(TEST_FILE);
TEST_PASS("收缩文件测试");
}
close(fd);
cleanup_test_file(TEST_FILE);
return 0;
}
// ==================== 边界条件测试 ====================
// 测试大块空间分配
static int test_large_allocation(void) {
printf("\n--- test_large_allocation ---\n");
int fd = open(TEST_LARGE_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd >= 0, "打开测试文件");
int support = check_fallocate_supported(fd);
if (support == 0) {
close(fd);
cleanup_test_file(TEST_LARGE_FILE);
TEST_SKIP("fallocate 不支持");
}
TEST_ASSERT(support > 0, "fallocate 探测失败");
// 测试较大但不极端的分配
off_t large_size = 10 * 1024 * 1024; // 10 MB
int result = fallocate_wrapper(fd, 0, 0, large_size);
printf("分配 %ld MB 返回: %d\n", large_size / (1024 * 1024), result);
if (result == 0) {
off_t allocated_size = get_fd_size(fd);
printf("大块分配后文件大小: %ld bytes\n", allocated_size);
TEST_ASSERT(allocated_size == large_size, "大块分配大小正确");
TEST_PASS("大块分配测试");
}
if (errno == ENOSPC || errno == EOPNOTSUPP) {
printf("SKIP: 大块分配失败 (errno=%d)\n", errno);
close(fd);
cleanup_test_file(TEST_LARGE_FILE);
TEST_SKIP("大块分配测试");
}
printf("FAIL: 大块分配失败 (errno=%d)\n", errno);
close(fd);
cleanup_test_file(TEST_LARGE_FILE);
return -1;
close(fd);
cleanup_test_file(TEST_LARGE_FILE);
return 0;
}
// ==================== 特殊文件类型测试 ====================
// 测试对管道调用 fallocate
static int test_pipe(void) {
printf("\n--- test_pipe ---\n");
int pipefd[2];
int result = pipe(pipefd);
TEST_ASSERT(result == 0, "创建管道");
errno = 0;
result = fallocate_wrapper(pipefd[1], 0, 0, 1000);
TEST_ASSERT(result == -1, "管道 fallocate 应失败");
TEST_ASSERT(errno == ESPIPE, "管道应返回 ESPIPE");
printf("管道 fallocate 返回: %d, errno: %d (ESPIPE=%d)\n",
result, errno, ESPIPE);
close(pipefd[0]);
close(pipefd[1]);
TEST_PASS("管道测试");
}
// 测试符号链接
static int test_symlink(void) {
printf("\n--- test_symlink ---\n");
// 创建目标文件
int fd = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd >= 0, "创建目标文件");
ssize_t written = write(fd, "target content", 13);
TEST_ASSERT(written == 13, "写入目标文件内容");
close(fd);
// 创建符号链接
unlink(TEST_SYMLINK);
int result = symlink(TEST_FILE, TEST_SYMLINK);
TEST_ASSERT(result == 0, "创建符号链接");
// 打开符号链接(会跟随到目标文件)
int fd_link = open(TEST_SYMLINK, O_RDWR);
TEST_ASSERT(fd_link >= 0, "打开符号链接");
// fallocate 应该作用在目标文件上
result = fallocate_wrapper(fd_link, 0, 0, 5000);
if (result == -1 && errno == EOPNOTSUPP) {
close(fd_link);
cleanup_test_file(TEST_FILE);
cleanup_test_file(TEST_SYMLINK);
TEST_SKIP("fallocate 不支持");
}
TEST_ASSERT(result == 0, "符号链接目标 fallocate 成功");
off_t link_size = get_fd_size(fd_link);
off_t target_size = get_file_size(TEST_FILE);
printf("符号链接 fd 大小: %ld, 目标文件大小: %ld\n",
link_size, target_size);
TEST_ASSERT(link_size == 5000, "符号链接操作成功");
TEST_ASSERT(target_size == 5000, "目标文件被正确修改");
close(fd_link);
cleanup_test_file(TEST_FILE);
cleanup_test_file(TEST_SYMLINK);
return 0;
}
// ==================== 一致性测试 ====================
// 测试与 ftruncate 的一致性
static int test_consistency_with_ftruncate(void) {
printf("\n--- test_consistency_with_ftruncate ---\n");
// 使用 fallocate
int fd1 = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd1 >= 0, "打开文件1");
int result = fallocate_wrapper(fd1, 0, 0, 10000);
if (result == -1 && errno == EOPNOTSUPP) {
close(fd1);
cleanup_test_file(TEST_FILE);
TEST_SKIP("fallocate 不支持");
}
TEST_ASSERT(result == 0, "fallocate 成功");
off_t size1 = get_fd_size(fd1);
close(fd1);
cleanup_test_file(TEST_FILE);
// 使用 ftruncate 达到相同大小
int fd2 = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd2 >= 0, "打开文件2");
result = ftruncate(fd2, 10000);
TEST_ASSERT(result == 0, "ftruncate 成功");
off_t size2 = get_fd_size(fd2);
close(fd2);
cleanup_test_file(TEST_FILE);
printf("fallocate 大小: %ld, ftruncate 大小: %ld\n", size1, size2);
TEST_ASSERT(size1 == size2, "fallocate 和 ftruncate 结果一致");
TEST_PASS("与 ftruncate 一致性测试");
}
// 测试与 write 的一致性
static int test_consistency_with_write(void) {
printf("\n--- test_consistency_with_write ---\n");
int fd = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644);
TEST_ASSERT(fd >= 0, "打开测试文件");
// fallocate 分配空间
off_t alloc_size = 10000;
int result = fallocate_wrapper(fd, 0, 0, alloc_size);
if (result == -1 && errno == EOPNOTSUPP) {
close(fd);
cleanup_test_file(TEST_FILE);
TEST_SKIP("fallocate 不支持");
}
TEST_ASSERT(result == 0, "fallocate 成功");
// 在分配的区域写入数据
const char *test_pattern = "ABCDE";
off_t offsets[] = {0, 100, 5000, 9995};
char read_buffer[128];
for (size_t i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
off_t offset = offsets[i];
lseek(fd, offset, SEEK_SET);
ssize_t written = write(fd, test_pattern, strlen(test_pattern));
TEST_ASSERT(written == (ssize_t)strlen(test_pattern), "写入测试数据");
// 读取验证
lseek(fd, offset, SEEK_SET);
memset(read_buffer, 0, sizeof(read_buffer));
ssize_t nread = read(fd, read_buffer, strlen(test_pattern));
TEST_ASSERT(nread == (ssize_t)strlen(test_pattern), "读取测试数据");
TEST_ASSERT(memcmp(read_buffer, test_pattern, strlen(test_pattern)) == 0,
"写入数据验证成功");
printf("偏移 %ld 处写入/读取验证成功\n", offset);
}
// 验证未写入区域应为零
lseek(fd, 200, SEEK_SET);
memset(read_buffer, 0, sizeof(read_buffer));
ssize_t nread = read(fd, read_buffer, 100);
TEST_ASSERT(nread == 100, "读取未写入区域");
int all_zero = 1;
for (int i = 0; i < 100; i++) {
if (read_buffer[i] != 0) {
all_zero = 0;
break;
}
}
TEST_ASSERT(all_zero, "未写入区域应为零");
close(fd);
cleanup_test_file(TEST_FILE);
TEST_PASS("与 write 一致性测试");
}
// ==================== 主函数 ====================
int main(void) {
printf("========================================\n");
printf(" fallocate 系统调用完备测试\n");
printf("========================================\n");
int passed = 0;
int failed = 0;
int skipped = 0;
int r;
// 基本功能测试
printf("\n========== 基本功能测试 ==========\n");
r = test_basic_fallocate(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
r = test_append_to_existing_data(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
r = test_multiple_allocations(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
// 错误条件测试
printf("\n========== 错误条件测试 ==========\n");
r = test_invalid_fd(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
r = test_readonly_fd(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
r = test_directory(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
r = test_zero_length(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
r = test_invalid_offset_length(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
r = test_offset_overflow(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
r = test_keep_size_mode(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
r = test_punch_hole_mode(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
r = test_zero_range_mode(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
r = test_shrink_file(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
// 边界条件测试
printf("\n========== 边界条件测试 ==========\n");
r = test_large_allocation(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
// 特殊文件类型测试
printf("\n========== 特殊文件类型测试 ==========\n");
r = test_pipe(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
r = test_symlink(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
// 一致性测试
printf("\n========== 一致性测试 ==========\n");
r = test_consistency_with_ftruncate(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
r = test_consistency_with_write(); if (r == 0) passed++; else if (r == 77) skipped++; else failed++;
// 最终统计
printf("\n========================================\n");
printf(" 测试完成\n");
printf("========================================\n");
printf("通过: %d\n", passed);
printf("失败: %d\n", failed);
printf("跳过: %d\n", skipped);
printf("总计: %d\n", passed + failed + skipped);
printf("========================================\n");
return (failed > 0) ? 1 : 0;
}