I believe this may hunt down a crocodile or two... I still need to test it, and before that, think about _how_ to set things up with that damn edge case.
Here's the idea: I have a
DIR_ENTRY structure (which will probably be fixed ZP locations when I convert to assembly).
Code: Select all
typedef struct {
uint32_t sector; // lba sector address
uint16_t entry; // pointer into transient to entry
uint8_t sec_in_cluster; // which sector in this cluster are we in?
uint32_t cluster; // what's the current cluster number?
} DIR_ENTRY;
This holds sufficient information to keep track of what's going on between successive calls to
fs_find_next. As the directory is a simple file, it has the same structure: incrementing sectors in the cluster pool to the size of one cluster, and then if required, subsequent clusters linked through the FAT.
When I search for a file (either to use the file or to prove it doesn't exist so I can create it) I first call
fs_find_first() to initialise the parameters in the
DIR_ENTRY de. There's a bit of a sneaky there in that I set the pointer to the minus first (virtual) entry, because
fs_find_next() automatically increments that, first thing.
Critically, we get the _next_ cluster number as we set these variables. That's disk heavy since it needs to reference and load the FAT sectors, so we don't want to make a habit of it, but it returns either the next cluster number, or a zero if we're currently in the last cluster of the chain (likely on a short directory).
Code: Select all
void fs_find_first (DIR_ENTRY * de)
{
// find the first entry in the cwd which is neither deleted nor a long file
// name, by preseting de and then calling fs_find_next.
// works in the current cwd; sets de members on exit
// note: we cannot simply return the first file; it will always be there
// for a subdirectory (as '.') but in root may have been deleted, in which
// case we should scan past it.
// as fs_find_next starts by moving to the 'next' record, we fake the minus
// oneth entry and then call fs_find_next to do the check.
de->sector = cluster_to_1st_sector(cwd);
de->entry = -32;
de->sec_in_cluster = 0;
de->cluster = next_cluster_number(cwd);
fs_find_next(de);
}
With that in place, and remembering that
de is preserved between calls, we find the next record entry just by adding the record size (32) to
de->entry. On the first call, that sets the pointer to zero, so we start with the first record. That handles the case that the root directory contains no '.' or '..' directories, and may have a valid filename starting at the first entry.
If that addition takes us past the end of the sector currently in
transient, then we can simply increment the sector LBA until we get to the end of the cluster. At that point, we need to move to the next sector, which is in a different cluster.
de->cluster will tell us whether there are more clusters in the chain (if it's non zero, in which case we can use it directly) or if it _is_ zero... we've arrived at the situation in the previous post: we're on the last record of the last cluster, and we need to allocate a new cluster.
Code: Select all
void fs_find_next (DIR_ENTRY * de)
{
// using existing data in de, alter de to reflect the next directory entry
// which is neither deleted nor has a long file name. de contains either a
// pointer to a valid file, or to the first empty entry found (so first byte
// of filename is 0x00)
// Advances through successive sectors in a directory cluster, and if
// necessary to subsequent clusters.
// NOTE: we enter with de pointing at the last record found.
read_sector(de->sector);
while (1)
{
// move to next directory entry
de->entry += 32; // size of entry
// increment the sector and cluster if necessary
if (SECTOR_SIZE == de->entry)
{
// time for the next sector
de->entry = 0;
de->sector++;
de->sec_in_cluster++;
if (de->sec_in_cluster == sectors_per_cluster)
{
// we also need a new cluster
de->sec_in_cluster = 0;
if (0 != de->cluster)
{
// we are not looking at the end of the chain yet
// so it's safe to use this next link
de->sector = cluster_to_1st_sector(de->cluster);
de->cluster = next_cluster_number(de->cluster);
}
else
{
// we need to allocate a new cluster
de->sector = cluster_to_1st_sector(f_alloc());
// this may be redundant since we know we're about to
// crash out on the new zero entry, but still...
de->cluster = 0;
}
}
read_sector(de->sector);
}
// now check the attribute byte to see if it's an LFN entry
if (ATTR_LONG != transient[de->entry + DIR_ATTRIB])
{
// or is it perhaps deleted?
if (0xe5 != transient[de->entry])
{
// nope, then we've found a valid entry
// and de contains the pointers to it
break;
}
// or zero, indicating no further entries?
if (0 == transient[de->entry])
{
break;
}
}
}
}
Hopefully this works. It only does the disc heavy stuff once per sector, and only reads sectors when it needs them, not every call to
fs_find_next().
fs_find_first() and
fs_find_next() are not intended as general purpose user-called routines, though they may be used if the user takes care not to demolish
transient between calls.
Neil
edit: oops, forgot to link the new sector into the directory chain. Thinking...