altos: Add a FAT test that re-writes the same file multiple times
[fw/altos] / src / test / ao_fat_test.c
1 /*
2  * Copyright © 2013 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 #include <stdint.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <stdarg.h>
22 #include <string.h>
23 #include <getopt.h>
24 #include <math.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <openssl/md5.h>
28
29 #define AO_FAT_TEST
30
31 void
32 ao_mutex_get(uint8_t *mutex)
33 {
34 }
35
36 void
37 ao_mutex_put(uint8_t *mutex)
38 {
39 }
40
41 void
42 ao_panic(uint8_t panic)
43 {
44         printf ("panic %d\n", panic);
45         abort();
46 }
47
48 #define AO_PANIC_BUFIO  15
49
50 #define ao_cmd_success  0
51
52 uint8_t ao_cmd_status;
53 uint32_t ao_cmd_lex_u32;
54
55 void
56 ao_cmd_decimal()
57 {
58 }
59
60 #define ao_cmd_register(x)
61
62 struct ao_cmds {
63         void            (*func)(void);
64         const char      *help;
65 };
66
67 int fs_fd;
68
69 uint64_t        total_reads, total_writes;
70
71 uint8_t
72 ao_sdcard_read_block(uint32_t block, uint8_t *data)
73 {
74         ++total_reads;
75         lseek(fs_fd, block * 512, 0);
76         return read(fs_fd, data, 512) == 512;
77 }
78
79 uint8_t
80 ao_sdcard_write_block(uint32_t block, uint8_t *data)
81 {
82         ++total_writes;
83         lseek(fs_fd, block * 512, 0);
84         return write(fs_fd, data, 512) == 512;
85 }
86
87 struct fs_param {
88         int     fat;
89         int     blocks;
90 } fs_params[] = {
91         { .fat = 16, .blocks = 16384 },
92         { .fat = 32, .blocks = 16384 },
93         { .fat = 16, .blocks = 65536 },
94         { .fat = 32, .blocks = 65536 },
95         { .fat = 16, .blocks = 1048576 },
96         { .fat = 32, .blocks = 1048576 },
97         { .fat = 0, .blocks = 0 },
98 };
99
100 char            *fs = "fs.fat";
101 struct fs_param *param;
102
103 void
104 ao_sdcard_init(void)
105 {
106         char    cmd[1024];
107
108         if (fs_fd) {
109                 close(fs_fd);
110                 fs_fd = 0;
111         }
112         snprintf(cmd, sizeof(cmd), "rm -f %s && mkfs.vfat -F %d -C %s %d",
113                  fs, param->fat, fs, param->blocks);
114         if (system (cmd) != 0) {
115                 fprintf(stderr, "'%s' failed\n", cmd);
116                 exit(1);
117         }
118         fs_fd = open(fs, 2);
119         if (fs_fd < 0) {
120                 perror (fs);
121                 exit(1);
122         }
123 }
124
125 #include "ao_bufio.c"
126 void
127 check_bufio(char *where)
128 {
129         int     b;
130
131         for (b = 0; b < AO_NUM_BUF; b++) {
132                 if (ao_bufio[b].busy) {
133                         printf ("%s: buffer %d busy. block %d seqno %u\n",
134                                 where, b, ao_bufio[b].block, ao_bufio[b].seqno & 0xffff);
135                         abort();
136                 }
137         }
138 }
139
140
141 void
142 check_fat(void);
143
144 #include "ao_fat.c"
145
146 /* Get the next cluster entry in the chain */
147 static cluster_t
148 ao_fat_entry_raw_read(cluster_t cluster, uint8_t fat)
149 {
150         sector_t                sector;
151         cluster_offset_t        offset;
152         uint8_t                 *buf;
153         cluster_t               ret;
154
155         if (fat32)
156                 cluster <<= 2;
157         else
158                 cluster <<= 1;
159         sector = cluster >> SECTOR_SHIFT;
160         offset = cluster & SECTOR_MASK;
161         buf = ao_fat_sector_get(fat_start + fat * sectors_per_fat + sector);
162         if (!buf)
163                 return 0;
164         if (fat32)
165                 ret = get_u32(buf + offset);
166         else
167                 ret = get_u16(buf + offset);
168         ao_fat_sector_put(buf, 0);
169         return ret;
170 }
171
172 void
173 dump_fat(void)
174 {
175         int     e;
176
177         printf ("\n **** FAT ****\n\n");
178         for (e = 0; e < number_cluster; e++) {
179                 if ((e & 0xf) == 0x0)
180                         printf ("%04x: ", e);
181                 if (fat32)
182                         printf (" %08x", ao_fat_entry_raw_read(e, 0));
183                 else
184                         printf (" %04x", ao_fat_entry_raw_read(e, 0));
185                 if ((e & 0xf) == 0xf)
186                         putchar ('\n');
187         }
188         if (e & 0xf)
189                 putchar('\n');
190 }
191
192 void
193 fat_list(void)
194 {
195         dirent_t                entry = 0;
196         struct ao_fat_dirent    dirent;
197
198         printf ("  **** Root directory ****\n");
199         while (ao_fat_readdir(&entry, &dirent)) {
200                 printf ("%04x: %-8.8s.%-3.3s %02x %04x %d\n",
201                         entry,
202                         dirent.name,
203                         dirent.name + 8,
204                         dirent.attr,
205                         dirent.cluster,
206                         dirent.size);
207         }
208
209         printf ("  **** End of root directory ****\n");
210 }
211
212 void
213 fatal(char *msg, ...)
214 {
215 //      dump_fat();
216 //      fat_list();
217
218         va_list l;
219         va_start(l, msg);
220         vfprintf(stderr, msg, l);
221         va_end(l);
222
223         abort();
224 }
225
226 void
227 check_fat(void)
228 {
229         cluster_t       e;
230         int             f;
231
232         for (e = 0; e < number_cluster; e++) {
233                 cluster_t       v = ao_fat_entry_raw_read(e, 0);
234                 for (f = 1; f < number_fat; f++) {
235                         cluster_t       o = ao_fat_entry_raw_read(e, f);
236                         if (o != v)
237                                 fatal ("fats differ at %08x (0 %08x %d %08x)\n", e, v, f, o);
238                 }
239         }
240 }
241
242 cluster_t
243 check_file(dirent_t dent, cluster_t first_cluster, dirent_t *used)
244 {
245         cluster_t       clusters = 0;
246         cluster_t       cluster;
247
248         if (!first_cluster)
249                 return 0;
250         
251         for (cluster = first_cluster;
252              fat32 ? !AO_FAT_IS_LAST_CLUSTER(cluster) : !AO_FAT_IS_LAST_CLUSTER16(cluster);
253              cluster = ao_fat_entry_raw_read(cluster, 0))
254         {
255                 if (!ao_fat_cluster_valid(cluster))
256                         fatal("file %d: invalid cluster %08x\n", dent, cluster);
257                 if (used[cluster])
258                         fatal("file %d: duplicate cluster %08x also in file %d\n", dent, cluster, used[cluster]-1);
259                 used[cluster] = dent;
260                 clusters++;
261         }
262         return clusters;
263 }
264
265 void
266 check_fs(void)
267 {
268         dirent_t        r;
269         cluster_t       cluster, chain;
270         dirent_t        *used;
271         uint8_t         *dent;
272
273         check_fat();
274
275         used = calloc(sizeof (dirent_t), number_cluster);
276
277         for (r = 0; (dent = ao_fat_root_get(r)); r++) {
278                 cluster_t       clusters;
279                 offset_t        size;
280                 cluster_t       first_cluster;
281                 char            name[11];
282
283                 if (!dent)
284                         fatal("cannot map dent %d\n", r);
285                 memcpy(name, dent+0, 11);
286                 first_cluster = get_u16(dent + 0x1a);
287                 if (fat32)
288                         first_cluster |= (cluster_t) get_u16(dent + 0x14) << 16;
289                 size = get_u32(dent + 0x1c);
290                 ao_fat_root_put(dent, r, 0);
291
292                 if (name[0] == AO_FAT_DENT_END) {
293                         break;
294                 }
295
296                 clusters = check_file(r + 1, first_cluster, used);
297                 if (size == 0) {
298                         if (clusters != 0)
299                                 fatal("file %d: zero sized, but %d clusters\n", clusters);
300                 } else {
301                         if (size > clusters * bytes_per_cluster)
302                                 fatal("file %d: size %u beyond clusters %d (%u)\n",
303                                       r, size, clusters, clusters * bytes_per_cluster);
304                         if (size <= (clusters - 1) * bytes_per_cluster)
305                                 fatal("file %d: size %u too small clusters %d (%u)\n",
306                                       r, size, clusters, clusters * bytes_per_cluster);
307                 }
308         }
309         if (!fat32) {
310                 for (; r < root_entries; r++) {
311                         uint8_t *dent = ao_fat_root_get(r);
312                         if (!dent)
313                                 fatal("cannot map dent %d\n", r);
314                         if (dent[0] != AO_FAT_DENT_END)
315                                 fatal("found non-zero dent past end %d\n", r);
316                         ao_fat_root_put(dent, r, 0);
317                 }
318         } else {
319                 check_file((dirent_t) -1, root_cluster, used);
320         }
321
322         for (cluster = 0; cluster < 2; cluster++) {
323                 chain = ao_fat_entry_raw_read(cluster, 0);
324
325                 if (fat32) {
326                         if ((chain & 0xffffff8) != 0xffffff8)
327                                 fatal("cluster %d: not marked busy\n", cluster);
328                 } else {
329                         if ((chain & 0xfff8) != 0xfff8)
330                                 fatal("cluster %d: not marked busy\n", cluster);
331                 }
332         }
333         for (; cluster < number_cluster; cluster++) {
334                 chain = ao_fat_entry_raw_read(cluster, 0);
335
336                 if (chain != 0) {
337                         if (used[cluster] == 0)
338                                 fatal("cluster %d: marked busy, but not in any file\n", cluster);
339                 } else {
340                         if (used[cluster] != 0)
341                                 fatal("cluster %d: marked free, but found in file %d\n", cluster, used[cluster]-1);
342                 }
343         }
344 }
345
346 #define NUM_FILES       100
347 #define LINES_FILE      500000
348
349 uint32_t                sizes[NUM_FILES];
350
351 unsigned char           md5[NUM_FILES][MD5_DIGEST_LENGTH];
352
353 void
354 micro_test_fs(void)
355 {
356         int8_t  fd;
357         char    name[] = "FOO        ";
358         char    buf[512];
359         int     len;
360
361         printf ("write once\n");
362         if ((fd = ao_fat_creat(name)) >= 0) {
363                 ao_fat_write(fd, "hello world\n", 12);
364                 ao_fat_close(fd);
365         }
366
367         printf ("read first\n");
368         if ((fd = ao_fat_open(name, AO_FAT_OPEN_READ)) >= 0) {
369                 len = ao_fat_read(fd, buf, sizeof(buf));
370                 write (1, buf, len);
371                 ao_fat_close(fd);
372         }
373         
374         printf ("write again\n");
375         if ((fd = ao_fat_creat(name)) >= 0) {
376                 ao_fat_write(fd, "hi\n", 3);
377                 ao_fat_close(fd);
378         }
379
380         printf ("read again\n");
381         if ((fd = ao_fat_open(name, AO_FAT_OPEN_READ)) >= 0) {
382                 len = ao_fat_read(fd, buf, sizeof(buf));
383                 write (1, buf, len);
384                 ao_fat_close(fd);
385         }
386
387         printf ("write 3\n");
388         if ((fd = ao_fat_creat(name)) >= 0) {
389                 int     l;
390                 char    c;
391
392                 for (l = 0; l < 10; l++) {
393                         for (c = ' '; c < '~'; c++)
394                                 ao_fat_write(fd, &c, 1);
395                         c = '\n';
396                         ao_fat_write(fd, &c, 1);
397                 }
398                 ao_fat_close(fd);
399         }
400
401         printf ("read 3\n");
402         if ((fd = ao_fat_open(name, AO_FAT_OPEN_READ)) >= 0) {
403                 while ((len = ao_fat_read(fd, buf, sizeof(buf))) > 0)
404                         write (1, buf, len);
405                 ao_fat_close(fd);
406         }
407
408         check_fs();
409         printf ("all done\n");
410 }
411
412
413 void
414 short_test_fs(void)
415 {
416         int     len;
417         int8_t  fd;
418         char    buf[345];
419
420         if ((fd = ao_fat_open("HELLO   TXT",AO_FAT_OPEN_READ)) >= 0) {
421                 printf ("File contents for HELLO.TXT\n");
422                 while ((len = ao_fat_read(fd, buf, sizeof(buf))))
423                         write(1, buf, len);
424                 ao_fat_close(fd);
425         }
426         
427         if ((fd = ao_fat_creat("NEWFILE TXT")) >= 0) {
428                 printf ("Create new file\n");
429                 for (len = 0; len < 2; len++)
430                         ao_fat_write(fd, "hello, world!\n", 14);
431                 ao_fat_seek(fd, 0, AO_FAT_SEEK_SET);
432                 printf ("read new file\n");
433                 while ((len = ao_fat_read(fd, buf, sizeof (buf))))
434                         write (1, buf, len);
435                 ao_fat_close(fd);
436         }
437
438         check_fs();
439 }
440
441 void
442 long_test_fs(void)
443 {
444         char    name[12];
445         int     id;
446         MD5_CTX ctx;
447         unsigned char   md5_check[MD5_DIGEST_LENGTH];
448         char buf[337];
449         int     len;
450         int8_t  fd;
451         uint64_t        total_file_size = 0;
452
453         total_reads = total_writes = 0;
454
455         printf ("   **** Creating %d files\n", NUM_FILES);
456
457         memset(sizes, '\0', sizeof (sizes));
458         for (id = 0; id < NUM_FILES; id++) {
459                 sprintf(name, "D%07dTXT", id);
460                 if ((id % ((NUM_FILES+49)/50)) == 0) {
461                         printf ("."); fflush(stdout);
462                 }
463                 if ((fd = ao_fat_creat(name)) >= 0) {
464                         int j;
465                         char    line[64];
466                         check_bufio("file created");
467                         MD5_Init(&ctx);
468                         for (j = 0; j < LINES_FILE; j++) {
469                                 int len, ret;
470                                 sprintf (line, "Hello, world %d %d\r\n", id, j);
471                                 len = strlen(line);
472                                 ret = ao_fat_write(fd, line, len);
473                                 if (ret <= 0)
474                                         break;
475                                 total_file_size += ret;
476                                 MD5_Update(&ctx, line, ret);
477                                 sizes[id] += ret;
478                                 if (ret != len)
479                                         printf ("write failed %d\n", ret);
480                         }
481                         ao_fat_close(fd);
482                         MD5_Final(&md5[id][0], &ctx);
483                         check_bufio("file written");
484                 }
485         }
486
487         printf ("\n   **** Write IO: read %llu write %llu data sectors %llu\n", total_reads, total_writes, (total_file_size + 511) / 512);
488
489         check_bufio("all files created");
490         printf ("   **** All done creating files\n");
491         check_fs();
492
493         total_reads = total_writes = 0;
494
495         printf ("   **** Comparing %d files\n", NUM_FILES);
496
497         for (id = 0; id < NUM_FILES; id++) {
498                 uint32_t size;
499                 sprintf(name, "D%07dTXT", id);
500                 size = 0;
501                 if ((id % ((NUM_FILES+49)/50)) == 0) {
502                         printf ("."); fflush(stdout);
503                 }
504                 if ((fd = ao_fat_open(name, AO_FAT_OPEN_READ)) >= 0) {
505                         MD5_Init(&ctx);
506                         while ((len = ao_fat_read(fd, buf, sizeof(buf))) > 0) {
507                                 MD5_Update(&ctx, buf, len);
508                                 size += len;
509                         }
510                         ao_fat_close(fd);
511                         MD5_Final(md5_check, &ctx);
512                         if (size != sizes[id])
513                                 fatal("file %d: size differs %d written %d read\n",
514                                       id, sizes[id], size);
515                         if (memcmp(md5_check, &md5[id][0], sizeof (md5_check)) != 0)
516                                 fatal ("file %d: checksum failed\n", id);
517                         check_bufio("file shown");
518                 }
519         }
520         printf ("\n  **** Read IO: read %llu write %llu\n", total_reads, total_writes);
521 }
522
523 char *params[] = {
524         "-F 16 -C %s 16384",
525         "-F 32 -C %s 16384",
526         "-F 16 -C %s 65536",
527         "-F 32 -C %s 65536",
528         "-F 16 -C %s 1048576",
529         "-F 32 -C %s 1048576",
530         NULL
531 };
532
533 void
534 do_test(void (*test)(void))
535 {
536         ao_fat_init();
537
538         check_bufio("top");
539         ao_fat_setup();
540
541         check_fs();
542         check_bufio("after setup");
543         (*test)();
544         ao_fat_unmount();
545 }
546
547 int
548 main(int argc, char **argv)
549 {
550         int     p;
551
552         if (argv[1])
553                 fs = argv[1];
554
555         for (p = 0; fs_params[p].fat; p++) {
556                 param = &fs_params[p];
557
558                 do_test(micro_test_fs);
559                 do_test(short_test_fs);
560                 do_test(long_test_fs);
561         }
562         unlink (fs);
563
564         return 0;
565 }