Before any file I/O can be done, XINU requires that the file be opened for access. This is a common strategy in order that needed information about the file can be cached to speed up I/O.
In XINU, when a file is opened, file information is cached in the struct flblk structure. These structures comprise the entries of the fltab[] array (however, if you look at the book, you'll find that the array is only one entry big - which conflicts with the devsw data structure shown in chapter 11 - this would be a big bug if we tried to compile the code as it appears in the book - our code (unfortunately) does not include the file system at all). This structure includes (among others) the following fields:
The routine dsopen() sets up a flblk entry for the opened file and returns to the caller the index of that flblk in the fltab. First dsopen() checks to see if the named file exists. It (depending on the mode) will create it if it does not. Next, it finds a free fltab entry for the in-memory file info cache. Finally, it initializes the fields described above, and returns the index.
This routine seeks the current file position to the file offset given as an argument. First, lfseek checks to see if the data block buffer has been modified since it was read in. If so, it writes it out to disk. Next, using the given offset, it reads the appropriate index block and data block of the file into the flblk cache. Note that lfseek does not bother to check whether the seek will move the current position off the current data block. Seek could have instead been written to check this, and if it was found that the seek did not change the current data block, it could have simply updated the pointer and returned right away.
The point of lfgetc() is to read the next byte from the file. Normally, this byte can be taken out of the data block buffer. First lfgetc() does some error checking to be sure the calling process is the one that opened the file and that the file was opened for reading. If the file position is found to be past the end, EOF is then returned. Otherwise, if the position is beyond the cached data block, then the data block is written back to disk (if it has been modified), and the next index and data block are read in. Finally, the character is read from the buffer, and the current position incremented.
Lfputc() is almost exactly the same as lfgetc(), except that the data block cache is modified rather than read. Note that the actual disk file is not updated - only the cached data block is modified.
These routines are trivially coded, but are horribly inefficient. Lfread() simply makes repeated calls to lfgetc() to fill its buffer, and lfwrite() makes repeated calls to lfputc() to write its data.
Lfclose() takes a previously opened file and de-caches it. First, lfclose() flushes the data block buffer if it has been modified. Then it frees the flblk entry for use by another open file, decrements the counter of open files, and finally writes the directory entry to disk in order that the counter is updated on disk.