xref: /aosp_15_r20/external/ltp/testcases/kernel/syscalls/writev/writev07.c (revision 49cdfc7efb34551c7342be41a7384b9c40d7cab7)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  *
4  *   Copyright (c) Linux Test Project, 2016
5  */
6 
7 /*\
8  * [Description]
9  *
10  * Verify writev() behaviour with partially valid iovec list.
11  * Kernel <4.8 used to shorten write up to first bad invalid
12  * iovec. Starting with 4.8, a writev with short data (under
13  * page size) is likely to get shorten to 0 bytes and return
14  * EFAULT.
15  *
16  * This test doesn't make assumptions how much will write get
17  * shortened. It only tests that file content/offset after
18  * syscall corresponds to return value of writev().
19  *
20  * See: [RFC] writev() semantics with invalid iovec in the middle
21  * https://marc.info/?l=linux-kernel&m=147388891614289&w=2.
22  *
23  * This is also regression test for kernel commits:
24  *
25  * * 20c64ec83a9f ("iomap: fix a regression for partial write errors")
26  * * 3ac974796e5d ("iomap: fix short copy in iomap_write_iter()")
27  */
28 
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <stdio.h>
32 #include <sys/mman.h>
33 #include <sys/stat.h>
34 #include <sys/types.h>
35 #include <sys/uio.h>
36 #include "tst_test.h"
37 
38 #define TESTFILE "testfile"
39 #define CHUNK 64
40 #define BUFSIZE (CHUNK * 4)
41 
42 static void *bad_addr;
43 
test_partially_valid_iovec(int initial_file_offset)44 static void test_partially_valid_iovec(int initial_file_offset)
45 {
46 	int i, fd;
47 	unsigned char buffer[BUFSIZE], fpattern[BUFSIZE], tmp[BUFSIZE];
48 	long off_after;
49 	struct iovec wr_iovec[] = {
50 		{ buffer, CHUNK },
51 		{ bad_addr, CHUNK },
52 		{ buffer + CHUNK, CHUNK },
53 		{ buffer + CHUNK * 2, CHUNK },
54 	};
55 
56 	tst_res(TINFO, "starting test with initial file offset: %d ",
57 		initial_file_offset);
58 
59 	for (i = 0; i < BUFSIZE; i++)
60 		buffer[i] = i % (CHUNK - 1);
61 
62 	memset(fpattern, 0xff, BUFSIZE);
63 	tst_fill_file(TESTFILE, 0xff, CHUNK, BUFSIZE / CHUNK);
64 
65 	fd = SAFE_OPEN(TESTFILE, O_RDWR, 0644);
66 	SAFE_LSEEK(fd, initial_file_offset, SEEK_SET);
67 	TEST(writev(fd, wr_iovec, ARRAY_SIZE(wr_iovec)));
68 	off_after = (long) SAFE_LSEEK(fd, 0, SEEK_CUR);
69 
70 	/* bad errno */
71 	if (TST_RET == -1 && TST_ERR != EFAULT) {
72 		tst_res(TFAIL | TTERRNO, "unexpected errno");
73 		SAFE_CLOSE(fd);
74 		return;
75 	}
76 
77 	/* nothing has been written */
78 	if (TST_RET == -1 && TST_ERR == EFAULT) {
79 		tst_res(TINFO, "got EFAULT");
80 		/* initial file content remains untouched */
81 		SAFE_LSEEK(fd, 0, SEEK_SET);
82 		SAFE_READ(1, fd, tmp, BUFSIZE);
83 		if (memcmp(tmp, fpattern, BUFSIZE))
84 			tst_res(TFAIL, "file was written to");
85 		else
86 			tst_res(TPASS, "file stayed untouched");
87 
88 		/* offset hasn't changed */
89 		if (off_after == initial_file_offset)
90 			tst_res(TPASS, "offset stayed unchanged");
91 		else
92 			tst_res(TFAIL, "offset changed to %ld",
93 				off_after);
94 
95 		SAFE_CLOSE(fd);
96 		return;
97 	}
98 
99 	/* writev() wrote more bytes than bytes preceding invalid iovec */
100 	tst_res(TINFO, "writev() has written %ld bytes", TST_RET);
101 	if (TST_RET > (long) wr_iovec[0].iov_len) {
102 		tst_res(TFAIL, "writev wrote more than expected");
103 		SAFE_CLOSE(fd);
104 		return;
105 	}
106 
107 	/* file content matches written bytes */
108 	SAFE_LSEEK(fd, initial_file_offset, SEEK_SET);
109 	SAFE_READ(1, fd, tmp, TST_RET);
110 	if (memcmp(tmp, wr_iovec[0].iov_base, TST_RET) == 0) {
111 		tst_res(TPASS, "file has expected content");
112 	} else {
113 		tst_res(TFAIL, "file has unexpected content");
114 		tst_res_hexd(TFAIL, wr_iovec[0].iov_base, TST_RET,
115 				"expected:");
116 		tst_res_hexd(TFAIL, tmp, TST_RET,
117 				"actual file content:");
118 	}
119 
120 	/* file offset has been updated according to written bytes */
121 	if (off_after == initial_file_offset + TST_RET)
122 		tst_res(TPASS, "offset at %ld as expected", off_after);
123 	else
124 		tst_res(TFAIL, "offset unexpected %ld", off_after);
125 
126 	SAFE_CLOSE(fd);
127 }
128 
test_writev(void)129 static void test_writev(void)
130 {
131 	test_partially_valid_iovec(0);
132 	test_partially_valid_iovec(CHUNK + 1);
133 	test_partially_valid_iovec(getpagesize());
134 	test_partially_valid_iovec(getpagesize() + 1);
135 }
136 
setup(void)137 static void setup(void)
138 {
139 	bad_addr = SAFE_MMAP(0, 1, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS,
140 			0, 0);
141 }
142 
143 static struct tst_test test = {
144 	.needs_tmpdir = 1,
145 	.setup = setup,
146 	.test_all = test_writev,
147 	.tags = (const struct tst_tag[]) {
148 		{"linux-git", "20c64ec83a9f"},
149 		{"linux-git", "3ac974796e5d"},
150 		{},
151 	}
152 };
153