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