Modifying MFSs and BFSs for the 2.0 BFS specification
Published By | Date | Version | Knowledge Level | Keywords |
---|---|---|---|---|
Revelation Technologies | 15 JAN 1990 | 2.X | EXPERT | ERROR, HANDLING, @FILE.ERROR, STATUS, STATUS(), RBASIC, R/BASIC, EXTENSIONS, FILING, SYSTEMS |
This is not an exhaustive coverage of the new 2.0 BFS specification. Rather it is a brief and pragmatic coverage of what must be done, minimally, to get 1.1x filing systems (modifying and base) up and running under 2.0.
Three areas of modification will impact existing filing systems: changed BFS calls, new BFS calls, and error handling.
BFS Code Changes
Two BFS codes have been redefined:
FLUSH.CACHE redefined to CLOSE.MEDIA
GROUP.NUMBER redefined to OMNI.SCRIPT
The FLUSH.CACHE and GROUP.NUMBER calls were primarily oriented to Linear Hash and were removed and redefined to make the BFS specification more generic.
Change in Use of STATUS
The 2.0 BFS specification makes greater use of the STATUS (last) argument to pass control data. The practice of setting STATUS to TRUE at the top of the MFS/BFS is no longer safe. Each call or set of similarly handled calls should set STATUS as required so that other calls that are testing STATUS will receive the correct value. Examples of calls which now test STATUS are READNEXT, SELECT.INDEX, and OMNI.SCRIPT.
Wraparound in Nonterminating READNEXT
BFSs and MFSs that return select lists should now return a flag when they wrap around. The wraparound condition should be flagged by returning a STATUS value of 2 rather than the usual 1.
The conform strictly, a filing system should not return FALSE on a nonterminating READNEXT request even if there is nothing to return. Rather, on the first READNEXT return it should return a null list and STATUS value of 1. Thereafter it should return a null list and a STATUS value of 2.
If a filing system has only one group of record keys to return, it should return the list on the first nonterminating READNEXT with a STATUS value of 1. Thereafter, the nonterminating READNEXT should return the list with a STATUS of 2.
Extended BFS Definition
In addition to changes in existing BFS codes, new ones have been added. The minimum action required is to use new copies of FILE.SYSTEM.EQUATES and FILE.SYSTEM.ONGOSUB records to include when compiling your filing systems, and to include stubs for the new BFS calls.
If you have implemented a BFS as opposed to an MFS you will need to implement code for the new SELECT.INDEX BFS call. MFS programmers should simply pass the SELECT.INDEX call through to the next filing system. Figure 1 illustrates how a BFS might handle the SELECT.INDEX call.
The new BFS codes are:
Code | Description |
---|---|
REMAKE.FILE | Restructure file |
CLOSE.MEDIA | Wrapup call (called out of DETACH verb) |
OMNI.SCRIPT | General purpose and back-end server dispatch |
CREATE.INDEX | Create an index |
UPDATE.INDEX | Update or rebuild index |
DELETE.INDEX | Remove an index |
SELECT.INDEX | Test and initialize index access |
READNEXT.INDEX | Extract information from an index |
Enhanced Error Handling
Error handling protocols for filing systems have been significantly enhanced for 2.0. In order to guarantee reliable operation of filing systems, this will require more code modification than the somewhat simpler addition of the new BFS codes.
Error Testing Extensions
Statements and the corresponding BFS calls that in 1.x releases of Advanced Revelation did not presume any possibility of failure now check for failure conditions:
R/BASIC Statement | BFS Call |
---|---|
WRITE WRITEV MATWRITE | WRITE.RECORD |
DELETE | DELETE.RECORD |
CLEARFILE | CLEARFILE |
UNLOCK | UNLOCK.RECORD |
The FLAG or STATUS argument (last argument) must now always be passed back from cascaded calls to lower filing systems. In addition, BFSs must always set the STATUS argument to indicate success or failure of all operations.
If STATUS is set to FALSE, the operation is defined not to have completed. This is particularly important in data-modifying operations: WRITE, DELETE, and CLEARFILE.
Error Code Detail
Whenever an error condition is indicated by the STATUS argument set to FALSE, codes indicating the nature of the error must be set. The variables designated for this purpose are STATUS( ) and the new variable @FILE.ERROR.
Error Severity Level
STATUS( ) must be assigned an integer value indicating the severity level of the error:
0 | Logical error. Natural logical termination conditions for example, record does not exist on READ or DELETE operation, READNEXT has returned the last key in a select list, or a WRITE operation has failed domain validation. |
1 | Physical error. Significant operational errors in the operation of filing system. This condition allows or indicates a retry of the operation. |
2 | Fatal error. Extreme operational or data errors. This may indicate a data loss or corruption condition. The condition indicates that the operation should not be retried before corrective action has been taken. |
Note: In the case of LOCK, STATUS( ) = 1 continues to indicate a lock failure due to self-held lock (for code compatibility).
Error Code and Detail
Whenever a filing system failure is indicated, the variable @FILE.ERROR must be set appropriately. @FILE.ERROR is currently defined to have three fields:
<1> FSCODE$. Error Code.
<2> FSMSG$. Parameters for default error messages.
<3> FSDETAIL$. Code-specific error detail.
@FILE.ERROR<FSCODE$> should be set to an appropriate code value which indicates the nature of the error. System errors, generated from Advanced Revelation filing systems (LH and ROS), MFSs, and RTPs, have been assigned positive integer error codes. Bond BFSs and non-system MFSs should use defined system error codes wherever applicable, and assign new error codes only where necessary. New error codes should begin with from one to three alpha characters that will be distinctive in the environment, followed by up to three numeric characters indicating the error condition (for example, QDX100).
Refer to distributed FSERROR_nnn $INSERT records in the INCLUDE file for error codes before assigning new codes. Most common error conditions already have error codes assigned. Documentation in the $INSERT records indicates the severity level and parameters associated with the error codes.
Each error code should have an associated message in the MESSAGES file. System (integer) error codes will have keys of the structure FSerror#. Non-system error codes (prefixed with characters) should have messages with keys that are the same as the error codes. For example, code BQ302 would have an associated message in the MESSAGES file with the key BQ302. This message may be called by any program that finds this code in @FILE.ERROR<FSCODE$> after a failed I/O call. Parameters for this message should be loaded into the second field (@FILE.ERROR<FSMSG$>).
The contents of field three of @FILE.ERROR code-dependent. Load this only if complex error reporting beyond the scope of parameter passing to MSG is required. This could be handled by a special error handler that tests for a particular error code or codes, and is currently used only for a few system codes.
Using and Setting Error Details
A few error codes are particularly strategic and must be set by all BFSs and MFSs under the specified circumstances. Figure 2 lists these codes.
The error record does not exist is set on READ.RECORD and DELETE.RECORD operations where the record indicated by the file handle and key does not exist. The READNEXT done error is set upon normal termination of a READNEXT operation at the top or bottom of a select list or file (implementation dependent) in terminating readnext mode.
For MFSs calling the next filing system, if failure is indicated by the STATUS argument returning FALSE (0), the typical response is to return immediately. The MFS or BFS that was called is responsible for setting @FILE.ERROR and STATUS( ) correctly to indicate the nature of the error. Similarly, any R/BASIC I/O statements which have THEN or ELSE clauses (now including WRITE and DELETE) will set or clear @FILE.ERROR. If they branch false, @FILE.ERROR and STATUS( ) will be set.
If an error occurs in called filing systems or R/BASIC I/O statements, it is usually the correct thing to leave @FILE.ERROR alone and exit as quickly as possible. Under some conditions, the MFS or BFS may wish to test for certain failure conditions and recast the error or clear the error condition and continue. This may frequently be the case when performing READs and testing for normal failure (for example, the record does not exist) as distinguished from more severe errors.
If errors occur and clean-up operations (such as unlocks) need to be performed before exiting, take care to save and restore the values of @FILE.ERROR and STATUS( ) around any operations that may affect them so that the original error will be preserved.
As a rule of thumb, assume that all file and record I/O operations will set or clear @FILE.ERROR and STATUS( ). Be aware of functions such as ICONV and OCONV which set STATUS( ). @FILE.ERROR is preserved across calls, but functions or subroutines of unknown nature such as MSG may set or clear @FILE.ERROR.
Now that a thorough system of error handling is provided, user interaction through MSG (or a similar routine) is discouraged. Well-behaved filing systems should leave error display and user interaction in the application and system level. This allows for greater control at the application level, and recoverability and non-blocking error recovery.
It is strongly suggested that the example QUICKDEX.MFS (Figure 3) be reviewed as an example of integration of error handling in an MFS.
Examples
Figure 1
$INSERT INCLUDE, FSERRORS_HDR $INSERT INCLUDE, FSERRORS_100 *--------------------------------------------------------------------- * the STATUS argument is passed in as a flag indicating whether the * index access is being tested or actually invoked. * as in the case of the READNEXT call in 1.1x, the STATUS argument * should NOT be set to TRUE at the top because it may be used to pass * in control data. * SELECT.INDEX: IF STATUS THEN * request to actually access the index * this request should not actually be made * but just in case ... GOSUB NOT_IMPLEMENTED END ELSE * indicate index access not supported FMC = 0:@FM:0 STATUS = TRUE$ END RETURN *-------------------------------------------------------------------- NOT_IMPLEMENTED: STATUS = FALSE$ @FILE.ERROR<FSCODE$> = FS_NOT_IMPL$ @FILE.ERROR<FSMSG$> = CODE :@VM: BFS STATUS() = FSPHYSICAL$ RETURN
Figure 2
Equate | Code | Severity | Parameters |
---|---|---|---|
FS_REC_DNE$ | 100 | Logical | Record Key |
FS_READNEXT_DONE$ | 111 | Logical | NONE |
Figure 3
SUBROUTINE QUICKDEX.MFS(CODE, BFS, FILEVAR, ITEM_ID, FMC, ITEM, FLAG) * this program is proprietary and is not to be used by or * disclosed to others, nor is it to be copied * without written permission from Revelation Technologies, Inc. * * VERSION : Advanced Revelation 2.0 * PURPOSE : provide automatic sorting of key field at SELECT time. * AUTHOR : Revelation Technologies * WARNINGS: use of this MFS is limited to files whose keys will fit * into a single (64K) record. * $INSERT Blocks : $INSERT INCLUDE, FILE.SYSTEM.EQUATES $INSERT INCLUDE, SELECT.CONSTANTS $INSERT INCLUDE, FSERRORS_HDR $INSERT INCLUDE, FSERRORS_100 $INSERT INCLUDE, FSERRORS_200 $INSERT INCLUDE, FSERRORS_400 * EQUATE Variables (Terminate with '$') : EQU COSMO$ TO 'Copyright (C) 1989, Revelation Technologies, Inc.' EQU TRUE$ TO 1 EQU FALSE$ TO 0 EQU YES$ TO 1 EQU NO$ TO 0 EQU OTHERWISE$ TO 1 EQUATE REC_ID$ TO '%RECORDS%' EQUATE MAX_STRING_LENGTH$ TO 65530 * * Omnibus/Script code equates EQUATE OMNIBUS_LIST$ TO 'GROUP_NUMBER' EQUATE OMNIBUS_CALL$ TO 0 EQUATE SCRIPT_TEST$ TO 1 EQUATE SCRIPT_CALL$ TO 2 EQUATE SCRIPT_OK_CALL$ TO 3 *===================================================================== FS = DELETE(BFS, 1, 1, 1) NEXTFS = FS<1,1,1> @FILE.ERROR = '' * $INSERT INCLUDE,FILE.SYSTEM.ONGOSUB RETURN *===================================================================== * new calls for 2.0 BFSs REMAKE.FILE: SELECT.INDEX: CREATE.INDEX: DELETE.INDEX: UPDATE.INDEX: READNEXT.INDEX: *---------------------- READ.RECORD: READO.RECORD: *---------------------- LOCK.RECORD: UNLOCK.RECORD: *---------------------- CREATE.FILE: OPEN.FILE: CLEARFILE: RENAME.FILE: DELETE.FILE: MOVE.FILE: *---------------------- LOCK.SEMAPHORE: UNLOCK.SEMAPHORE: SET.USER.SEMAPHORE: *---------------------- OPEN.MEDIA: * close media new for 2.0 CLOSE.MEDIA: CREATE.MEDIA: READ.MEDIA: WRITE.MEDIA: CALL @NEXTFS(CODE, FS, FILEVAR, ITEM_ID, FMC, ITEM, FLAG) RETURN *--------------------------------------------------------------------- RECORD.COUNT: CALL @NEXTFS(CODE,FS, FILEVAR, ITEM_ID, FMC, ITEM, FLAG) IF FLAG THEN * don't count %RECORDS% FMC -= 1 END RETURN *--------------------------------------------------------------------- * the system makes these calls direct to each filing system * cascading is unnecessary UNLOCK.ALL: INSTALL: FLAG = TRUE$ RETURN *--------------------------------------------------------------------- * this call is typically made to each filing system directly. * NEXTFS is checked on the chance that a list to call is passed. FLUSH: FLAG = TRUE$ IF LEN(NEXTFS) THEN CALL @NEXTFS(CODE, FS, FILEVAR, ITEM_ID, FMC, ITEM, FLAG) END RETURN *-------------------------------------------------------------------- * omnibus/script call (new for 2.0) * the Omnibus call is intended to handle special case administrative * calls that an MFS or BFS requires and cannot be reasonably mapped into * the existing call structures. This should be used only as a last resort * and should not be used to implement new I/O calls, inasmuch as this * would defeat portability. * OMNI.SCRIPT: FLAG = TRUE$ IF FMC = OMNIBUS_CALL$ THEN LOCATE ITEM_ID IN OMNIBUS_LIST$ USING ',' SETTING POS THEN ON POS GOSUB GROUP_NUMBER RETURN END END * * continue on if omnibus call not handled * or if script call. (back-end server dispatch) * CALL @NEXTFS( CODE, FS, FILEVAR, ITEM_ID, FMC, ITEM, FLAG) RETURN *--------------------------------------- * group number is no longer a directly supported BFS call. * (it is specific to Linear Hash) * normally this would not have to be handled as a special case * because execution falls through to the next filing system if * the current MFS does not support the call in question. * this is here only as example of an omnibus dispatch call. * GROUP_NUMBER: CALL @NEXTFS( CODE, FS, FILEVAR, ITEM_ID, FMC, ITEM, FLAG) RETURN *--------------------------------------------------------------------- WRITE.RECORD: DELETE_FLAG = FALSE$ * COMMON.CODE: IF ITEM_ID = REC_ID$ THEN * direct modification of %RECORDS% not allowed FLAG = FALSE$ @FILE.ERROR<FSCODE> = FS_FIELD_PROTECT_ERR$ @FILE.ERROR<FSMSG$> = : @VM : ITEM_ID STATUS() = FSPHYSICAL$ RETURN END * CALL @NEXTFS(CODE, FS, FILEVAR, ITEM_ID, FMC, ITEM, FLAG) IF FLAG ELSE RETURN * * write or delete successful - continue on to update %RECORDS% LOCKED = FALSE$ LOOP LOCK FS:@VM:FILEVAR, REC_ID$ THEN LOCKED = TRUE$ UNTIL LOCKED LINEMARK REPEAT * CALL @NEXTFS(READ.RECORD, FS, FILEVAR, REC_ID$, '', REC, FLAG) IF FLAG ELSE * read of %RECORDS% failed IF @FILE.ERROR<FSCODE$> = FS_REC_DNE$ THEN * the record does not exist (DNE) - continue FLAG = TRUE$ REC = '' END ELSE * serious problem encountered - terminate GOSUB UNLOCK_RECORDS RETURN END END * IF DELETE_FLAG THEN * called from Delete.Record LOCATE ITEM_ID IN REC USING @FM SETTING POS THEN REC = DELETE(REC, POS, 0, 0) FLAG = TRUE$ CALL @NEXTFS(WRITE.RECORD,FS,FILEVAR,REC_ID$, '',REC,FLAG) END ELSE * case where record deleted is not in %RECORDS% @FILE.ERROR<FSCODE$> = FS_MISSING_FIELD$ @FILE.ERROR<FSDETAIL$> = : @VM : ITEM_ID STATUS() = FSLOGICAL$ END END ELSE LOCATE ITEM_ID IN REC BY 'AL' USING @FM SETTING POS ELSE TOTAL_LENGTH = LEN(REC) LEN(ITEM_ID) IF TOTAL_LENGTH GT MAX_STRING_LENGTH$ THEN @FILE.ERROR<FSCODE$> = FS_CONTROL_REC_TOO_LONG$ @FILE.ERROR<FSDETAIL$> = TOTAL_LENGTH STATUS() = FSPHYSICAL$ END ELSE REC = INSERT(REC,POS, 0, 0, ITEM_ID) FLAG = TRUE$ CALL @NEXTFS(WRITE.RECORD,FS,FILEVAR,REC_ID$,'',REC, FLAG) END END END * GOSUB UNLOCK_RECORDS RETURN *-------------------------------------------------------------------- DELETE.RECORD: DELETE_FLAG = TRUE$ GOSUB COMMON.CODE RETURN *-------------------------------------------------------------------- SELECT: CALL @NEXTFS(CODE, FS, FILEVAR, ITEMID, FMC, ITEM, FLAG) IF ITEM = LATENT.FILE.SELECT$ THEN * if the select is not trapped by si.mfs then quickdex will * handle it - clear the filing system select CALL @NEXTFS(CLEARSELECT, FS, FILEVAR, ITEMID, FMC, ITEM,FLAG) ITEM = LATENT.FILE.SELECT$ END FMC = TRUE$ RETURN *-------------------------------------------------------------------- CLEARSELECT: RETURN *-------------------------------------------------------------------- READNEXT: IF ITEM_ID = LATENT.INDEX.SELECT$ THEN * selects being handled by si.mfs should pass through CALL @NEXTFS(CODE, FS, FILEVAR, ITEMID, FMC, ITEM, FLAG) RETURN END * IF FMC ELSE * READNEXT complete FLAG = FALSE$ @FILE.ERROR<FSCODE$> = FS_READNEXT_DONE$ STATUS() = FSLOGICAL$ RETURN END * * lock %RECORDS% LOCKED = FALSE$ LOOP LOCK FS:@VM:FILEVAR, REC_ID$ THEN LOCKED = TRUE$ END UNTIL LOCKED LINEMARK REPEAT * CALL @NEXTFS(READ.RECORD, FS, FILEVAR, REC_ID$, '', ITEM, FLAG) IF FLAG ELSE * read of %RECORDS% failed IF @FILE.ERROR<FSCODE$> # FS_REC_DNE$ THEN * serious problem - terminate GOSUB UNLOCK_RECORDS RETURN END * %RECORDS% doesn't exist, generate it FLAG = TRUE$ GOSUB GENERATE_RECORDS END * FMC = FALSE$ ;* Readnext exhausted GOSUB UNLOCK_RECORDS RETURN *-------------------------------------------------------------------- * the %RECORDS% record does not exist - generate it GENERATE_RECORDS: ITEM = '' CALL @NEXTFS(SELECT, FS, FILEVAR, '', TEMP, '', FLAG) IF FLAG ELSE RETURN LOOP FLAG = ASND.TERM$ CALL @NEXTFS(READNEXT, FS, FILEVAR, '', TEMP, TEMPLIST, FLAG) WHILE FLAG LOOP WHILE LEN(TEMPLIST) NEXTID = TEMPLIST[1, @FM] TEMPLIST[1, COL2()] = '' LOCATE NEXTID IN ITEM BY 'AL' USING @FM SETTING POS ELSE ITEM = INSERT(ITEM, POS, 0, 0, NEXTID) END REPEAT REPEAT * IF @FILE.ERROR<FSCODE$> = FS_READNEXT_DONE$ THEN * normal Readnext termination - continue FLAG = TRUE$ CALL @NEXTFS(WRITE.RECORD, FS, FILEVAR, REC_ID$, '',ITEM,FLAG) IF FLAG THEN * if write was successful then call clearselect CALL @NEXTFS(CLEARSELECT, FS, FILEVAR, '', TEMP, '', FLAG) END END RETURN *-------------------------------------------------------------------- * @FILE.ERROR AND STATUS() are saved and restored arount unlock * if an error condition occured, so that the original error codes * are retained. The UNLOCK statement will reset these vars otherwise UNLOCK_RECORDS: IF FLAG ELSE TEMP_STAT = STATUS() TEMP_FSERROR = @FILE.ERROR END * UNLOCK FS:@VM:FILEVAR, REC_ID$ * IF FLAG ELSE STATUS() = TEMP_STAT @FILE.ERROR = TEMP_FSERROR END RETURN