This is a test for a filesystem model aiming for binary compatibility with FAT32 (specifically, the Linux implementation.) The model relies on ACL2's single threaded objects (stobjs) in order to slurp FAT32 disk images into memory quickly by placing their contents in stobj arrays, which provide constant time random access. This is in contrast to a list representation, which is expensive to construct because there are as many cons pairs as there are elements in the list. Another performance improvement comes from the use of read-file-into-string, which allows us to use a string representation of the contents of a file, which again allows us to have constant time random access to the characters within the string. This is in contrast to the functions described in read-bytes$-n, which use list representations for the data read from files and suffer from the same drawbacks described above. In this test, we attempt to test the ability of our stobj model (and the "cat" implementation which rests atop it) to reckon with regular files which span across multiple FAT32 clusters. This requires a traversal of the file allocation table, which contains a linked list of cluster indices for each file. This traversal of the file allocation table goes about as one would expect a linked list traversal to go: for each cluster, we look up the cluster index in the file allocation table to get the next cluster index, and continue doing so until we reach an end-of-file value. However, this may not be straightforward to justify termination for, which ACL2 requires. More precisely, we run into the risk of entering a cycle of dereferences, which would cause an infinite loop. The way around this is to rely on the file length, which is a piece of metadata provided by FAT32 as well as other filesystems. By decrementing the length of the read for each recursive call, we can ensure that the read terminates. Moreover, we can actually place a sanity check on the filesystem by asserting that an end-of-file value in the file allocation table must be reached when the remaining length of the read becomes zero. One behaviour that we have not yet addressed is the fact that directories, too, can span across multiple clusters. This is actually not as far-fetched a scenario as one might think: given that each directory entry in FAT32 is 32 bytes long (this is a coincidence; the reason for the name FAT32 is that each entry in the file allocation table is 32 bits wide), each sector is at least 512 bytes long and each cluster is at least one sector long, we could exceed one cluster for the contents of a directory with few as 17 entries. Thus, our functions for reading directory entries, or searching for a directory entry given a filename, must account for this and either fetch the entire contents of the directory, or adopt a buffered reading algorithm which recognises that multiple clusters may need to be read into the buffer, a few at a time. There are limitations on this behaviour, however. One limitation that applies to all files, including directories, is that their length cannot exceed 0xffffffff bytes, in order to allow the file length to be stored in a 32-bit segment of the directory entry. Another, specific to directories, is that a directory cannot be larger that 0x200000 bytes in size, thus allowing for 0x10000 directory entries - this ensures that programs which need to count the number of entries in a directory with a 16-bit counter can do so. One goal of this cat(1) re-implementation is to co-simulate with the actual system utility, to ensure our filesystem can do the basic things we need it to. Another goal is to serve as an ACL2 specification for another implementation of cat, which uses only system calls instead of manipulating the disk image directly. Towards this latter goal, we can actually construct a proof of equivalence between cat implementations. This will continue to be useful as and when we add a realistic buffer cache to our filesystem. Once we are done with this, we expect to move into file operations which actually modify our filesystem. This would also entail proofs that converting a string to our stobj representation, and converting a stobj to a string, are operations which are mathematically inverses. In parallel, we also plan to figure out a means for programs, which use filesystem operations, to interact with our filesystem implementation. It's not exactly clear how a FUSE driver, for instance, could get an in-memory buffer from a calling program and provide it for ACL2 to write a struct stat into (how would we represent a struct stat, anyway?)