Copyright (c) 1991, 1992 by Frank J. Edwards See the file COPYRIGHT in this distribution for copyright details. The fs generates debugging information through the STREAMS logger facility during most operations. At strategic points throughout the mount/umount process, and in the most of the routines, log messages are generated. Catch the trace logs with strace(1m). Typically I run something like this from the console display: # strace | tee /dev/par Results/strace which pipes the output of strace(1m) into tee(1) so that the trace entries are printed on the parallel printer (/dev/par), recorded in a file (Results/strace), and displayed on the screen (stdout). Since the VFS sometimes crashes badly, I changed the console I/O routines to always put out characters to the serial port at 9600 baud. This means that any console messages also go to my Wyse50 terminal. If the VFS goes into an infinite loop or something, when I do the Ctrl-Amiga-Amiga the last page of data will still be on the terminal. Then my "getpanic" program will do a screen dump of the terminal (when the machine reboots) to grab an image of the data and store it in a file. An unfortunate side-effect is that any modem work gets blown out of the water, so I only use the console routines sparingly (I usually use the STREAMS facilities). Of course, I tend to turn the modem off when I'm doing this work anyway... [The above has changed slightly. There is now a variable which contains a bitmask of how the debugging information should be generated: 0x01 = strlog(), 0x02 = serial port, 0x04 = parallel port, 0x08 = console printf(). That variable is "ados_debug" and can be modified in the running kernel by using adb: "adb -w /unix /dev/kmem". Ignore the warning message. "ados_debug/X" will display the value, and to change it to something else, use "ados_debug/W 5", for instance, to get strlog() and parallel port output. The parallel port is not timed-out, so putting the printer offline will cause the kernel to hang waiting for the printer. Output to the serial port is always at 4800 baud (I needed a speed that my A500 could keep up with -- it's a long story).] Here's a chart that shows which fields are zero/non-zero for the different key block types. These are longwords which are checked by the sanity routines when they try to read a block of the corresponding type. Only applies to the AmigaDOS VFS. Zero Non-Zero ROOT block: rd_rsvp1[0..1] x rd_rsvp2[0] x rd_rsvp3[0..2] x rd_hashsz x ((BlockSz / 4) - 56) Zero Non-Zero FILE block uf_rsvp1[0..1] x uf_hkey x uf_hiseq x # blocks in file uf_hashsz x # entries in uf_data[] uf_block0 x first entry of uf_data[] uf_hashchn x Other files with same Hash? uf_parent x uf_exten x File > 36K? Zero Non-Zero DIR block ud_rsvp1[0..2] x ud_rsvp2[0..1] x ud_rsvp3[0] x ud_rsvp4[0..6] x ud_hkey x ud_parent x Most of the filesystem code does what would be expected of each of the routines, ie. vfs_mount() mounts the filesystem. Some are not quite so obvious; ados_inactive() is called when a vnode goes inactive and is usually to free the memory for the vnode as well as putting the anode back onto the anode freelist, flushing pages to disk, etc. A lot of these routines need major work since my fs is read-only currently, and doesn't implement a lot of fancy caching routines (see TODO also). -- 11/08/92 Some key points about certain routines: ados_inactive is called after the OS finishes with an anode. The VN_RELE macro calls vn_rele() which causes VOP_INACTIVE() to be called. This is also done during the purging of the DNLC (directory name lookup cache). Ados_inactive() should free the anode and put it back on the free list. I don't free the memory associated with it 'cuz there's too much startup overhead in reading it back. The filesystem has a perf- ormance problem in any case, partly because I don't use the hashing routine to find a filename, I just search the directory sequentially. It was easier to implement, but I may pay for it in the long run since I'll have to rewrite that section now! (But the directory handling stuff is a mess anyway and needs to be redone.) And The kernel needs much of the headers read anytime it sees a directory (to determine size, nlinks, etc). ados_aget tries to find the anode that goes with the keyblk in one of the in-memory lists, aused or afree. Aused is the list of currently active anodes. Afree is the list of anodes that are free. Being a free anode doesn't mean the data isn't valid. Whenever the code finishes with an anode it goes on the end of the list. When a fresh anode is needed, the first one on the list is used. This implements a simple LRU for anode reuse. Being on the free list doesn't mean that the contents of the anode are invalid. In fact, when an anode is not "in use" it goes onto the free list without any data being purged, although it will (in the future) be queued for flushing back to disk. That's why the ados_aget() routine checks both the used and free list before allocating a new anode. (NB: it should check the free list from the tail forward; FIXME) ados_lookup this is the function in vnops.c which is responsible for locating a given filename. It's supposed to parse as much of the pathname as it wants, returning whatever pieces aren't consumed in the process (this was for efficiency in RFS). This is the ugliest part of this VFS. Mostly because the layering between the routines is trash. On my list of "things to do" is a cflow chart that I'll use to restructure the calls. dnlc_enter dnlc_lookup dnlc_purge1 dnlc_remove so far this code only uses the first three of these. They should be pretty much self-explanatory as to what they do. They take as arguments the vnode pointer of the directory and a filename within the directory. The dnlc_remove() would be used when files are renamed or removed (so the cache is no longer valid) or when selective purging of the cache is desired. Speaking of purging, the dnlc_purge1() routine purges a single entry from the cache. This is used in ados_aget() thus: if there are no free anodes (on the free list) then the purge routine is called. If a vnode is purged, a "while" loop causes the free list to be checked again. If there's still not a free anode, the purge routine is called again. This is repeated until there's nothing left in the cache, or a free anode shows up on the free list (because it was just purged). If all cache entries are purged and there are still no free anodes, the current implementation will dynamically allocate 4 more anodes and add them to the free list, then loop back to the above mentioned "while" loop. This is kinda' kludgey and forces the cache to be purged unnecessarily. A better method would be to call dnlc_purge_vfs() which only purges cache entries of a given vfs type. But this routine isn't discussed much in the SVR4 tutorial paper, so I'm shying away from using it until later. When I have a source license (yeah, right). There are a couple of other ugly coding hacks that will change eventually. For instance, (struct llchain) is cached immediately if a directory is the target of ados_aget() or when reading the raw data blocks of a directory that isn't already in the cache (the first should prevent the second, but just in case...) The ugly part is that the a_idx field is used to indicate if the llchain list has been read yet. It really should just use the a_ll member of (struct anode) and test for zero/non-zero. There are other similar strangeness which are a result of piecing the code together based on pseudo-code provided in the SVR4 tutorial, some of the system header files, and the Trivial File System (TFS) sent to me by Ford and Keith. The directory reading/parsing routines need to be thrown out and the AmigaDOS hashing routines used instead. No caching of directories at all (let the VMM cache the blocks that we read a lot). We can cache file extension blocks, though, which means we still need the (struct ll_chain) and the above paragraph still applies. -- 11/13/92 The sanitychk() routine would complain about a reserved field of a ROOT block being non-zero, but it didn't indicate which one. It does now. -- 11/16/92 Turns out that the directory caching FS uses that field in the directory blocks (called the "extension" field). So I added some hacks that tell the vfs to ignore that field sometimes! (Like when mounting a DOS3 or DOS5 filesystem.) Mail sent to "bugs@cbmvax" for clarification on this. Also, it occurs to me that other folks won't have the kernel mods for KPutFmt() and pKPutFmt() for the debugging module. There are two ways around this. (1) Edit the Makefile and remove "debug.o" from the list of object modules required and then "#define DBG(a,b,c,d) /* nothing */" in ados_defs.h to prevent those routines from being called. Or (2) edit the "debug.c" file and comment out the if stmts at the end of the file which reference those two functions. I'm going to see about hacking a #define into the Makefile to fix this before I ship it out. Look for "-DNO_..." in the Makefile and uncomment it if it's in there. Each one provides for the elimination of a different debugging output stream. -- 11/19/92 The getblk() and reada() routines in subr2.c were using the smaller block size when the SMALL_BLOCK bit was set, but they didn't account for the fact that only reads of a regular file were affected, not directories. This was allowing the mount command to work, but nothing would show up in it since the ados_readdir() and ados_lookup() routines were getting garbage data back from the low-level read-a-block routines. -- 11/25/92 The ados_readdir() and ados_lookup() routines need to handle "." and ".." especially for the shell's purposes. Each of those routines can simply write a fake entry. Calling the raw ados_read() routine on a directory should return the raw data, but ados_readdir() should fake the first two entries. I'd like to see an option on the mount command that makes the filenames case-insensitive like they are under AmigaDOS. I like the fact that I can type filenames without hitting shift, but they will sort in the right locations when displayed by "ls" or "echo *". Speaking of wildcards, what would case folding do to the shell? I need to check the dnlc routines and make sure that filenames are going into, and coming out of, the cache at the right places. And another idea for a permission scheme: the owner and group id of the original mount point are propogated to all files on the partition. This allows the superuser to mount the partition on a user's subdirectory and that user would have access. Or mount it in a protected location and regular permission checks would limit access.