Don't write outside the requested range
[test-ssd.git] / test-ssd-write.c
1 /*
2 * test-ssd-write.c
3 *
4 * Hard disk write workload: write random (or zeroed) data at random
5 * locations in a file, and optionally validate by re-reading some
6 * blocks after a while.
7 *
8 * Compile with:
9 * gcc -O2 -Wall -o test-ssd-write test-ssd-write.c
10 *
11 * Hard disk setups known to stop responding with this program (requires a
12 * warm machine reboot to get the drive to respond, as soft reset is not
13 * enough):
14 *
15 * Vendor Model Firmware Controller # drives tested HW BIOS
16 * Intel (Lenovo) SSDSC2BW180A3L LE1i SandForce 2281 1 (3.5 Debian kernel) Lenovo x230 G2ET90WW (2.50) 2012-20-12
17 * Intel (Lenovo) SSDSC2BW180A3L LF1i SandForce 2281 2 (3.2 Debian kernel) Lenovo x230 G2ET90WW (2.50) 2012-20-12
18 * Intel (Lenovo) SSDSC2BW180A3L LF1i SandForce 2281 1 (3.7.9 Arch kernel) Lenovo x230 G2ET86WW (2.06) 2012-11-13
19 * Intel (Lenovo) SSDSC2BW180A3L LF1i SandForce 2281 1 (3.2 Debian kernel) Lenovo x200
20 *
21 * In order to narrow down the problem, we ran this test on a number of
22 * other hardware configurations which don't show this problem:
23 *
24 * Vendor Model Firmware Controller # drives tested HW BIOS
25 * Intel SSDSA2M160G2GC 2CV102HD Intel 1
26 * Intel SSDSA2CW300G310 ??????? Intel 1 (over USB on 3.8 kernel)
27 * Intel SSDSA2CT040G3 4PC10302 Intel 1 (3.2 kernel)
28 * Intel SSDSA2CT040G3 4PC10362 Intel 1 (3.2 kernel)
29 * Intel SSDSC2CT120A3K5 ??????? SandForce 2281 1 (on HP SmartArray P212 with 3.2 kernel)
30 * OCZ OCZ-VERTEX3 2.25 SF-2281 1 (3.7.9 Arch kernel)
31 * OCZ OCZSSD2-2VTXE180G 1.37 SF-1200 1 (3.7.6 Arch kernel)
32 *
33 * Under Linux (Debian, Ubuntu, various kernels), after about 5 minutes,
34 * we get this result:
35 *
36 * ata1.00: exception Emask 0x0 SAct 0x1 SErr 0x0 action 0x6 frozen
37 * ata1.00: failed command: WRITE FPDMA QUEUED
38 * ata1.00: cmd 61/28:00:a8:a9:7f/00:00:02:00:00/40 tag 0 ncq 20480 out
39 * res 40/00:00:00:4f:c2/00:00:00:00:00/00 Emask 0x4 (timeout)
40 * ata1.00: status: { DRDY }
41 * ata1.00: COMRESET failed (errno=-16)
42 * ata1.00: COMRESET failed (errno=-16)
43 *
44 * This happens with random data, zeroed data (-z), and has been tested
45 * with file sizes of 1MB, 200MB, 3.1GB and 21GB. The error happens with
46 * and without the validation (-v) option.
47 *
48 * Copyright 2013 - Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
49 *
50 * Permission is hereby granted, free of charge, to any person obtaining
51 * a copy of this software and associated documentation files (the
52 * "Software"), to deal in the Software without restriction, including
53 * without limitation the rights to use, copy, modify, merge, publish,
54 * distribute, sublicense, and/or sell copies of the Software, and to
55 * permit persons to whom the Software is furnished to do so, subject to
56 * the following conditions:
57 *
58 * The above copyright notice and this permission notice shall be
59 * included in all copies or substantial portions of the Software.
60 *
61 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
62 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
63 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
64 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
65 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
66 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
67 * SOFTWARE.
68 */
69
70 #include <stdio.h>
71 #include <sys/types.h>
72 #include <sys/stat.h>
73 #include <fcntl.h>
74 #include <assert.h>
75 #include <stdlib.h>
76 #include <unistd.h>
77 #include <stdint.h>
78 #include <inttypes.h>
79 #include <string.h>
80
81 enum write_mode {
82 WRITE_RANDOM = 0,
83 WRITE_ZEROES,
84 };
85
86 static enum write_mode write_mode;
87 static int verify_mode;
88
89 #define PRINT_FREQ 100000
90 #define VALIDATE_FREQ 10000
91 #define BUFLEN 4096
92
93 static void rand_buf(char *buf, size_t buflen)
94 {
95 size_t i;
96
97 for (i = 0; i < buflen; i += sizeof(int)) {
98 union {
99 int i;
100 char c[sizeof(int)];
101 } u;
102 u.i = rand();
103 memcpy(&buf[i], u.c, sizeof(int));
104 }
105 }
106
107 static uint64_t validate(int fd, const char *validate_buf, off_t offset)
108 {
109 char buf[BUFLEN];
110 ssize_t rret;
111 off_t pos;
112 int i;
113 uint64_t diffcnt = 0;
114
115 if (!verify_mode)
116 return 0;
117
118 pos = lseek(fd, offset, SEEK_SET);
119 if (pos < 0) {
120 perror("lseek");
121 exit(EXIT_FAILURE);
122 }
123 rret = read(fd, buf, BUFLEN);
124 if (rret != BUFLEN) {
125 fprintf(stderr, "Error at read from offset: %zu\n",
126 pos);
127 perror("read");
128 exit(EXIT_FAILURE);
129 }
130 for (i = 0; i < BUFLEN; i++) {
131 if (buf[i] != validate_buf[i]) {
132 diffcnt++;
133 }
134 }
135 return diffcnt;
136 }
137
138 static int rand_write(int fd, size_t len)
139 {
140 off_t pos, offset;
141 uint64_t write_nr = 0;
142 ssize_t wret;
143 char buf[BUFLEN];
144 char validate_buf[BUFLEN];
145 off_t validate_offset = 0;
146 uint64_t valcount;
147 int ret;
148
149 if (len < BUFLEN) {
150 fprintf(stderr, "Error: File size needs to be at least %u\n", BUFLEN);
151 exit(EXIT_FAILURE);
152 }
153
154 memset(buf, 0, BUFLEN);
155
156 for (;;) {
157 if (len > UINT32_MAX) {
158 offset = (((size_t) rand() << 32) + (size_t) rand()) % (len - BUFLEN);
159 } else {
160 offset = rand() % (len - BUFLEN);
161 }
162
163 if ((offset >= validate_offset &&
164 offset < validate_offset + BUFLEN)
165 || (validate_offset >= offset &&
166 validate_offset < offset + BUFLEN)) {
167 /* Don't overwrite the range we want to validate. */
168 continue;
169 }
170 if (write_mode == WRITE_RANDOM)
171 rand_buf(buf, BUFLEN);
172 /* Save validation buffer and position */
173 if (write_nr % VALIDATE_FREQ == 0 && verify_mode) {
174 memcpy(validate_buf, buf, BUFLEN);
175 validate_offset = offset;
176 }
177 pos = lseek(fd, offset, SEEK_SET);
178 if (pos < 0) {
179 perror("lseek");
180 exit(EXIT_FAILURE);
181 }
182 wret = write(fd, buf, BUFLEN);
183 if (wret != BUFLEN) {
184 fprintf(stderr, "Error at write to offset: %zu\n",
185 pos);
186 perror("write");
187 exit(EXIT_FAILURE);
188 }
189
190 /*
191 * Advise that we won't be re-reading the blocks. This
192 * will ask the kernel to drop pages related to this
193 * file quickly from its page cache, thus forcing a read
194 * from disk.
195 */
196 ret = fdatasync(fd);
197 if (ret) {
198 perror("fdatasync");
199 exit(EXIT_FAILURE);
200 }
201 ret = posix_fadvise(fd, offset, BUFLEN, POSIX_FADV_DONTNEED);
202 if (ret) {
203 perror("posix_fadvise");
204 exit(EXIT_FAILURE);
205 }
206
207 write_nr++;
208 if (write_nr % PRINT_FREQ == 0) {
209 printf("Status: %" PRIu64 " writes.\n", write_nr);
210 }
211
212 /*
213 * Use the validation buffer and position saved
214 * VALIDATE_FREQ operations earlier.
215 */
216 if (write_nr % VALIDATE_FREQ == 0 && verify_mode) {
217 valcount = validate(fd, validate_buf, validate_offset);
218 if (valcount) {
219 printf("VALIDATION ERROR at offset %zu, %" PRIu64 " bytes differ\n",
220 validate_offset, valcount);
221 }
222 }
223 }
224 return 0;
225 }
226
227 int main(int argc, char **argv)
228 {
229 int fd, ret, i, seed;
230 size_t len;
231 off_t pos;
232 ssize_t wret;
233
234 if (argc < 4) {
235 printf("Usage: %s <output file> <len (64-bit)> <seed (32-bit)> <-z to write zeroes> <-v to verify written data>\n", argv[0]);
236 exit(EXIT_FAILURE);
237 }
238
239 fd = open(argv[1], O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
240 if (fd < 0) {
241 perror("open");
242 exit(EXIT_FAILURE);
243 }
244
245 len = atoll(argv[2]);
246 seed = atoi(argv[3]);
247 srand(seed);
248
249 printf("Creating file %s of length %zu, random seed %u\n", argv[1], len,
250 seed);
251
252 for (i = 4; i < argc; i++) {
253 if (strcmp(argv[i], "-z") == 0) {
254 write_mode = WRITE_ZEROES;
255 } else if (strcmp(argv[i], "-v") == 0) {
256 verify_mode = 1;
257 }
258 }
259
260 switch (write_mode) {
261 case WRITE_RANDOM:
262 printf("Generating random data\n");
263 break;
264 case WRITE_ZEROES:
265 printf("Filling with zeroes (compressible pattern)\n");
266 break;
267 default:
268 printf("Unsupported write-mode\n");
269 exit(EXIT_FAILURE);
270 }
271
272 if (verify_mode) {
273 printf("Verification mode activated.\n");
274 }
275
276 /* Grow file */
277 pos = lseek(fd, len - 1, SEEK_SET);
278 if (pos < 0) {
279 perror("lseek");
280 exit(EXIT_FAILURE);
281 }
282 wret = write(fd, "", 1);
283 if (wret < 0) {
284 perror("write");
285 exit(EXIT_FAILURE);
286 }
287
288 /* Advise the OS that we are performing random accesses */
289 ret = posix_fadvise(fd, 0, len, POSIX_FADV_RANDOM);
290 if (ret) {
291 perror("posix_fadvise");
292 exit(EXIT_FAILURE);
293 }
294
295 ret = rand_write(fd, len);
296 if (ret) {
297 exit(EXIT_FAILURE);
298 }
299
300 ret = close(fd);
301 assert(!ret);
302 exit(EXIT_SUCCESS);
303 }
This page took 0.036558 seconds and 5 git commands to generate.