opinion noted
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, StartNotify()
and 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. ram_handler
read nr_Name
and built nr_FullName
incorrectly itself, such that notifications worked.
The first thing I did was reimplement StartNotify()
and EndNotify()
to do the right thing. This involved doing calls to GetDeviceProc()
and 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 :)