| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (c) Linux Test Project, 2019 |
| */ |
| |
| /* |
| * This tests the fundamental functionalities of the copy_file_range |
| * syscall. It does so by copying the contents of one file into |
| * another using various different combinations for length and |
| * input/output offsets. |
| * |
| * After a copy is done this test checks if the contents of both files |
| * are equal at the given offsets. It is also inspected if the offsets |
| * of the file descriptors are advanced correctly. |
| */ |
| |
| #define _GNU_SOURCE |
| |
| #include "tst_test.h" |
| #include "tst_safe_stdio.h" |
| #include "copy_file_range.h" |
| |
| static int page_size; |
| static int errcount, numcopies; |
| static int fd_in, fd_out, cross_sup; |
| |
| static struct tcase { |
| char *path; |
| int flags; |
| char *message; |
| } tcases[] = { |
| {FILE_DEST_PATH, 0, "non cross-device"}, |
| {FILE_MNTED_PATH, 1, "cross-device"}, |
| }; |
| |
| static int check_file_content(const char *fname1, const char *fname2, |
| loff_t *off1, loff_t *off2, size_t len) |
| { |
| FILE *fp1, *fp2; |
| int ch1, ch2; |
| size_t count = 0; |
| |
| fp1 = SAFE_FOPEN(fname1, "r"); |
| if (off1 && fseek(fp1, *off1, SEEK_SET)) |
| tst_brk(TBROK | TERRNO, "fseek() failed"); |
| |
| fp2 = SAFE_FOPEN(fname2, "r"); |
| if (off2 && fseek(fp2, *off2, SEEK_SET)) |
| tst_brk(TBROK | TERRNO, "fseek() failed"); |
| |
| do { |
| ch1 = getc(fp1); |
| ch2 = getc(fp2); |
| count++; |
| } while ((count < len) && (ch1 == ch2)); |
| |
| SAFE_FCLOSE(fp1); |
| SAFE_FCLOSE(fp2); |
| |
| return !(ch1 == ch2); |
| } |
| |
| static int check_file_offset(const char *m, int fd, loff_t len, |
| loff_t *off_before, loff_t *off_after) |
| { |
| loff_t fd_off = SAFE_LSEEK(fd, 0, SEEK_CUR); |
| int ret = 0; |
| |
| if (off_before) { |
| /* |
| * copy_file_range offset is given: |
| * - fd offset should stay 0, |
| * - copy_file_range offset is updated |
| */ |
| if (fd_off != 0) { |
| tst_res(TFAIL, |
| "%s fd offset unexpectedly changed: %ld", |
| m, (long)fd_off); |
| ret = 1; |
| |
| } else if (*off_before + len != *off_after) { |
| tst_res(TFAIL, "%s offset unexpected value: %ld", |
| m, (long)*off_after); |
| ret = 1; |
| } |
| } |
| /* |
| * no copy_file_range offset given: |
| * - fd offset advanced by length |
| */ |
| else if (fd_off != len) { |
| tst_res(TFAIL, "%s fd offset unexpected value: %ld", |
| m, (long)fd_off); |
| ret = 1; |
| } |
| |
| return ret; |
| } |
| |
| static void test_one(size_t len, loff_t *off_in, loff_t *off_out, char *path) |
| { |
| int ret; |
| size_t to_copy = len; |
| loff_t off_in_value_copy, off_out_value_copy; |
| loff_t *off_new_in = &off_in_value_copy; |
| loff_t *off_new_out = &off_out_value_copy; |
| char str_off_in[32], str_off_out[32]; |
| |
| if (off_in) { |
| off_in_value_copy = *off_in; |
| sprintf(str_off_in, "%ld", (long)*off_in); |
| } else { |
| off_new_in = NULL; |
| strcpy(str_off_in, "NULL"); |
| } |
| |
| if (off_out) { |
| off_out_value_copy = *off_out; |
| sprintf(str_off_out, "%ld", (long)*off_out); |
| } else { |
| off_new_out = NULL; |
| strcpy(str_off_out, "NULL"); |
| } |
| |
| /* |
| * copy_file_range() will return the number of bytes copied between |
| * files. This could be less than the length originally requested. |
| */ |
| do { |
| TEST(sys_copy_file_range(fd_in, off_new_in, fd_out, |
| off_new_out, to_copy, 0)); |
| if (TST_RET == -1) { |
| tst_res(TFAIL | TTERRNO, "copy_file_range() failed"); |
| errcount++; |
| return; |
| } |
| |
| to_copy -= TST_RET; |
| } while (to_copy > 0); |
| |
| ret = check_file_content(FILE_SRC_PATH, path, |
| off_in, off_out, len); |
| if (ret) { |
| tst_res(TFAIL, "file contents do not match"); |
| errcount++; |
| return; |
| } |
| |
| ret |= check_file_offset("(in)", fd_in, len, off_in, off_new_in); |
| ret |= check_file_offset("(out)", fd_out, len, off_out, off_new_out); |
| |
| if (ret != 0) { |
| tst_res(TFAIL, "off_in: %s, off_out: %s, len: %ld", |
| str_off_in, str_off_out, (long)len); |
| errcount++; |
| } |
| } |
| |
| static void open_files(char *path) |
| { |
| fd_in = SAFE_OPEN(FILE_SRC_PATH, O_RDONLY); |
| fd_out = SAFE_OPEN(path, O_CREAT | O_WRONLY | O_TRUNC, 0644); |
| } |
| |
| static void close_files(void) |
| { |
| if (fd_out > 0) |
| SAFE_CLOSE(fd_out); |
| if (fd_in > 0) |
| SAFE_CLOSE(fd_in); |
| } |
| |
| static void copy_file_range_verify(unsigned int n) |
| { |
| int i, j, k; |
| struct tcase *tc = &tcases[n]; |
| |
| if (tc->flags && !cross_sup) { |
| tst_res(TCONF, |
| "copy_file_range() doesn't support cross-device, skip it"); |
| return; |
| } |
| |
| errcount = numcopies = 0; |
| size_t len_arr[] = {11, page_size-1, page_size, page_size+1}; |
| loff_t off_arr_values[] = {0, 17, page_size-1, page_size, page_size+1}; |
| |
| int num_offsets = ARRAY_SIZE(off_arr_values) + 1; |
| loff_t *off_arr[num_offsets]; |
| |
| off_arr[0] = NULL; |
| for (i = 1; i < num_offsets; i++) |
| off_arr[i] = &off_arr_values[i-1]; |
| |
| /* Test all possible cobinations of given lengths and offsets */ |
| for (i = 0; i < (int)ARRAY_SIZE(len_arr); i++) |
| for (j = 0; j < num_offsets; j++) |
| for (k = 0; k < num_offsets; k++) { |
| open_files(tc->path); |
| test_one(len_arr[i], off_arr[j], off_arr[k], tc->path); |
| close_files(); |
| numcopies++; |
| } |
| |
| if (errcount == 0) |
| tst_res(TPASS, |
| "%s copy_file_range completed all %d copy jobs successfully!", |
| tc->message, numcopies); |
| else |
| tst_res(TFAIL, "%s copy_file_range failed %d of %d copy jobs.", |
| tc->message, errcount, numcopies); |
| } |
| |
| static void setup(void) |
| { |
| syscall_info(); |
| page_size = getpagesize(); |
| cross_sup = verify_cross_fs_copy_support(FILE_SRC_PATH, FILE_MNTED_PATH); |
| } |
| |
| static void cleanup(void) |
| { |
| close_files(); |
| } |
| |
| static struct tst_test test = { |
| .setup = setup, |
| .cleanup = cleanup, |
| .tcnt = ARRAY_SIZE(tcases), |
| .mount_device = 1, |
| .mntpoint = MNTPOINT, |
| .all_filesystems = 1, |
| .test = copy_file_range_verify, |
| .test_variants = TEST_VARIANTS, |
| }; |