A Single Generic File IO Routine:
Before starting a large project consider using one routine to handle all file IO. There are many good reasons for doing this, and no reasonable reason that I know of not to. Some reasons for doing this are:- Code maintainability
- Future additions or modifications
- Fewer bugs
Note: All programming displayed on this site is for illustrative purposes only. Use at your own risk.
subroutine file.io.sub(mode, file.handle, record.id, record.data, result)
*
* mode : input, Choices are R,RL,W, and WL
* file.handle : input, Previously opened file variable
* record.id : input, ID (key) to read or write
* record.data : input/output, Record to return from read, or record to write
* result : output, Text result of the read or write
*
begin case
case mode = “R” ;* Read from a file (no locking)...
read record.data from file.handle, record.id on error result = “Unknown read error!”
then result = “Existing record”
else result = “New record”
case mode = “RL” ;* Read from a file and lock the record...
readu record.data from file.handle, record.id on error result = “Unknown read error!”
locked result = "Record locked by ":STATUS()
then result = “Existing record”
else result = “New record”
case mode = “W” ;* Write to a file and release the lock...
write record.data to file.handle, record.id on error result = “Unknown write error!”
then result = “Wrote record”
else result = “Record not written”
case mode = “WL” ;* Write to a file and maintain the lock...
writeu record.data to file.handle, record.id on error result = “Unknown write error!”
then result = “Wrote record”
else result = “Record not written”
case 1
result=”Unknown mode!”
end case
return
end
A program would then read and write data simply by calling this subroutine. For example:
program customer.orders
open '','CUSTOMERS' to F.CUSTOMERS then
loop.cnt = 0
loop
loop.cnt += 1
print loop.cnt:') Enter customer # ':;input cust.no
until cust.no = '' or cust.no = 'Q'
*
* Read the record from the file...
*
cust.rec = ''
call FILE.IO.SUB("RL", F.CUSTOMERS, cust.no, cust.rec, results)
if results <> 0 and results <> 1 then
print "results=":results
end else
if results = 0 then
print "Existing customer# ":cust.no
end else if results = 1 then
print "New customer# ":cust.no
end
loop
print "1. Name: ":cust.rec<1>
print "2. Addr: ":cust.rec<2>
print "3. City: ":cust.rec<3>
print "4. ZIP : ":cust.rec<4>
print "Enter 1-4, (S)ave or (Q)uit ":;input num.to.modify
if num.to.modify > 0 and num.to.modify < 5 then
print "New data ":;input new.data
cust.rec<num.to.modify> = new.data
end
until num.to.modify = "Q" or num.to.modify = "S"
repeat
if num.to.modify = "S" then
*
* Write the record to the file...
*
call FILE.IO.SUB("W", F.CUSTOMERS, cust.no, cust.rec, results)
if results <> 0 then print results
end
end
repeat
end else stop "Unable to open CUSTOMERS!"
end
There is a lot that this example leaves out (record locking, error handling/recovery/roll-back, security, auditing, etc.). The goal is to highlight the benefits of using a generic subroutine for file IO.
For the sake of argument, let's say that, six months later, after creating many different programs (tens? Hundreds?) with multiple calls each to the generic file IO subroutine, there is a new requirement. Instead of modifying, compiling, testing, delivering, and cataloging every program that contains file reads and writes, just this one program needs to be modified. If the new requirement for example is additional security and basic auditing, then the following may be sufficient:
subroutine file.io.sub(mode, file.handle, record.id, record.data, errcode)
*
* mode : input, Choices are R,RL,W, and WL.
* file.handle : input, Previously opened file variable.
* record.id : input, ID (key) to read or write.
* record.data : input/output, Record to return from read, or record to write.
* errcode : output, Result of operation.
* Zero is usually normal (then clause).
* Anything else could be an error.
* The calling program must know what the errcode value means
* and act accordingly.
*
* Though tempting, do not recursively call this routine to read the security or audit file.
* The result will be an infinite loop.
*
equ true to 1
equ false to 0
COM /FILECOM/ INIT.COM,F.SECURITY,F.AUDIT
$include UVHOME.INCLUDE FILEINFO.INS.IBAS
deffun FILEINFO(aa,bb)
errcode = 0
if not(init.com) then
open '','SECURITY' to F.SECURITY then
open '','AUDIT' to F.AUDIT then
init.com = true ;* One init.com assignment for both opens...
end else errcode = 6 ;* Unable to open AUDIT
end else errcode = 7 ;* Unable to open SECURITY
end
read.access = false
write.access = false
if init.com and not(errcode) then
read security.rec from F.SECURITY, @LOGNAME
on error print "Unknown error reading SECURITY!"
then ;* Existing record
begin case
case security.rec<1> = "ALL" or security.rec<1> = "RW"
read.access = true
write.access = true
case security.rec<1> = "READ" or security.rec<1> = "R"
read.access = true
end case
end else print "No security for user ":@LOGNAME:"."
end
begin case
case not(init.com) ;* Do nothing
case mode= "R" and read.access ;* Read from a file (no locking)...
read record.data from file.handle, record.id
on error errcode = 9 ;* Unknown read error!
then errcode = 0 ;* Existing record
else errcode = 1 ;* New record
case mode= "RL" and read.access ;* Read from a file and lock the record...
readu record.data from file.handle, record.id
on error errcode = 9 ;* Unknown read error!
locked errcode = 2 ;* Locked record
then errcode = 0 ;* Existing record
else errcode = 1 ;* New record
case mode= "W" and write.access ;* Write to a file and release the lock...
write record.data to file.handle, record.id
on error errcode = 9 ;* Unknown write error!
then errcode = 0 ;* Wrote record
else errcode = 1 ;* Record not written
case mode= "WL" and write.access;* Write to a file and maintain the lock...
writeu record.data to file.handle, record.id
on error errcode = 9 ;* Unknown write error!
then errcode = 0 ;* Wrote record
else errcode = 1 ;* Record not written
case mode[1,1] = "R" and not(read.access)
errcode = 3 ;* No read access
case mode[1,1] = "W" and not(write.access)
errcode = 4 ;* No write access
case 1
errcode = 8 ;* Unknown mode or security error!
end case
if init.com and not(errcode) then
*
* Only audit reads of existing records, and writes...
*
audit.date = date()
audit.time = time()
audit.rec = audit.date:@FM:audit.time:@FM:@USERNO:@FM:@LOGNAME
audit.rec<-1> = fileinfo(file.handle,FINFO$VOCNAME)
audit.rec<-1> = record.id
audit.rec<-1> = mode
audit.key = audit.date:audit.time
loop.cnt = 0
done.writing.audit = false
loop
readu dummy from F.AUDIT, audit.key
on error print "Unknown error reading AUDIT!"
then loop.cnt += 1 ;* Existing record. Try again...
else ;* New record
write audit.rec to F.AUDIT, audit.key
on error print "Unknown error writing to AUDIT file!"
else print "Audit record not created!"
done.writing.audit = true
end
until done.writing.audit or loop.cnt > 100
loop.cnt += 1
audit.key = audit.date:audit.time:loop.cnt
repeat
end
return
end
A lot was added to the initial example subroutine.
- A named common with file variables for the security and audit files was added (along with an initialization flag to only open the files once).
- The read and write results were changed from text strings to error codes. The codes values do not have an inherent meaning, only that they be unique for each possible error (or non-error) condition. Calling programs should test for the code value and behave accordingly.
- The additional audit code also makes use of the FILEINFO function to output the data file being written to. This requires a file pointer in the VOC to the INCLUDE file under the UniVerse home account, to include the list of function argument values (though, hard coding the numeric equivalents will also work).
- Though crude, the auditing uses an arbitrarily serialized key comprised of the date and time in internal format (potentially iterating up to 100 times to not step on any existing audit record that happened to be written at the exact same time).
There is much, much more that needs to be done to make this one routine truly capable of handling all types of file IO (MATREADs (and MATWRITEs), READVs, sequential IO, etc.) but the idea I hope to instill is that by using a single subroutine (or function for that matter) to perform the file input and output, adding and changing features in the future become immensely easier, and reduces the opportuniy to introduce errors in the coding.
No comments:
Post a Comment