1 /*
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright © 2023 Keith Packard
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above
14  *    copyright notice, this list of conditions and the following
15  *    disclaimer in the documentation and/or other materials provided
16  *    with the distribution.
17  *
18  * 3. Neither the name of the copyright holder nor the names of its
19  *    contributors may be used to endorse or promote products derived
20  *    from this software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
33  * OF THE POSSIBILITY OF SUCH DAMAGE.
34  */
35 #define _DEFAULT_SOURCE
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <stdint.h>
39 #include <string.h>
40 
41 #ifndef TEST_FILE_NAME
42 #define TEST_FILE_NAME "FIO.TXT"
43 #endif
44 
45 #define check(condition, message) do {                  \
46         if (!(condition)) {                             \
47             printf("%s: %s\n", message, #condition);    \
48             exit(1);                                    \
49         }                                               \
50     } while(0)
51 
52 #define RAND_MUL        1664525
53 #define RAND_ADD        1013904223
54 
55 static inline unsigned long
my_permute(long input)56 my_permute(long input)
57 {
58     return input * RAND_MUL + RAND_ADD;
59 }
60 
61 static inline uint8_t
test_value(long offset,int generation)62 test_value(long offset, int generation)
63 {
64     return (uint8_t) my_permute(offset * (generation + 1));
65 }
66 
67 static void
fill_buf(long offset,int generation,void * b,size_t len)68 fill_buf(long offset, int generation, void *b, size_t len)
69 {
70     uint8_t *bp = b;
71     while (len--)
72         *bp++ = test_value(offset++, generation);
73 }
74 
75 static long
check_buf(long offset,int generation,void * b,size_t len)76 check_buf(long offset, int generation, void *b, size_t len)
77 {
78     uint8_t *bp = b;
79     while (len--) {
80         if (test_value(offset, generation) != *bp++) {
81             return offset;
82         }
83         offset++;
84     }
85     return -1L;
86 }
87 
88 #define BUF_SIZE        2048
89 
90 static uint8_t out[BUF_SIZE];
91 static uint8_t in[BUF_SIZE];
92 
93 static long offsets[BUF_SIZE];
94 
95 static void
random_offsets(int generation,int num_ops,size_t per_op)96 random_offsets(int generation, int num_ops, size_t per_op)
97 {
98     int i, j;
99 
100     for (i = 0; i < num_ops; i++)
101         offsets[i] = per_op * i;
102 
103     for (i = 0; i < num_ops - 1; i++) {
104         j = my_permute(i * num_ops * per_op * generation) % (num_ops - i) + i;
105         long tmp = offsets[i];
106         offsets[i] = offsets[j];
107         offsets[j] = tmp;
108     }
109 }
110 
111 int
main(int argc,char ** argv)112 main(int argc, char **argv)
113 {
114     FILE *f;
115     int generation = 0;
116     size_t ret;
117     size_t size;
118     size_t nmemb, per_op;
119     off_t offset;
120     char *file_name = TEST_FILE_NAME;
121     int order;
122     int num_ops;
123     int op;
124     int status = 0;
125     int repeat = 10000;
126     long failed;
127 
128     if (argc > 1) {
129         repeat = atoi(argv[1]);
130         if (!repeat)
131             repeat = 10000;
132     }
133 
134     printf("test file %s repeat %d\n", file_name, repeat);
135 
136     /* Make sure we can create a file, write contents and read them back */
137 
138     for (size = 1; size < 16; size <<= 1) {
139         nmemb = BUF_SIZE / size;
140         for (order = 0; order < 2; order++) {
141             for (per_op = nmemb; per_op >= 1; per_op /= 2) {
142 
143                 num_ops = nmemb / per_op;
144 
145                 f = fopen(file_name, "w");
146                 check(f != NULL, "fopen w");
147 
148                 fill_buf(0, generation, out, BUF_SIZE);
149 
150                 if (order == 0) {
151                     for (op = 0; op < num_ops; op++) {
152                         ret = fwrite(out + op * size * per_op, size, per_op, f);
153                         check(ret == per_op, "fwrite ordered");
154                     }
155                 } else {
156                     random_offsets(generation, num_ops, per_op);
157                     offset = 0;
158                     for (op = 0; op < num_ops; op++) {
159                         fseek(f, size * offsets[op] - offset, SEEK_CUR);
160                         ret = fwrite(out + size * offsets[op], size, per_op, f);
161                         offset = size * offsets[op] + size * per_op;
162                         check(ret == per_op, "fwrite random");
163                     }
164                 }
165                 fclose(f);
166 
167                 memset(in, 0xcd, BUF_SIZE);
168 
169                 f = fopen(file_name, "r");
170                 check(f != NULL, "fopen r");
171                 if (order == 0) {
172                     for (op = 0; op < num_ops; op++) {
173                         ret = fread(in + op * size * per_op, size, per_op, f);
174                         check(ret == per_op, "fread ordered");
175                     }
176                 } else {
177                     random_offsets(generation * 1146533039, num_ops, per_op);
178                     offset = 0;
179                     for (op = 0; op < num_ops; op++) {
180                         fseek(f, size * offsets[op] - offset, SEEK_CUR);
181                         ret = fread(in + size * offsets[op], size, per_op, f);
182                         offset = size * offsets[op] + size * per_op;
183                         check(ret == per_op, "fread random");
184                     }
185                 }
186 
187                 fclose(f);
188                 failed = check_buf(0, generation, in, BUF_SIZE);
189                 if (failed != -1L) {
190                     printf("check failed at offset %ld size %zd order %d per_op %zd\n",
191                            failed, size, order, per_op);
192                     status = 1;
193                 }
194                 generation++;
195             }
196         }
197     }
198 
199     /* Now do a mix of read/write operations */
200 
201     f = fopen(file_name, "w+");
202     check(f != NULL, "fopen w+");
203 
204     memset(out, '\0', BUF_SIZE);
205 
206     ret = fwrite(out, 1, BUF_SIZE, f);
207     check(ret == BUF_SIZE, "fwrite mixed setup");
208 
209     for (int generation = 0; generation < repeat; generation++) {
210 
211         if (random() & 0x8) {
212             long write_offset, write_len;
213 
214             write_offset = random() % BUF_SIZE;
215             write_len = random() % (BUF_SIZE - write_offset);
216 
217             fill_buf(write_offset, generation, out + write_offset, write_len);
218 
219             fseek(f, write_offset, SEEK_SET);
220 
221             ret = fwrite(out + write_offset, 1, write_len, f);
222             check(ret == (size_t) write_len, "fwrite mixed");
223         } else {
224             long read_offset, read_len, i;
225             read_offset = random() % BUF_SIZE;
226             read_len = random() % (BUF_SIZE - read_offset);
227 
228             fseek(f, read_offset, SEEK_SET);
229 
230             /* Make sure we can unget a char and have it appear on the input */
231             if (generation % 10 == 9) {
232                 int c = getc(f);
233                 ungetc(c + 1, f);
234             }
235 
236             ret = fread(in + read_offset, 1, read_len, f);
237             check(ret == (size_t) read_len, "fread mixed");
238 
239             for (i = 0; i < read_len; i++) {
240                 uint8_t expect = out[read_offset + i];
241                 if (generation % 10 == 9 && i == 0)
242                     expect++;
243                 if (in[read_offset + i] != expect) {
244                     printf("mixed check failed at offset %ld generation %d\n",
245                            read_offset + i, generation);
246                     status = 1;
247                 }
248             }
249         }
250     }
251 
252     (void) remove(file_name);
253 
254     exit(status);
255 }
256