[[https://www.revelation.com/|Sign up on the Revelation Software website to have access to the most current content, and to be able to ask questions and get answers from the Revelation community]] ==== System Variables for Audit Trail (AREV Specific) ==== === At 02 SEP 1999 06:49:07PM Wilhelm Schmitt wrote: === {{tag>"AREV Specific"}} For an audit trail, we want to be able to monitor changes made to a record in a specific file. Among others the audit record must contain: 1. @username 2. @station 3. date() 4. time() 5. PROGRAM NAME, LINE NUMBER or TEMPLATE/WINDOW NAME. We do not know in advance where the update comes from. It may be through TCL(EDIT), WINDOW) or directly through RBasic code. Any ideas? Wilhelm ---- === At 02 SEP 1999 08:38PM akaplan@sprezzatura.com - [url=http://www.sprezzatura.com]Sprezzatura Group[/url] wrote: === Only way to do this properly is through an MFS. Here's the text of something I wrote a few years back that should help some. akaplan@sprezzatura.com [url=http://www.sprezzatura.com]Sprezzatura Group[/url] [img]http://www.sprezzatura.com/zz.jpg[/img] TAMING the beast: MFSs for beginners by Aaron Kaplan Revelation Technologies, Inc. There are few things as misunderstood in Advanced Revelation as Modifying Filing Systems (MFSs). Many fear MFSs and tremble at the thought of writing one. Others valiantly attempt to conquer the beast, yet retreat vanquished and demoralized. But MFSs are not something to be feared. They are our friends and can help us in ways that mankind has yet to determine. However, you should still treat an MFS with kindness (and keep your hands away from its mouth while it's eating). MFSs defined First on your mind might be "Well, that's all well and good. But what is this MFS, this fearsome beast that has brought mighty programmers to the brink of tears?" An MFS is a program in R/BASIC (or C or assembly language, if you have an adventurous streak that approaches suicidal) that monitors all input and output (I/O) to a file. All I/O. Well, what would you use such a feature for? Oh, the possibilities are endless. You could use it for creating a security log whenever a user accesses specified records, or you could prevent certain users from seeing data in protected fields, or you could encrypt and decrypt data, or you could write your own indexing routines, or you could logically join separate data files into one logical file, and the list goes on and on and on ... just about anything that involves reading or writing data. How does an MFS work? MFSs are not difficult to work with, but you do have to understand some basics about how file I/O works in Advanced Revelation for the concept of an MFS to make sense. The absolute first thing to understand is that a filing system such as linear hash is really just a subroutine. For linear hash, the subroutine is RTP57. There are some other filing systems available in Advanced Revelation, including ROS files (RTP51), the ASCII bond (ASCII_BFS), and the dBASE bond (DBASE_BFS). When a program somewhere in Advanced Revelation executes an I/O command--R/BASIC commands such as OPEN, READ, WRITE, DELETE or TCL commands such as DEFINEFILE (CREATEFILE)--Advanced Revelation translates this command into a call to the filing system. The filing system subroutine does whatever is required to fulfill the request (for example, for a read request it goes and fetches the record from a file somewhere) and passes control back to Advanced Revelation. An MFS is a subroutine, too. To "install" an MFS, you modify the way Advanced Revelation calls the filing system. Instead of calling the BFS, Advanced Revelation calls your MFS with exactly the same parameters it would normally pass to the BFS. Your MFS will be called for anything that regards files and volumes. This means not just obvious I/O like a READ or WRITE in an R/BASIC program, but XLATEs, deletes, selects, READNEXT, record and file copies, remake files, everything.. An MFS is all-encompassing, all-powerful beast. The fact that MFS gets the I/O call first gives you an opportunity to modify the parameters (hence modifying filing system, get it?) or do some intermediary processing. When your MFS has had its shot at the data, it calls the BFS to finish the I/O process. (In some MFSs, such as securityMFSs, the purpose is to prevent the BFS from ever getting the call.) There are some complications, but this is the basic scheme. I'll fill in more details when we look at a real, live MFS. What do you do in your MFS, anyway? As little as possible, basically. For most I/O calls, you just turn right around and pass the call on to the BFS. You really only need to worry about calls that pertain to your MFS's purpose. To give you a simplified example, think about an MFS that encrypts data. You need to monitor calls that write records in order to encrypt the data before passing it on to the BFS to file. Then you need to monitor the read calls in order to decrypt the data before passing it back to Advanced Revelation. Everything else you pass through intact. Other MFSs are more complex (yeah, indexing MFSs, they're fun) but still, the MFS worries only about calls that directly affect it. Writing your first MFS The best way to learn about MFSs is to have a look at an existing one, so I've created a simple MFS that creates a log of all changes to records in a file. When you create your own MFS, I recommend using one of the MFS shells provided by Revelation Technologies. You can find these in the utility volume of ARev 1.x, on the disks provided with the MFS system book (available separately from RevTI), or you can use an existing MFS such as PROGRAM.MFS from the REVSOURCE file. For this article, I'll use the example MFS in Figure 1 to describe how MFSs work. Keep that listing handy as you read on. You will see that the MFS is passed 6 parameters: * CODE is an integer value that represents the I/O action taking place in the MFS. I'll describe this in more detail later. * BFS contains a list of filing systems for the file being accessed. I'll explain later the clever way in which you use this to call this BFS. * HANDLE is usually the file handle of the file being accessed. * NAME is the key of the item being accessed. * FMC has various functions depending on what I/O operation the MFS has been called to do. * RECORD is the record referenced by name and handle. For example, when you write a record, the record parameter contains the actual record being written. * STATUS is set to indicate the success or failure of the operation. (This is part of the way certain disk I/O operations know which way to fall through their THEN/ELSE clauses.) Let me examine the most interesting parameters in a little more detail. The CODE parameter The CODE parameter is a number that identifies what I/O operation has been requested--read, write, delete, create a file, whatever. There are 34 codes in all. One job your MFS has is to look at this code to see what operation is in progress and react accordingly. Now might be a good time to check out the record called FILE.SYSTEM.EQUATES in the SYSINCLUDE file. (That's the INCLUDE file for those who haven't made the great 3.x leap). You will see the listing of system events that your MFS will have access to. For example, here's an extract from that record: EQU READ.RECORD TO 1 EQU READO.RECORD TO 2 EQU WRITE.RECORD TO 3 EQU DELETE.RECORD TO 4 EQU LOCK.RECORD TO 5 EQU UNLOCK.RECORD TO 6 EQU SELECT TO 7 EQU READNEXT TO 8 EQU CLEARSELECT TO 9 EQU CLEARFILE TO 10 EQU OPEN.FILE TO 11 EQU CREATE.FILE TO 12 As you can see, this is a block of EQU statements that provide names for the codes. They're reasonably self-explanatory: READ.RECORD is a read, WRITE.RECORD is a write, READNEXT is the start of an R/BASIC READNEXT operation, and so on. The BFS parameter When Advanced Revelation calls your MFS, the BFS parameter contains a list of the filing systems for the file being accessed. At the front of the list is your MFS, because that's the next filing system in line. At the end of the list is the BFS for this file--usually RTP57, but theoretically any BFS. In between there can be any number of other filings sytems. If there are multiple MFSs on a file, this list will have a number of MFSs plus the BFS. If there's only one MFS on the file, this list will contain only the name of the MFS and the BFS. (Actually, you don't know whether there aren't maybe MFSs that have been called before yours, but for our purposes this doesn't matter.) The point of this parameter is to allow you to call the next filing system. It's an important point: Advanced Revelation calls your MFS, and it's your responsibility to call the next filing system. If you don't call the next filing system, that's it--the I/O operation stops with you. To call the next filing system, you first remove yourself from the list by deleting the first element of the BFS parameter. Then you extract the next element and call it. You'll see how in a moment. Dissecting an MFS Got that listing handy? Most of the top part is standard, but I've added a few equates for message numbers. To use this particular MFS, you need a message AU101 in your message file with some text about not being able to write to the audit file. AU102 should contain a similar message about not opening the audit file. Do something similar for the rest of the messages. After inserting FILE.SYSTEM.EQUATES, the next section of the program is the RevTI standard program header. Below that, there are a few short lines of code that strip off the MFS name call from the BFS variable in order to be ready for the next call, as I explained above. Lastly, there is an $INSERT line that contains a single ON...GOSUB command. This command uses the value in CODE to call a corresponding (local) subroutine in the MFS. MFS internal subroutines Going down the list of subroutines, most of them should be obvious, as noted earlier.The ones that are not quite as obvious are OMNI.SCRIPT, NEXT_FS,and INSTALL. OMNI.SCRIPT is used by what are called "smart" bonds (like the SQL Server Bond), and are called only from special subroutines in Advanced Revelation. You don't need to worry about that call right now. The INSTALL code is called the first time the MFS is loaded in the system. You can use this to set up whatever pointers or files that your MFS might require. INSTALL and OMNI.SCRIPT, along with FLUSH and UNLOCK.ALL, are different from the other MFS calls. Advanced Revelation calls your MFS directly with these codes to alert you to special situations. As I just noted, Advanced Revelation calls your MFS with an INSTALL code so your MFS can initialize itself; this isn't a "real" I/O call. The same applies to FLUSH ("flush all your buffers, please") and UNLOCK.ALL ("release all your locks, please"). OMNI.SCRIPT is another one of those smart bond calls that I told you not to worry about for now. Important: when you process these calls, you should not try to do the usual call to the next filing system. There is nothing in the BFS parameter, so if you do call the next filing system, you get a "Null program loading" error and everything comes to a screeching halt. You should also set STATUS to 1 (true) before you return from these calls so that Advanced Revelation knows you got the call and did your thing, even if that thing was to do nothing. Again, failure to do so gets you errors. The next internal subroutine call, NEXT_FS, is the one that calls the next filing system in the BFS parameter. It illustrates a little known feature of Advanced Revelation, namely indirect subroutine calls. Back at the top of the MFS, the first value of FS is stripped off and placed in the NEXTFS variable. Now, by putting "@" in front of the variable, you can use it as the name of a subroutine to call. For example, if the BFS parameter starts with this value: MY_MFS : @VM : RTP57 the program strips off MY_MFS from the front of BFS and puts RTP57 in FS. Then, NEXTFS would be set to RTP57. When the MFS gets to the NEXT_FS subroutine, calling @NEXTFS calls RTP57 since that is the value of NEXTFS. Creating the MFS logic Well, I was writing a simple file audit MFS, wasn't I? So, where in the MFS do I need to put code to track I/O? Right: WRITE.RECORD and DELETE.RECORD. The MFS needs to create a log entry every time a user changes a record, which means when they write a (changed) record to the file or delete one. Starting with the code sections, you'll see this is rather easy. The MFS builds an audit record that tells you when the I/O operation happened. The first field of the audit record contains the key of the affected record, and the second is the MFS code for the I/O operation requested. The next field contains the user name, and the fourth and fifth fields contain the date and time. The rest of the code is fairly simple. On the read and write calls, the MFS creates the audit record, and writes it out to the audit file using a sequential key stored in the dictionary. The delete is a little more complex. I decided that it would be nice to include the record that was deleted as part of the audit. Since RECORD is null when Advanced Revelation calls the MFS to delete a record, I need to read the record from the disk. The problem here concerns MFS actions. If I do a normal read, I'll cause Advanced Revelation to generate a brand-new call to the filing system, which is me. The MFS will kick in for read this time, and I will generate an additional audit record for the read. To avoid this, I generate a direct call to the BFS. I could have written code that would have extracted the last value in the BFS parameter (which would have been the actual base filing system subroutine), but I decided it would be clearer just to call RTP57 directly. Using your MFS You're almost done. Compile the program and catalog it like any other subroutine. Then you need to install it on a file. Be sure you're in the SYSPROG account, find yourself a nice file to audit, then follow these steps: 1. Attach the volume directory (media map). Type this: ATTACH volumename REVMEDIA where volumeNAME is the directory where the file to be audited is living. 2. Edit the entry in the media map for the file with this command: EDIT REVMEDIA filename*account where filename is the file to be audited, and account is its account. 3. In the second line (field), put in the name of the MFS using the name it's cataloged under. (If there are already other MFSs listed there, add yours to the end of the list with an @VM to delimit it.) Be very careful only to change line 2 and not to add an extra line by mistake! 4. Save the entry. 5. Attach or re-attach the file. 6. Create a file called AUDIT_FILE somewhere to hold the audit log entries. Now make changes to the file you want to audit, then go peek in the audit file to see what you've accomplished. Pretty amazing, isn't it? If you intend to use this MFS for real, be sure to remove the word EXPENDABLE in the first line. But first be sure it's working exactly the way you want! That is really all there is to writing MFSs. Other than remembering what effect your MFS will have upon the system and file access, MFSs are no more complicated than any other R/BASIC program. By the way, by adding the condition block, this MFS will run under OI. @FILE.ERROR is still up in the air at this writing. Figure 1 EXPENDABLE SUBROUTINE BFS(CODE, BFS, HANDLE, NAME, FMC, RECORD, STATUS) /* _ VERSION : 1.0 _ PURPOSE : Performs a rudimentary audit MFS _ AUTHOR : Aaron Kaplan _ CREATED : July 29, 1992 *_ THEORY OF OPERATION : Writes a record to an audit file upon any read, write or delete to a file. If the record is deleted, the record is stored with the audit record. *******************************/ EQU TRUE$ TO 1 EQU FALSE$ TO 0 EQU NULL$ TO "" /* audit file equates */ equ NAME$ to 1 equ CODE$ to 2 equ USER$ to 3 equ DATE$ to 4 equ TIME$ to 5 equ AUDIT_KEY_NAME$ to "%AUDIT_KEY%" /* audit error equates */ equ AUDIT_WRITE_ERR$ to "AU101" equ AUDIT_OPEN_ERR$ to "AU102" equ AUDIT_DICT_OPEN_ERR$ to "AU103" equ AUDIT_DICT_WRITE_ERR$ to "AU104" $INSERT SYSINCLUDE, FILE.SYSTEM.EQUATES $INSERT SYSINCLUDE, FSERRORS_HDR ******************************* *__ PROGRAM TOP FS=DELETE(BFS,1,1,1) NEXTFS=FS @FILE.ERROR=" $INSERT SYSINCLUDE, FILE.SYSTEM.ONGOSUB RETURN *---------------------------------------------------------------------------- /* unused record operations */ READ.RECORD: READO.RECORD: /* Media operations */ CREATE.MEDIA: OPEN.MEDIA: READ.MEDIA: WRITE.MEDIA: CLOSE.MEDIA: /* File-oriented operations */ CLEARFILE: CREATE.FILE: DELETE.FILE: MOVE.FILE: OPEN.FILE: REMAKE.FILE: RENAME.FILE: /* Select operations */ SELECT: READNEXT: CLEARSELECT: /* Lock operations */ LOCK.RECORD: UNLOCK.RECORD: /* Index operations */ CREATE.INDEX: UPDATE.INDEX: DELETE.INDEX: SELECT.INDEX: READNEXT.INDEX: RESERVED: /* Misc calls */ OMNI.SCRIPT: RECORD.COUNT: LOCK.SEMAPHORE: UNLOCK.SEMAPHORE: SET.USER.SEMAPHORE: CALL @NEXTFS(CODE, FS, HANDLE, NAME, FMC, RECORD, STATUS) return *---------------------------------------------------------------------------- /* Record update operations */ WRITE.RECORD: CALL @NEXTFS(CODE, FS, HANDLE, NAME, FMC, RECORD, STATUS) DELETE.RECORD: open 'AUDIT_FILE' to auditHandle then open 'DICT.AUDIT_FILE' to dictAuditHandle then /* get audit key */ read auditKey from dictAuditHandle, AUDIT_KEY_NAME$ else auditKey=0 end auditKey += 1 write auditKey on dictAuditHandle, AUDIT_KEY_NAME$ then /* create audit record */ audit_rec=NULL$ audit_rec=name ;* key of affected record audit_rec=code ;* I/O code passed to MFS audit_rec=@USERNAME audit_rec=date() audit_rec=time() /* if record is deleted, keep track of it */ if code=DELETE.RECORD then temp_rec=' CALL RTP57(READ.RECORD, "RTP57", HANDLE, NAME, FMC, temp_rec, temp_status) /* we could check for temp_status here, but assume that the read was successful */ audit_rec := @RM : temp_rec /* this will delete the record */ CALL @NEXTFS(CODE, FS, HANDLE, NAME, FMC, RECORD, STATUS) end /* write out the audit record */ write audit_rec on auditHandle, auditkey else @FILE.ERROR=AUDIT_WRITE_ERR$ status=FALSE$ end end else @FILE.ERROR=AUDIT_DICT_WRITE_ERR$ status=FALSE$ end end else @FILE.ERROR=AUDIT_DICT_OPEN_ERR$ status=FALSE$ end end else @FILE.ERROR=AUDIT_OPEN_ERR$ status=FALSE$ end return NEXT_FS: CALL @NEXTFS(CODE, FS, HANDLE, NAME, FMC, RECORD, STATUS) RETURN *---------------------------------------------------------------------------- * Install, unlock all and flush are called directly, no need to call next FS. INSTALL: FLUSH: UNLOCK.ALL: STATUS=TRUE$ RETURN [[https://www.revelation.com/revweb/oecgi4p.php/O4W_HANDOFF?DESTN=O4W_RUN_FORM&INQID=NONWORKS_READ&SUMMARY=1&KEY=383811837A50BEAB852567E0007D58B3|View this thread on the forum...]]