blob: 2bca8a403173fb2dd6fbf352ef11898002df9019 [file] [log] [blame]
/*
* Copyright (c) Linux Test Project, 2017
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License for more details.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include "tst_test.h"
#include "tst_safe_stdio.h"
#include "lapi/syscalls.h"
#define TEST_FILE_1 "copy_file_range_ltp01.txt"
#define TEST_FILE_2 "copy_file_range_ltp02.txt"
#define STR "abcdefghijklmnopqrstuvwxyz12345\n"
#define verbose 0
static size_t *len_arr;
static loff_t **off_arr;
static int len_sz, off_sz;
static void setup(void)
{
int i, fd, page_size;
page_size = getpagesize();
fd = SAFE_OPEN(TEST_FILE_1, O_RDWR | O_CREAT, 0664);
/* Writing page_size * 4 of data into test file */
for (i = 0; i < (int)(page_size * 4); i++)
SAFE_WRITE(1, fd, STR, strlen(STR));
SAFE_CLOSE(fd);
len_sz = 4;
len_arr = malloc(sizeof(size_t) * len_sz);
len_arr[0] = 11;
len_arr[1] = page_size - 1;
len_arr[2] = page_size;
len_arr[3] = page_size + 1;
off_sz = 6;
off_arr = malloc(sizeof(loff_t *) * off_sz);
for (i = 1; i < off_sz; i++)
off_arr[i] = malloc(sizeof(loff_t));
off_arr[0] = NULL;
*off_arr[1] = 0;
*off_arr[2] = 17;
*off_arr[3] = page_size - 1;
*off_arr[4] = page_size;
*off_arr[5] = page_size + 1;
}
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_ori, loff_t *off_after)
{
int ret = 0;
if (off_ori) {
/* FD should stay untouched, and off_in/out is updated */
loff_t fd_off = SAFE_LSEEK(fd, 0, SEEK_CUR);
if (fd_off == 0) {
if (verbose)
tst_res(TPASS, "%s FD offset unchanged", m);
} else {
tst_res(TFAIL, "%s FD offset changed: %ld",
m, (long)fd_off);
ret = 1;
}
if (!off_after) {
tst_res(TFAIL, "%s offset is NULL", m);
ret = 1;
}
if ((off_after) && (*off_ori + len == *off_after)) {
if (verbose) {
tst_res(TPASS, "%s offset advanced as"
" expected: %ld", m, (long)*off_after);
}
} else {
tst_res(TFAIL, "%s offset unexpected value: %ld",
m, (long)*off_after);
ret = 1;
}
} else {
/* FD offset is advanced by len */
loff_t fd_off = SAFE_LSEEK(fd, 0, SEEK_CUR);
if (fd_off == len) {
if (verbose) {
tst_res(TPASS, "%s FD offset changed as"
" expected: %ld", m, (long)fd_off);
}
} else {
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)
{
size_t to_copy = len;
int fd_in, fd_out, ret;
loff_t *off_in_ori = off_in;
loff_t *off_out_ori = off_out;
loff_t off_in_copy;
loff_t off_out_copy;
char str_off_in[32], str_off_out[32];
if (off_in) {
off_in_copy = *off_in;
off_in = &off_in_copy;
sprintf(str_off_in, "%ld", (long)*off_in);
} else {
strcpy(str_off_in, "NULL");
}
if (off_out) {
off_out_copy = *off_out;
off_out = &off_out_copy;
sprintf(str_off_out, "%ld", (long)*off_out);
} else {
strcpy(str_off_out, "NULL");
}
fd_in = SAFE_OPEN(TEST_FILE_1, O_RDONLY);
fd_out = SAFE_OPEN(TEST_FILE_2, O_CREAT | O_WRONLY | O_TRUNC, 0644);
/*
* copy_file_range() will return the number of bytes copied between
* files. This could be less than the length originally requested.
*/
do {
TEST(tst_syscall(__NR_copy_file_range, fd_in, off_in, fd_out,
off_out, to_copy, 0));
if (TST_RET == -1) {
tst_res(TFAIL | TTERRNO, "copy_file_range() failed");
SAFE_CLOSE(fd_in);
SAFE_CLOSE(fd_out);
return;
}
to_copy -= TST_RET;
} while (to_copy > 0);
ret = check_file_content(TEST_FILE_1, TEST_FILE_2,
off_in_ori, off_out_ori, len);
if (ret)
tst_res(TFAIL, "file contents do not match");
ret |= check_file_offset("(in)", fd_in, len, off_in_ori, off_in);
ret |= check_file_offset("(out)", fd_out, len, off_out_ori, off_out);
tst_res(ret == 0 ? TPASS : TFAIL, "off_in: %s, off_out: %s, len: %ld",
str_off_in, str_off_out, (long)len);
SAFE_CLOSE(fd_in);
SAFE_CLOSE(fd_out);
}
static void copy_file_range_verify(void)
{
int i, j, k;
for (i = 0; i < len_sz; i++)
for (j = 0; j < off_sz; j++)
for (k = 0; k < off_sz; k++)
test_one(len_arr[i], off_arr[j], off_arr[k]);
}
static struct tst_test test = {
.setup = setup,
.needs_tmpdir = 1,
.test_all = copy_file_range_verify,
};