Xiafs dentry deletion fixed

This is pretty nice. I’m happy to say that the bug mentioned in the previous post, where deleting a bunch of entries from a directory, then copying new ones in, would cause fsck to complain about bad directories and some inodes and zones to be “free, but marked in use” has been fixed.

It was an interesting problem. To simplify the xiafs port, I had hollowed out the minixfs code to build on because xiafs was originally an extension of the Minix filesystem, but not everything carried straight over between the two filesystems. Directory entries were one of those things. The minix fs dentries were fixed length, so all you had to do was set the inode number to 0 to delete the entry. Xiafs, however, extends the previous directory entry to cover the deleted entry (ext2 works this way as well, I believe). This requires passing the previous dentry into the unlink function along with the entry being deleted, so I had to change the find and delete entry functions to do that. That was pretty straightforward.

After I did that, the “free, but marked in use” errors went away, but fsck was still deeply unhappy about directory entries after adding, then deleting, a bunch of files to a directory. The problem here was that xiafs expects to read all of its data in 1024 byte blocks (theoretically it could support larger blocks, but that was never implemented). In the old kernels it would just read the data off the disk itself, but now you use the page cache to fetch data off the disks. This is a good thing, but the pages come in in 4096 byte chunks. The xiafs directory code expects the directory entries to be aligned along 1024 byte boundaries, and just joining directory entries together willy nilly would result in unaligned records spanning blocks. The filesystem would still work, but fsck would scream about the error. Limiting the dentry record lengths to 1024 bytes wasn’t a solution, because all of the dentries combined within the 1024 byte block need to fit. The easiest case to think of is “.” and “..” when the directory is empty - “.” has a record length of 12, and “..” has a record length of 1012 bytes. New dentries get added, reducing the length of the last record until the 1024 bytes are filled. When that happens, dir->i_size is increased and a new 1024 byte directory entry is added. Deleted entries are joined to the previous entry, but they can’t be longer than 1024 bytes and cannot extend past the end of the block.

My solution is here. In plain(ish) English, what it’s doing is after it checks to make sure that the current and previous dentry are not the same, and sets the previous dentry to the correct value if needed, it takes the position in the 4KB page of the previous entry, ANDs it by 1023 (the xiafs block size minus one) to figure out what the position would be in a 1024 byte block, and adds the lengths of the previous dentry and the dentry being deleted to that position. If that value is less than or equal to the xiafs block size, it adds the deleted dentry’s length to the previous entry and sets the pos and len variables to their new values. Otherwise, it just sets the dentry’s inode to 0.

It was a bit tricky, but now it seems to work as well as can be expected. It is, after all, a filesystem with a 2GB volume limit and 64MB max file size, so while porting it was very interesting, I obviously don’t expect much serious work do be done with it. It’s done everything I wanted to do with it, though.

modern-xiafs on github.

comments powered by Disqus