File System Hooks
Articles and Tips: article
Developer Support Engineer
Novell Developer Support
01 Mar 1996
NLM applications can be made aware of specific file system events (pre and/or post) by registering call back routines. File System call back routines are registered with NetWare using the NWAddFSMonitorHook() function. When writing FS Hook call back routines, developers should remember these points: "Post" routines must not block (or sleep), and must never call a function that does; "Pre" routines are allowed to block, but doing so will degrade a file server's performance; be aware of context; never "kill" an OS thread; and do not use call back routines to perform actions that could be done elsewhere.
In the good ol' days, programmers wrote code that was very sequential: "open file, process file, close file" kind of stuff. Today's applications are much less sequential and much more "event driven." Instead of polling, vogue applications expect to be notified that a specific event has occurred. NetWare is no exception to this trend. NLM applications can be made aware of specific file system events (pre and/or post) by registering call-back routines. File System call-back routines are registered with NetWare using the NWAddFSMonitorHook() function.
NetWare file servers communicate with NetWare clients using the NetWare Core Protocol (NCP). NCPs are a form of Remote Procedure Call (RPC), where the NetWare client passes data parameters to the NetWare server and the server processes the client's data, returning the results to the client.
Assume, for example, that a NetWare DOS client wants to open a file on a file server. The client would construct an NCP packet that specifies the volume and path, open mode, etc. The client sends this packet to a specific file server.
NetWare receives the NCP packet from the client and assigns a thread to process it. The thread examines the packet and discovers that it's an "Open File" request. The thread then jumps to the appropriate "Open File" code.
The "Open File" code checks to see if any NLMs have registered for a pre-"Open File" event. If so, the "Open File" code calls the NLM's registered pre-execution routine.
The pre-execution routine is expected to quickly perform its task, then return control of the thread to the "Open File" code. Generally the pre-execution routine would return a "successful" value indicating that the OS may proceed with the DOS client's "Open File" request. However, the pre-execution routine can also return a non-zero value that indicates a failure. (Failure codes should comply with NetWare's standard error codes as documented in NITERROR.H or NWERRNO.H).
When the pre-execution routine is finished and control is returned to the "Open File" code, the return code is examined to see if the NLM's registered pre-execution routine returned a success or failure code.
If failure is indicated, the thread constructs a reply-NCP to report the error and sends it to the DOS client. The file, specified in the DOS client's request-NCP, is not opened and the thread does no further processing on the DOS client's request-NCP.
If the NLM's registered pre-execution routine returns successful, NetWare will attempt to open the specified file. Then the "Open File" code checks to see if any NLMs have registered for a post-"File Open" event. If so, the "Open File" code calls the NLM's registered post-execution routine.
The post-execution routine is expected to quickly perform its task, then return control of the thread to the "Open File" code. It must not sleep (or block). One of the parameters passed to the post-execution routine is the NCP's completion code. This value is supplied to the post routine to indicate whether or not NetWare successfully completed the client's request.
Why Can't a Post-Execution Routine Block?
NetWare cache memory containing directory entry information is referenced by the data passed to a post-execution routine. Referencing the directory entry information in "live" cache is risky because cache blocks are constantly re-used as NetWare sees fit. Data passed to the post-execution routine references data (such as directory entry information) stored in cache. The NCP handler thread can get away with this informal use of memory as long as it does not relinquish control of the CPU.
If it did relinquish control, the cache block could be allocated to another process that would probably change the values stored in the cache block. Therefore, it is important that the NCP handler thread not relinquish control. An FS Hook post-execution routine is an extension to the NCP handler, uses the same thread and also must not relinquish control. Therefore, an FS Hook post-execution routine must not block (or sleep) or call any function that might cause it to block. Hence, the following statement in the NLM SDK:
Note: The post-execution call back function must not sleep, becausethe fields in the return structure are subject to change.
Even so, some developers overlook this statement and attempt to block anyway. Rather than allowing these developers to get further "out in the weeds," current versions of NetWare will usually ABEND if a post-execution routine block is detected.
FS Hooks and NCPs
File system hooks have a direct correlation with NCPs. The structure of the data passed to pre and post routines has a direct relationship to the underlying NCP structure. This is why each hook has a different data structure.
As NetWare evolves, new NCPs are introduced. The first "Open File" NCPs were very DOS specific in structure. Later, generic "Open File" NCPs were structured to work with a variety of client operating systems. The specific NCP a NetWare client elects to use depends on the client's operating system and the version of NetWare requester the client has installed.
Generic file system hooks correspond to the later generic NCPs. Non-generic file system hooks correspond to the earlier DOS-specific NCPs. The structures of the new generic NCPs are not the same as the earlier DOS-specific NCPs, and therefore the new generic NCPs require new file system hooks.
If an NLM application must trap all the pre-"File Open" requests on a Novell file server, it must hook both FSHOOK_PRE_OPENFILE and FSHOOK_PRE_GEN_OPEN_CREATE
NetWare is fast and Novell plans to keep it that way. Novell took a risk introducing FS Hooks. NLM developers must write quick, bulletproof FS Hook call-back routines. An FS hook call-back routine that is slow and buggy casts a bad shadow on NetWare. From the standpoint of end-users (NetWare clients), it is not possible to determine why access to a server is slow. A poorly written FS Hook call-back routine may be the problem, but an end-user cannot determine that. NetWare will usually be blamed for the resulting poor performance.
Realizing this, Novell is careful about the type of FS Hooks available to NLMs. For example, there are hooks to inform an NLM that a file has been opened, closed and deleted; however, no hooks exist for file reading and writing events. The reasoning is that files are opened, closed, and deleted relatively infrequently compared to file read and writes. Extrapolating this further, a badly written "Read File" hook would have a much more noticeable effect on performance than a badly written "Open File" hook.
Tips and Hints
Here are some points to remember when writing FS Hook call-back routines:
"Post" routines must not block (or sleep), and must never call a function that does. "Pre"routines are allowed to block, but doing so degrades a file server's performance.
Be aware of your context. If a CLIB context is needed in your pre-routine, Novell recommends calling GetThreadGroupID()and SetThreadContextSpecifier(thread_id, NO_CONTEXT) in main(), and then calling SetThreadGroupID() in your pre-routine. Remember to reset the context-via a second call to SetThreadGroupID()-before returning control to the caller. This method insures compatibility with NetWare 3.x, which does not have an "auto-context" behavior. The function SetThreadGroupID() is a blocking function. Therefore, do not attempt to change your context in a post-routine.
Never "kill" an OS thread. Do not call exit(), ExitThread() (or any similar function) from your FS Hookcall-back routine.
Before you work on the plumbing, turn off the water and drain the pipe. When your NLM is preparing to shut down, be sure to call NWRemoveFSMonitorHook(). Before your NLM terminates, make sure that there are no threads still running in your call-back routines. I generally increment a global variable called NLM_thread-ntfirst thing in my call-back routines, and decrement the same just before my call-back routine returns control to the caller. Inmy shut-down code, I wait for NLM_thread-nt to become zero before I allow my NLM to terminate.
Make it very fast. The primary purpose of the thread executing your call-back routine is to service a NetWare client. Do not use this thread to perform actions that could be done somewhere else. If you need to do more extensive processing on the data you receive from an FS Hook, copy the data to a buffer (such as a linked list) and return control of the thread to NetWare. Then use another thread in your NLM to process the data at a more appropriate time.
* Originally published in Novell AppNotes
The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.