syscalls: add new test for copy_file_range(2)

Signed-off-by: Li Wang <liwang@redhat.com>
Signed-off-by: Jan Stancek <jstancek@redhat.com>
diff --git a/runtest/syscalls b/runtest/syscalls
index 1860b71..06ce51e 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -1459,3 +1459,5 @@
 
 memfd_create01 memfd_create01
 memfd_create02 memfd_create02
+
+copy_file_range01 copy_file_range01
diff --git a/testcases/kernel/include/aarch64.in b/testcases/kernel/include/aarch64.in
index b81c068..8b725e3 100644
--- a/testcases/kernel/include/aarch64.in
+++ b/testcases/kernel/include/aarch64.in
@@ -256,3 +256,4 @@
 kcmp 272
 getrandom 278
 memfd_create 279
+copy_file_range 391
diff --git a/testcases/kernel/include/arm.in b/testcases/kernel/include/arm.in
index 0a5dde1..1a13661 100644
--- a/testcases/kernel/include/arm.in
+++ b/testcases/kernel/include/arm.in
@@ -339,3 +339,4 @@
 renameat2 (__NR_SYSCALL_BASE+382)
 getrandom (__NR_SYSCALL_BASE+384)
 memfd_create (__NR_SYSCALL_BASE+385)
+copy_file_range (__NR_SYSCALL_BASE+391)
diff --git a/testcases/kernel/include/hppa.in b/testcases/kernel/include/hppa.in
index 3946155..bb7a694 100644
--- a/testcases/kernel/include/hppa.in
+++ b/testcases/kernel/include/hppa.in
@@ -16,3 +16,4 @@
 tee 293
 vmsplice 294
 memfd_create 340
+copy_file_range 346
diff --git a/testcases/kernel/include/i386.in b/testcases/kernel/include/i386.in
index 42c5e3f..9cbb139 100644
--- a/testcases/kernel/include/i386.in
+++ b/testcases/kernel/include/i386.in
@@ -339,3 +339,4 @@
 renameat2 354
 getrandom 355
 memfd_create 356
+copy_file_range 377
diff --git a/testcases/kernel/include/ia64.in b/testcases/kernel/include/ia64.in
index dad25f4..1d5b21c 100644
--- a/testcases/kernel/include/ia64.in
+++ b/testcases/kernel/include/ia64.in
@@ -295,3 +295,4 @@
 renameat2 1338
 getrandom 1339
 memfd_create 1340
+copy_file_range 1347
diff --git a/testcases/kernel/include/powerpc.in b/testcases/kernel/include/powerpc.in
index 10a6e5d..f87192e 100644
--- a/testcases/kernel/include/powerpc.in
+++ b/testcases/kernel/include/powerpc.in
@@ -346,3 +346,4 @@
 renameat2 357
 getrandom 359
 memfd_create 360
+copy_file_range 379
diff --git a/testcases/kernel/include/powerpc64.in b/testcases/kernel/include/powerpc64.in
index 10a6e5d..f87192e 100644
--- a/testcases/kernel/include/powerpc64.in
+++ b/testcases/kernel/include/powerpc64.in
@@ -346,3 +346,4 @@
 renameat2 357
 getrandom 359
 memfd_create 360
+copy_file_range 379
diff --git a/testcases/kernel/include/regen.sh b/testcases/kernel/include/regen.sh
index 3755bbf..929a3f8 100755
--- a/testcases/kernel/include/regen.sh
+++ b/testcases/kernel/include/regen.sh
@@ -60,7 +60,7 @@
 		tst_ret = syscall(NR, ##__VA_ARGS__); \\
 	} \\
 	if (tst_ret == -1 && errno == ENOSYS) { \\
-		tst_brk(TCONF, "syscall(%d) " #NR "not supported", NR); \\
+		tst_brk(TCONF, "syscall(%d) " #NR " not supported", NR); \\
 	} \\
 	tst_ret; \\
 })
diff --git a/testcases/kernel/include/s390.in b/testcases/kernel/include/s390.in
index 770db7f..2526f38 100644
--- a/testcases/kernel/include/s390.in
+++ b/testcases/kernel/include/s390.in
@@ -330,3 +330,4 @@
 renameat2 347
 getrandom 349
 memfd_create 350
+copy_file_range 375
diff --git a/testcases/kernel/include/s390x.in b/testcases/kernel/include/s390x.in
index 770db7f..2526f38 100644
--- a/testcases/kernel/include/s390x.in
+++ b/testcases/kernel/include/s390x.in
@@ -330,3 +330,4 @@
 renameat2 347
 getrandom 349
 memfd_create 350
+copy_file_range 375
diff --git a/testcases/kernel/include/sh.in b/testcases/kernel/include/sh.in
index 0345f8d..b41b578 100644
--- a/testcases/kernel/include/sh.in
+++ b/testcases/kernel/include/sh.in
@@ -363,3 +363,4 @@
 prlimit64 369
 kcmp 378
 memfd_create 385
+copy_file_range 391
diff --git a/testcases/kernel/include/sparc.in b/testcases/kernel/include/sparc.in
index b84c844..729768b 100644
--- a/testcases/kernel/include/sparc.in
+++ b/testcases/kernel/include/sparc.in
@@ -335,3 +335,4 @@
 renameat2 345
 getrandom 347
 memfd_create 348
+copy_file_range 357
diff --git a/testcases/kernel/include/sparc64.in b/testcases/kernel/include/sparc64.in
index 7e0be30..9eb21ee 100644
--- a/testcases/kernel/include/sparc64.in
+++ b/testcases/kernel/include/sparc64.in
@@ -311,3 +311,4 @@
 renameat2 345
 getrandom 347
 memfd_create 348
+copy_file_range 357
diff --git a/testcases/kernel/include/x86_64.in b/testcases/kernel/include/x86_64.in
index dec7742..29051d4 100644
--- a/testcases/kernel/include/x86_64.in
+++ b/testcases/kernel/include/x86_64.in
@@ -306,3 +306,4 @@
 renameat2 316
 getrandom 318
 memfd_create 319
+copy_file_range 326
diff --git a/testcases/kernel/syscalls/.gitignore b/testcases/kernel/syscalls/.gitignore
index d9e75da..497e58e 100644
--- a/testcases/kernel/syscalls/.gitignore
+++ b/testcases/kernel/syscalls/.gitignore
@@ -1126,3 +1126,4 @@
 /perf_event_open/perf_event_open02
 /memfd_create/memfd_create01
 /memfd_create/memfd_create02
+/copy_file_range/copy_file_range01
diff --git a/testcases/kernel/syscalls/copy_file_range/Makefile b/testcases/kernel/syscalls/copy_file_range/Makefile
new file mode 100644
index 0000000..0143bde
--- /dev/null
+++ b/testcases/kernel/syscalls/copy_file_range/Makefile
@@ -0,0 +1,19 @@
+#
+#  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.
+#
+
+top_srcdir		?= ../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/syscalls/copy_file_range/copy_file_range01.c b/testcases/kernel/syscalls/copy_file_range/copy_file_range01.c
new file mode 100644
index 0000000..e6bd921
--- /dev/null
+++ b/testcases/kernel/syscalls/copy_file_range/copy_file_range01.c
@@ -0,0 +1,219 @@
+/*
+ * 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 "linux_syscall_numbers.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 (TEST_RETURN == -1) {
+			tst_res(TFAIL | TTERRNO, "copy_file_range() failed");
+			SAFE_CLOSE(fd_in);
+			SAFE_CLOSE(fd_out);
+			return;
+		}
+
+		to_copy -= TEST_RETURN;
+	} 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 = {
+	.tid = "copy_file_range01",
+	.setup = setup,
+	.needs_tmpdir = 1,
+	.test_all = copy_file_range_verify,
+};