Modifying MFSs and BFSs for the 2.0 BFS specification

Published ByDateVersionKnowledge LevelKeywords
Revelation Technologies15 JAN 19902.XEXPERTERROR, 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.

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.

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:

CodeDescription
REMAKE.FILERestructure file
CLOSE.MEDIAWrapup call (called out of DETACH verb)
OMNI.SCRIPTGeneral purpose and back-end server dispatch
CREATE.INDEXCreate an index
UPDATE.INDEXUpdate or rebuild index
DELETE.INDEXRemove an index
SELECT.INDEXTest and initialize index access
READNEXT.INDEXExtract information from an index

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 StatementBFS Call
WRITE
WRITEV
MATWRITE
WRITE.RECORD
DELETEDELETE.RECORD
CLEARFILECLEARFILE
UNLOCKUNLOCK.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:

0Logical 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.
1Physical error. Significant operational errors in the operation of filing system. This condition allows or indicates a retry of the operation.
2Fatal 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.

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

EquateCodeSeverityParameters
FS_REC_DNE$100LogicalRecord Key
FS_READNEXT_DONE$111LogicalNONE

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
  • tips/revmedia/r43.txt
  • Last modified: 2024/06/19 20:20
  • by 127.0.0.1