altos: Support open on multiple simultaneous FAT files
[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 short_test_fs(void)
355 {
356         int     len;
357         char    buf[345];
358
359         if (ao_fat_open("HELLO   TXT",AO_FAT_OPEN_READ) == AO_FAT_SUCCESS) {
360                 printf ("File contents for HELLO.TXT\n");
361                 while ((len = ao_fat_read(buf, sizeof(buf))))
362                         write(1, buf, len);
363                 ao_fat_close();
364         }
365         
366         if (ao_fat_creat("NEWFILE TXT") == AO_FAT_SUCCESS) {
367                 printf ("Create new file\n");
368                 for (len = 0; len < 2; len++)
369                         ao_fat_write("hello, world!\n", 14);
370                 ao_fat_seek(0, AO_FAT_SEEK_SET);
371                 printf ("read new file\n");
372                 while ((len = ao_fat_read(buf, sizeof (buf))))
373                         write (1, buf, len);
374                 ao_fat_close();
375         }
376
377         check_fs();
378 }
379
380 void
381 long_test_fs(void)
382 {
383         char    name[12];
384         int     id;
385         MD5_CTX ctx;
386         unsigned char   md5_check[MD5_DIGEST_LENGTH];
387         char buf[337];
388         int     len;
389         uint64_t        total_file_size = 0;
390
391         total_reads = total_writes = 0;
392
393         printf ("   **** Creating %d files\n", NUM_FILES);
394
395         memset(sizes, '\0', sizeof (sizes));
396         for (id = 0; id < NUM_FILES; id++) {
397                 sprintf(name, "D%07dTXT", id);
398                 if ((id % (NUM_FILES/50)) == 0) {
399                         printf ("."); fflush(stdout);
400                 }
401                 if (ao_fat_creat(name) == AO_FAT_SUCCESS) {
402                         int j;
403                         char    line[64];
404                         check_bufio("file created");
405                         MD5_Init(&ctx);
406                         for (j = 0; j < LINES_FILE; j++) {
407                                 int len, ret;
408                                 sprintf (line, "Hello, world %d %d\r\n", id, j);
409                                 len = strlen(line);
410                                 ret = ao_fat_write((uint8_t *) line, len);
411                                 if (ret <= 0)
412                                         break;
413                                 total_file_size += ret;
414                                 MD5_Update(&ctx, line, ret);
415                                 sizes[id] += ret;
416                                 if (ret != len)
417                                         printf ("write failed %d\n", ret);
418                         }
419                         ao_fat_close();
420                         MD5_Final(&md5[id][0], &ctx);
421                         check_bufio("file written");
422                 }
423         }
424
425         printf ("\n   **** Write IO: read %llu write %llu data sectors %llu\n", total_reads, total_writes, (total_file_size + 511) / 512);
426
427         check_bufio("all files created");
428         printf ("   **** All done creating files\n");
429         check_fs();
430
431         total_reads = total_writes = 0;
432
433         printf ("   **** Comparing %d files\n", NUM_FILES);
434
435         for (id = 0; id < NUM_FILES; id++) {
436                 uint32_t size;
437                 sprintf(name, "D%07dTXT", id);
438                 size = 0;
439                 if ((id % (NUM_FILES/50)) == 0) {
440                         printf ("."); fflush(stdout);
441                 }
442                 if (ao_fat_open(name, AO_FAT_OPEN_READ) == AO_FAT_SUCCESS) {
443                         MD5_Init(&ctx);
444                         while ((len = ao_fat_read((uint8_t *) buf, sizeof(buf))) > 0) {
445                                 MD5_Update(&ctx, buf, len);
446                                 size += len;
447                         }
448                         ao_fat_close();
449                         MD5_Final(md5_check, &ctx);
450                         if (size != sizes[id])
451                                 fatal("file %d: size differs %d written %d read\n",
452                                       id, sizes[id], size);
453                         if (memcmp(md5_check, &md5[id][0], sizeof (md5_check)) != 0)
454                                 fatal ("file %d: checksum failed\n", id);
455                         check_bufio("file shown");
456                 }
457         }
458         printf ("\n  **** Read IO: read %llu write %llu\n", total_reads, total_writes);
459 }
460
461 char *params[] = {
462         "-F 16 -C %s 16384",
463         "-F 32 -C %s 16384",
464         "-F 16 -C %s 65536",
465         "-F 32 -C %s 65536",
466         "-F 16 -C %s 1048576",
467         "-F 32 -C %s 1048576",
468         NULL
469 };
470
471 int
472 main(int argc, char **argv)
473 {
474         int     p;
475
476         if (argv[1])
477                 fs = argv[1];
478
479         for (p = 0; fs_params[p].fat; p++) {
480                 param = &fs_params[p];
481                 ao_fat_init();
482
483                 check_bufio("top");
484                 ao_fat_setup();
485
486                 check_fs();
487                 check_bufio("after setup");
488
489 #ifdef SIMPLE_TEST
490                 short_test_fs();
491 #else
492                 long_test_fs();
493 #endif
494                 ao_fat_unmount();
495         }
496
497         return 0;
498 }