#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <removefile.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include "../copyfile.h"
#include "sparse_test.h"
#include "test_utils.h"
#include "systemx.h"
static bool test_copy(int src_fd, char* orig_name, char* copy_name, bool do_sparse, off_t start_off) {
struct stat orig_sb, copy_sb;
int copy_fd;
bool result = true;
copyfile_state_t cpf_state;
memset(&orig_sb, 0, sizeof(orig_sb));
memset(©_sb, 0, sizeof(copy_sb));
assert_with_errno((cpf_state = copyfile_state_alloc()) != NULL);
assert_with_errno(lseek(src_fd, start_off, SEEK_SET) == start_off);
copyfile_flags_t flags = COPYFILE_ALL;
if (do_sparse) {
flags |= COPYFILE_DATA_SPARSE;
}
assert_no_err(copyfile(orig_name, copy_name, cpf_state, flags));
assert_no_err(stat(orig_name, &orig_sb));
assert_no_err(stat(copy_name, ©_sb));
result &= verify_copy_sizes(&orig_sb, ©_sb, cpf_state, do_sparse, 0);
result &= verify_copy_contents(orig_name, copy_name);
assert_no_err(copyfile_state_free(cpf_state));
assert_no_err(removefile(copy_name, NULL, REMOVEFILE_RECURSIVE));
memset(&orig_sb, 0, sizeof(struct stat));
memset(©_sb, 0, sizeof(struct stat));
assert_with_errno((copy_fd = open(copy_name, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM)) > 0);
assert_with_errno((cpf_state = copyfile_state_alloc()) != NULL);
assert_no_err(fcopyfile(src_fd, copy_fd, cpf_state, flags));
assert_no_err(fstat(src_fd, &orig_sb));
assert_no_err(fstat(copy_fd, ©_sb));
result &= verify_copy_sizes(&orig_sb, ©_sb, cpf_state,
start_off > 0 ? false : do_sparse, start_off);
if (start_off == 0) {
result &= verify_copy_contents(orig_name, copy_name);
}
assert_no_err(copyfile_state_free(cpf_state));
assert_no_err(removefile(copy_name, NULL, REMOVEFILE_RECURSIVE));
assert_no_err(close(copy_fd));
return result;
}
typedef off_t (*creator_func)(int, off_t);
static off_t write_start_and_end_holes(int fd, off_t block_size) {
assert_with_errno(pwrite(fd, "j", 1, block_size) == 1);
assert_no_err(ftruncate(fd, 3 * block_size));
assert_no_err(create_hole_in_fd(fd, 0, block_size));
assert_no_err(create_hole_in_fd(fd, 2 * block_size, block_size));
return 0;
}
static off_t write_end_hole(int fd, off_t block_size) {
assert_with_errno(pwrite(fd, "n", 1, 0) == 1);
assert_no_err(ftruncate(fd, 16 * block_size));
assert_no_err(create_hole_in_fd(fd, block_size, 15 * block_size));
return 0;
}
static off_t write_start_hole(int fd, off_t block_size) {
assert_with_errno(pwrite(fd, "p", 1, 16 * block_size) == 1);
assert_no_err(create_hole_in_fd(fd, 0, 16 * block_size));
return 0;
}
static off_t write_middle_hole(int fd, off_t block_size) {
assert_with_errno(pwrite(fd, "k", 1, 0) == 1);
assert_with_errno(pwrite(fd, "k", 1, 4 * block_size) == 1);
assert_no_err(ftruncate(fd, 5 * block_size));
assert_no_err(create_hole_in_fd(fd, block_size, 3 * block_size));
return 0;
}
static off_t write_start_and_middle_holes(int fd, off_t block_size) {
assert_with_errno(pwrite(fd, "l", 1, 16 * block_size) == 1);
assert_with_errno(pwrite(fd, "l", 1, 32 * block_size) == 1);
assert_no_err(create_hole_in_fd(fd, 0, 16 * block_size));
assert_no_err(create_hole_in_fd(fd, 17 * block_size, 15 * block_size));
return 0;
}
static off_t write_middle_and_end_holes(int fd, off_t block_size) {
assert_with_errno(pwrite(fd, "m", 1, 0) == 1);
assert_with_errno(pwrite(fd, "m", 1, 16 * block_size) == 1);
assert_no_err(ftruncate(fd, 32 * block_size));
assert_no_err(create_hole_in_fd(fd, block_size, 15 * block_size));
assert_no_err(create_hole_in_fd(fd, 17 * block_size, 15 * block_size));
return 0;
}
static off_t write_no_sparse(int fd, __unused off_t block_size) {
assert_with_errno(pwrite(fd, "z", 1, 0) == 1);
return 0;
}
static off_t write_sparse_odd_offset(int fd, off_t block_size) {
assert_with_errno(pwrite(fd, "q", 1, block_size) == 1);
assert_no_err(create_hole_in_fd(fd, 0, block_size));
assert_with_errno(lseek(fd, 1, SEEK_SET) == 1);
return 1;
}
static off_t write_sparse_bs_offset(int fd, off_t block_size) {
assert_with_errno(pwrite(fd, "a", 1, block_size) == 1);
assert_with_errno(pwrite(fd, "b", 1, 2 * block_size) == 1);
assert_no_err(create_hole_in_fd(fd, 0, block_size));
assert_with_errno(lseek(fd, block_size, SEEK_SET) == block_size);
return block_size;
}
static off_t write_diff_adj_holes(int fd, off_t block_size) {
assert_with_errno(pwrite(fd, "w", 1, 0));
assert_with_errno(pwrite(fd, "w", 1, 3 * block_size));
assert_no_err(ftruncate(fd, 4 * block_size));
assert_no_err(create_hole_in_fd(fd, block_size, block_size));
assert_no_err(create_hole_in_fd(fd, 2 * block_size, block_size));
return 0;
}
typedef struct {
creator_func func; const char * name; } sparse_test_func;
#define NUM_TEST_FUNCTIONS 10
sparse_test_func test_functions[NUM_TEST_FUNCTIONS] = {
{write_start_and_end_holes, "start_and_end_holes"},
{write_middle_hole, "middle_hole"},
{write_start_and_middle_holes, "start_and_middle_holes"},
{write_middle_and_end_holes, "middle_and_end_holes"},
{write_end_hole, "end_hole"},
{write_start_hole, "start_hole"},
{write_no_sparse, "no_sparse"},
{write_sparse_odd_offset, "write_sparse_odd_offset"},
{write_sparse_bs_offset, "write_sparse_bs_offset"},
{write_diff_adj_holes, "write_diff_adj_holes"}
};
bool do_sparse_test(const char* apfs_test_directory, size_t block_size) {
int fd, test_file_id;
char out_name[BSIZE_B], sparse_copy_name[BSIZE_B], full_copy_name[BSIZE_B];
bool success = true, sub_test_success;
off_t start_off;
for (size_t sub_test = 0; sub_test < NUM_TEST_FUNCTIONS; sub_test++) {
printf("START [%s]\n", test_functions[sub_test].name);
sub_test_success = false;
test_file_id = rand() % DEFAULT_NAME_MOD;
create_test_file_name(apfs_test_directory, "sparse", test_file_id, out_name);
create_test_file_name(apfs_test_directory, "copy_sparse", test_file_id, sparse_copy_name);
create_test_file_name(apfs_test_directory, "copy_full", test_file_id, full_copy_name);
fd = open(out_name, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM);
assert_with_errno(fd >= 0);
start_off = test_functions[sub_test].func(fd, (off_t) block_size);
assert_no_err(fsync(fd));
sub_test_success = test_copy(fd, out_name, sparse_copy_name, true, start_off);
if (sub_test_success) {
sub_test_success = test_copy(fd, out_name, full_copy_name, false, start_off);
}
if (!sub_test_success) {
printf("FAIL [%s]\n", test_functions[sub_test].name);
success = false;
} else {
printf("PASS [%s]\n", test_functions[sub_test].name);
}
assert_no_err(close(fd));
(void)removefile(out_name, NULL, 0);
(void)removefile(sparse_copy_name, NULL, 0);
(void)removefile(full_copy_name, NULL, 0);
memset(out_name, 0, BSIZE_B);
memset(sparse_copy_name, 0, BSIZE_B);
memset(full_copy_name, 0, BSIZE_B);
}
return success ? EXIT_SUCCESS : EXIT_FAILURE;
}
bool do_sparse_recursive_test(const char *apfs_test_directory, size_t block_size) {
int exterior_file_src_fd, interior_file_src_fd, test_file_id;
char exterior_dir_src[BSIZE_B] = {0}, interior_dir_src[BSIZE_B] = {0}, exterior_dir_dst[BSIZE_B] = {0}, interior_dir_dst[BSIZE_B] = {0};
char exterior_file_src[BSIZE_B] = {0}, interior_file_src[BSIZE_B] = {0}, exterior_file_dst[BSIZE_B] = {0}, interior_file_dst[BSIZE_B] = {0};
struct stat exterior_file_src_sb, interior_file_src_sb, exterior_file_dst_sb, interior_file_dst_sb;
bool success = true;
printf("START [sparse_recursive]\n");
memset(&exterior_file_src_sb, 0, sizeof(exterior_file_src_sb));
memset(&interior_file_src_sb, 0, sizeof(interior_file_src_sb));
memset(&exterior_file_dst_sb, 0, sizeof(exterior_file_dst_sb));
memset(&interior_file_dst_sb, 0, sizeof(interior_file_dst_sb));
assert_with_errno(snprintf(exterior_dir_src, BSIZE_B, "%s/recursive_src", apfs_test_directory) > 0);
assert_with_errno(snprintf(interior_dir_src, BSIZE_B, "%s/interior", exterior_dir_src) > 0);
assert_no_err(mkdir(exterior_dir_src, DEFAULT_MKDIR_PERM));
assert_no_err(mkdir(interior_dir_src, DEFAULT_MKDIR_PERM));
test_file_id = rand() % DEFAULT_NAME_MOD;
create_test_file_name(exterior_dir_src, "exterior_sparse_file", test_file_id, exterior_file_src);
create_test_file_name(interior_dir_src, "interior_sparse_file", test_file_id, interior_file_src);
exterior_file_src_fd = open(exterior_file_src, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM);
assert_with_errno(exterior_file_src_fd >= 0);
write_start_and_end_holes(exterior_file_src_fd, block_size);
interior_file_src_fd = open(interior_file_src, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM);
assert_with_errno(interior_file_src_fd >= 0);
write_middle_hole(interior_file_src_fd, block_size);
assert_with_errno(snprintf(exterior_dir_dst, BSIZE_B, "%s/recursive_dst", apfs_test_directory) > 0);
assert_no_err(copyfile(exterior_dir_src, exterior_dir_dst, NULL, COPYFILE_ALL|COPYFILE_RECURSIVE|COPYFILE_DATA_SPARSE));
assert_with_errno(snprintf(exterior_dir_dst, BSIZE_B, "%s/recursive_dst", apfs_test_directory) > 0);
create_test_file_name(exterior_dir_dst, "exterior_sparse_file", test_file_id, exterior_file_dst);
assert_with_errno(snprintf(interior_dir_dst, BSIZE_B, "%s/interior", exterior_dir_dst) > 0);
create_test_file_name(interior_dir_dst, "interior_sparse_file", test_file_id, interior_file_dst);
assert_no_err(fstat(exterior_file_src_fd, &exterior_file_src_sb));
assert_no_err(stat(exterior_file_dst, &exterior_file_dst_sb));
assert_no_err(fstat(interior_file_src_fd, &interior_file_src_sb));
assert_no_err(stat(interior_file_dst, &interior_file_dst_sb));
success &= verify_copy_sizes(&exterior_file_src_sb, &exterior_file_dst_sb, NULL, true, 0);
success &= verify_copy_sizes(&interior_file_src_sb, &interior_file_dst_sb, NULL, true, 0);
success &= verify_copy_contents(exterior_file_src, exterior_file_dst);
success &= verify_copy_contents(interior_file_src, interior_file_dst);
if (success) {
printf("PASS [sparse_recursive]\n");
} else {
printf("FAIL [sparse_recursive]\n");
}
assert_no_err(close(interior_file_src_fd));
assert_no_err(close(exterior_file_src_fd));
(void)removefile(exterior_dir_src, NULL, REMOVEFILE_RECURSIVE);
(void)removefile(exterior_dir_dst, NULL, REMOVEFILE_RECURSIVE);
return success ? EXIT_SUCCESS : EXIT_FAILURE;
}
bool do_fcopyfile_offset_test(const char *apfs_test_directory, size_t block_size) {
int src_fd, sparse_copy_fd, full_copy_fd, test_file_id;
char out_name[BSIZE_B], sparse_copy_name[BSIZE_B], full_copy_name[BSIZE_B];
bool success = true;
printf("START [fcopyfile_offset]\n");
test_file_id = rand() % DEFAULT_NAME_MOD;
create_test_file_name(apfs_test_directory, "foff_sparse", test_file_id, out_name);
create_test_file_name(apfs_test_directory, "foff_copy_sparse", test_file_id, sparse_copy_name);
create_test_file_name(apfs_test_directory, "foff_copy_full", test_file_id, full_copy_name);
src_fd = open(out_name, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM);
assert_with_errno(src_fd >= 0);
assert_with_errno(lseek(src_fd, write_middle_hole(src_fd, block_size), SEEK_SET) == 0);
sparse_copy_fd = open(sparse_copy_name, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM);
assert_with_errno(sparse_copy_fd >= 0);
assert_with_errno(lseek(sparse_copy_fd, block_size, SEEK_SET) == (off_t) block_size);
assert_with_errno(pwrite(sparse_copy_fd, "z", 1, block_size) == 1);
assert_no_err(fcopyfile(src_fd, sparse_copy_fd, NULL, COPYFILE_ALL|COPYFILE_DATA_SPARSE));
success &= verify_fd_contents(src_fd, 0, sparse_copy_fd, block_size, 4 * block_size);
assert_with_errno(lseek(src_fd, 0, SEEK_SET) == 0);
full_copy_fd = open(full_copy_name, DEFAULT_OPEN_FLAGS, DEFAULT_OPEN_PERM);
assert_with_errno(full_copy_name >= 0);
assert_with_errno(lseek(full_copy_fd, block_size, SEEK_SET) == (off_t) block_size);
assert_with_errno(pwrite(full_copy_fd, "r", 1, block_size) == 1);
assert_no_err(fcopyfile(src_fd, full_copy_fd, NULL, COPYFILE_ALL));
success &= verify_fd_contents(src_fd, 0, full_copy_fd, block_size, 4 * block_size);
if (success) {
printf("PASS [fcopyfile_offset]\n");
} else {
printf("FAIL [fcopyfile_offset]\n");
}
assert_no_err(close(full_copy_fd));
assert_no_err(close(sparse_copy_fd));
assert_no_err(close(src_fd));
(void)removefile(full_copy_name, NULL, 0);
(void)removefile(sparse_copy_name, NULL, 0);
(void)removefile(out_name, NULL, 0);
return success ? EXIT_SUCCESS : EXIT_FAILURE;
}