After implementing the global lock stuff last week, I spent a couple of hours on the weekend making renames work. Its a naive implementation, which as we know contributes to fragmentation, but it works and was trivial to implement, which is all I care about now.
Since then I've been building support for notifications. DOS has a pair of functions,
EndNotify() that allow an application to receive a message when a file changes. The application passes a structure to
StartNotify() that contains, among other things, the name of the file and a port (or signal) to notify when something happens to it. The most interesting thing about it is that the file is specified by name, not by lock or anything like that. Additionally, the file doesn't have to exist at the time
StartNotify() is called.
struct NotifyRequest, which gets passed to
StartNotify(), has two filename fields in it. The idea is that the caller sets up
nr_Name, which is the name of a file relative to the current directory, and DOS then build
nr_FullName to contain the full path and volume of the wanted file (expanding any assigns) for the handler to use.
nr_FullName is off-limits to the application, and
nr_Name is off-limits to the handler. Looking through our code, I found that DOS wasn't setting up
nr_FullName at all. We only have two filesystems that support notification, SFS and
ram_handler. SFS, being ported from AmigaOS, did the right thing trying to use
nr_FullName and so notifications didn't work.
nr_Name and built
nr_FullName incorrectly itself, such that notifications worked.
The first thing I did was reimplement
EndNotify() to do the right thing. This involved doing calls to
NameFromLock() which apparently is a standard procedure in AmigaOS for building a full path. It isn't used anywhere in AROS however, with work instead being performed by the IOFS code (
DoName()). That will change when packets finally replace IOFS inside DOS, so it was good for me to learn.
Once that was done,
ram_handler got changed to do the right thing and just use
nr_FullName as it should. That worked, and SFS notifies magically came to life too. The stage was set for notifications in FAT.
I setup a new list in the superblock structure to hold notification requests. Each list entry holds a pointer to the
struct NotifyRequest that was passed in, and a pointer to the global lock for the file (or NULL if the file isn't currently locked). When a global lock is created, we traverse this list looking for entries with no global lock. If
nr_FullName matches the name of the file being locked, a link is created.
This matching process is interesting. Inside
fat.handler files a referenced by two numbers - the cluster that holds the directory that references them, and their entry within that cluster. Converting a path to a cluster/entry pair is pretty straightforward - you break up the path, start at the root dir and look for each piece recursively. (The
GetDirEntryByPath() function does this). Going from a pair to path is much more difficult - you start in pair directory, get the parent dir, search that dir for the subdir to get its name, then go up and do it again until you can assemble a full path from all the name pieces.
Because of this complexity, it actually works out to be faster when we want to see if a name matches a pair to convert the name to a pair (using
GetDirEntryByPath()) and then simply comparing with the wanted pair. Its a shame there's no good way to make it efficient, but fortunately it doesn't have to happen too often.
A notification can be sent by cluster/entry pair or by global lock. The global lock case is easy; we just traverse the notify list and if its lock pointer matches ours, we send the notification. For the pair case, we traverse the list and compare against the cluster/entry in the lock, or if there is no lock, expand the name and compare with that. Both types are needed - when opening, closing or writing a file, there is a lock available (because the application is working with the file). When renaming a file, for example, there no locking occurs, and all we have at that time are cluster/entry pairs.
That pretty much sums it up. The actual implementation was quite simple, again suggesting that the internal APIs are spot on :)
Today I got my laptop back, so got to code on the bus again, which is very nice. I implemented code to actually checked the state of the
READ_ONLY flag before allowing anything that might write. I still to need to have it check the disk write protect stuff and make
C:Lock work, but now we're getting down into the minutae of this thing. Nearly done :)