1 2 . 3
D E B U G G I N G T O O L S
205
checking of the
data stream
structure is done when changing a file size (either
growing or shrinking).
In addition to the above consistency checks, the code that manipulates
the
data stream
structure must also error-check the results of other BFS func-
tions. For example, when growing a file, the block number returned by the
block allocation functions is sanity-checked to ensure that bugs in other parts
of the system do not cause damage. This style of defensive programming may
seem unnecessary, but cross-checking the correctness of other modules helps
to ensure that bugs in one part of the system will not cause another module
to crash or write to improper locations on the disk.
BFS also checks for impossible conditions in a large number of situations.
Impossible conditions are those that should not happen but invariably do.
For example, when locating a data block in a file
data stream
, it is possible to
encounter a
block run
that refers to block zero instead of a valid block num-
ber. If the file system did not check for this situation (which should of course
never happen), it could allow a program to write over the file system super-
block and thus destroy crucial file system information. If the check were not
done and the superblock overwritten, detecting the error would likely not
happen for some time, long after the damage was done. Impossible situations
almost always arise while debugging a system, and thus checking for them
even when it seems unlikely is always beneficial.
When the file system detects an inconsistent state it is best to simply
halt the file system or at least a particular thread of execution. BFS accom-
plishes this by entering a routine that prints a panic message and then loops
infinitely. Halting the system (or at least a particular thread of execution)
allows a programmer to enter a debugger and examine the state of the sys-
tem. In a production environment, it usually renders a locked-up system, and
while that is rather unacceptable, it is preferable to a corrupted hard disk.
12.3
Debugging Tools
Early development of a file system can be done at the user level by building a
test harness that hooks up the core functionality of the file system to a set of
simple API calls that a test program can call. Developing a test environment
allows the file system developer to use source-level debugging tools to get
basic functionality working and to quickly prototype the design. Working at
the user level to debug a file system is much preferable to the typical kernel
development cycle, which involves rebooting after a crash and usually does
not afford the luxuries of user-level source debugging.
Although the debugging environment of every system has its own pecu-
liarities, there is almost always a base level of functionality. The most basic
debugging functionality is the ability to dump memory and to get a stack
backtrace that shows which functions were called before the current state.
Practical File System Design:The Be File System
, Dominic Giampaolo
page 205