![Content starts here](http://welcome.hp-ww.com/img/s.gif) |
OpenVMS Programming Concepts Manual
28.3.2 Shared Access
The Fortran specifier READONLY and the SHARED specifier allow multiple
processes to open the same file simultaneously, provided that each
process uses one of these specifiers when opening the file. The
READONLY specifier allows the process read access to the file; the
SHARED specifier allows other processes read and write access to the
file. If a process opens the file without specifying READONLY or
SHARED, no other process can open that file even by specifying READONLY
or SHARED.
In the following Fortran segment, if the read operation indicates that
the record is locked, the read operation is repeated. You should not
attempt to read a locked record without providing a delay (in this
example, the call to ERRSNS) to allow the other process time to
complete its operation and unlock the record.
! Status variables and values
INTEGER STATUS,
2 IOSTAT,
2 IO_OK
PARAMETER (IO_OK = 0)
INCLUDE '($FORDEF)'
! Logical unit number
INTEGER LUN /1/
! Record variables
INTEGER LEN
CHARACTER*80 RECORD
.
.
.
READ (UNIT = LUN,
2 FMT = '(Q,A)'
2 IOSTAT = IOSTAT) LEN, RECORD (1:LEN)
IF (IOSTAT .NE. IO_OK) THEN
CALL ERRSNS (,,,,STATUS)
IF (STATUS .EQ. FOR$_SPERECLOC) THEN
DO WHILE (STATUS .EQ. FOR$_SPERECLOC)
READ (UNIT = LUN,
2 FMT = '(Q,A)'
2 IOSTAT = IOSTAT) LEN, RECORD(1:LEN)
IF (IOSTAT .NE. IO_OK) THEN
CALL ERRSNS (,,,,STATUS)
IF (STATUS .NE. FOR$_SPERECLOC) THEN
CALL LIB$SIGNAL(%VAL(STATUS))
END IF
END IF
END DO
ELSE
CALL LIB$SIGNAL (%VAL(STATUS))
END IF
END IF
.
.
.
|
In Fortran, each time you access a record in a shared file, that record
is automatically locked either until you perform another I/O operation
on the same logical unit, or until you explicitly unlock the record
using the UNLOCK statement. If you plan to modify a record, you should
do so before unlocking it; otherwise, you should unlock the record as
soon as possible.
28.4 File Access and Mapping
To copy an entire data file from the disk to program variables and back
again, either use language I/O statements to read and write the data or
use the Create and Map Section (SYS$CRMPSC) system service to map the
data. Often times, mapping the file is faster than reading it. However,
a mapped file usually uses more virtual memory than one that is read
using language I/O statements. Using I/O statements, you have to store
only the data that you have entered. Using SYS$CRMPSC, you have to
initialize the database and store the entire structure in virtual
memory including the parts that do not yet contain data.
28.4.1 Using SYS$CRMPSC
Mapping a file means associating each byte of the file with a byte of
program storage. You access data in a mapped file by referencing the
program storage; your program does not use I/O statements.
Note
Files created using OpenVMS RMS typically contain control information.
Unless you are familiar with the structure of these files, do not
attempt to map one. The best practice is to map only those files that
have been created as the result of mapping.
|
To map a file, perform the following operations:
- Place the program variables for the data in a common block. Page
align the common block at link time by specifying an options file
containing the following link option for VAX and Alpha systems:
For VAX systems, specify the following:
For Alpha systems, specify the following:
PSECT_ATTR = name, solitary
|
The variable name is the name of the common block.
Within the common block, you should specify the data in order from
most complex to least complex (high to low rank), with character data
last. This naturally aligns the data, thus preventing troublesome page
breaks in virtual memory.
- Open the data file using a user-open routine. The user-open routine
must open the file for user I/O (as opposed to OpenVMS RMS I/O) and
return the channel number on which the file is opened.
- Map the data file to the common block.
- Process the records using the program variables in the common block.
- Free the memory used by the common block, forcing modified data to
be written back to the disk file.
Do not initialize variables in a common block that you plan to map; the
initial values will be lost when SYS$CRMPSC maps the common block.
28.4.1.1 Mapping a File
The format for SYS$CRMPSC is as follows:
SYS$CRMPSC [inadr],[retadr],[acmode],[flags],[gsdnam],[ident],[relpag],
[chan], [pagcnt],[vbn],[prot],[pfc]
|
For a complete description of the SYS$CRMPSC system service, see the
OpenVMS System Services Reference Manual.
Starting and Ending Addresses of the Mapped Section
On VAX systems, specify the location of the first variable in the
common block as the value of the first array element of the array
passed by the inadr argument. Specify the location of
the last variable in the common block as the value of the second array
element.
On Alpha systems, specify the location of the first variable in the
common block as the value of the first array element of the array
passed by the inadr argument; the second array element
must be the address of the last variable in the common block, which is
derived by performing a logical OR with the value of the size of a
memory page minus 1. The size of the memory page can be retrieved by a
call to the SYS$GETSYI system service.
If the first variable in the common block is an array or string, the
first variable in the common block is the first element of that array
or string. If the last variable in the common block is an array or
string, the last variable in the common block is the last element in
that array or string.
Returning the Location of the Mapped Section
On VAX systems, SYS$CRMPSC returns the location of the first and last
elements mapped in the retadr argument. The value
returned as the starting virtual address should be the same as the
starting address passed to the inadr argument. The
value returned as the ending virtual address should be equal to or
slightly more than (within 512 bytes, or 1 block) the value of the
ending virtual address passed to the inadr argument.
On Alpha systems, SYS$CRMPSC returns the location of the first and last
elements mapped in the retadr argument. The value
returned as the starting virtual address should be the same as the
starting address passed to the inadr argument. The
value returned as the ending virtual address should be equal to or
slightly less than (within a single page size) the value of the ending
virtual address passed to the inadr argument.
If the first element is in error, you probably forgot to page-align the
common block containing the mapped data.
If the second element is in error, you were probably creating a new
data file and forgot to specify the size of the file in your program
(see Section 28.4.1.3).
Using Private Sections
Specify SEC$M_WRT for the flags to indicate that the
section is writable. If the file is new, also specify SEC$M_DZRO to
indicate that the section should be initialized to zero.
Obtaining the Channel Number
You must use a user-open routine to get the channel number (see
Section 28.4.1.2). Pass the channel number to the chan
argument.
On VAX systems, Example 28-1 maps a data file consisting of one
longword and three real arrays to the INC_DATA common block. The
options file INCOME.OPT page-aligns the INC_DATA common block.
If SYS$CRMPSC returns a status of SS$_IVSECFLG and you have correctly
specified the flags in the mask argument, check to see if you are
passing a channel number of 0.
Example 28-1 Mapping a Data File to the
Common Block on a VAX System |
!INCOME.OPT
PSECT_ATTR = INC_DATA, PAGE
|
INCOME.FOR
! Declare variables to hold statistics
REAL PERSONS_HOUSE (2048),
2 ADULTS_HOUSE (2048),
2 INCOME_HOUSE (2048)
INTEGER TOTAL_HOUSES
! Declare section information
! Data area
COMMON /INC_DATA/ PERSONS_HOUSE,
2 ADULTS_HOUSE,
2 INCOME_HOUSE,
2 TOTAL_HOUSES
! Addresses
INTEGER ADDR(2),
2 RET_ADDR(2)
! Section length
INTEGER SEC_LEN
! Channel
INTEGER*2 CHAN,
2 GARBAGE
COMMON /CHANNEL/ CHAN,
2 GARBAGE
! Mask values
INTEGER MASK
INCLUDE '($SECDEF)'
! User-open routines
INTEGER UFO_OPEN,
2 UFO_CREATE
EXTERNAL UFO_OPEN,
2 UFO_CREATE
! Declare logical unit number
INTEGER STATS_LUN
! Declare status variables and values
INTEGER STATUS,
2 IOSTAT,
2 IO_OK
PARAMETER (IO_OK = 0)
INCLUDE '($FORDEF)'
EXTERNAL INCOME_BADMAP
! Declare logical for INQUIRE statement
LOGICAL EXIST
! Declare subprograms invoked as functions
INTEGER LIB$GET_LUN,
2 SYS$CRMPSC,
2 SYS$DELTVA,
2 SYS$DASSGN
! Get logical unit number for STATS.SAV
STATUS = LIB$GET_LUN (STATS_LUN)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS))
INQUIRE (FILE = 'STATS.SAV',
2 EXIST = EXIST)
IF (EXIST) THEN
OPEN (UNIT=STATS_LUN,
2 FILE='STATS.SAV',
2 STATUS='OLD',
2 USEROPEN = UFO_OPEN)
MASK = SEC$M_WRT
ELSE
! If STATS.SAV does not exist, create new database
MASK = SEC$M_WRT .OR. SEC$M_DZRO
SEC_LEN =
! (address of last - address of first + size of last + 511)/512
2 ( (%LOC(TOTAL_HOUSES) - %LOC(PERSONS_HOUSE(1)) + 4 + 511)/512 )
OPEN (UNIT=STATS_LUN,
2 FILE='STATS.SAV',
2 STATUS='NEW',
2 INITIALSIZE = SEC_LEN,
2 USEROPEN = UFO_CREATE)
END IF
! Free logical unit number and map section
CLOSE (STATS_LUN)
! ********
! MAP DATA
! ********
! Specify first and last address of section
ADDR(1) = %LOC(PERSONS_HOUSE(1))
ADDR(2) = %LOC(TOTAL_HOUSES)
! Map the section
STATUS = SYS$CRMPSC (ADDR,
2 RET_ADDR,
2 ,
2 %VAL(MASK),
2 ,,,
2 %VAL(CHAN),
2 ,,,)
IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS))
! Check for correct mapping
IF (ADDR(1) .NE. RET_ADDR (1))
2 CALL LIB$SIGNAL (%VAL (%LOC(INCOME_BADMAP)))
.
.
.
! Reference data using the
! data structures listed
! in the common block
.
.
.
! Close and update STATS.SAV
STATUS = SYS$DELTVA (RET_ADDR,,)
IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS))
STATUS = SYS$DASSGN (%VAL(CHAN))
IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS))
END
|
Example 28-2 shows the code for performing the same functions as
Example 28-1 but in an Alpha system's environment.
Example 28-2 Mapping a Data File to the
Common Block on an Alpha System |
!INCOME.OPT
PSECT_ATTR = INC_DATA, SOLITARY, SHR, WRT
|
INCOME.FOR
! Declare variables to hold statistics
REAL PERSONS_HOUSE (2048),
2 ADULTS_HOUSE (2048),
2 INCOME_HOUSE (2048)
INTEGER TOTAL_HOUSES, STATUS
! Declare section information
! Data area
COMMON /INC_DATA/ PERSONS_HOUSE,
2 ADULTS_HOUSE,
2 INCOME_HOUSE,
2 TOTAL_HOUSES
! Addresses
INTEGER ADDR(2),
2 RET_ADDR(2)
! Section length
INTEGER SEC_LEN
! Channel
INTEGER*2 CHAN,
2 GARBAGE
COMMON /CHANNEL/ CHAN,
2 GARBAGE
! Mask values
INTEGER MASK
INCLUDE '($SECDEF)'
! User-open routines
INTEGER UFO_OPEN,
2 UFO_CREATE
EXTERNAL UFO_OPEN,
2 UFO_CREATE
! Declare logical unit number
INTEGER STATS_LUN
! Declare status variables and values
INTEGER STATUS,
2 IOSTAT,
2 IO_OK
PARAMETER (IO_OK = 0)
INCLUDE '($FORDEF)'
EXTERNAL INCOME_BADMAP
! Declare logical for INQUIRE statement
LOGICAL EXIST
! Declare subprograms invoked as functions
INTEGER LIB$GET_LUN,
2 SYS$CRMPSC,
2 SYS$DELTVA,
2 SYS$DASSGN
! Get logical unit number for STATS.SAV
STATUS = LIB$GET_LUN (STATS_LUN)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS))
INQUIRE (FILE = 'STATS.SAV',
2 EXIST = EXIST)
IF (EXIST) THEN
OPEN (UNIT=STATS_LUN,
2 FILE='STATS.SAV',
2 STATUS='OLD',
2 USEROPEN = UFO_OPEN)
MASK = SEC$M_WRT
ELSE
! If STATS.SAV does not exist, create new database
MASK = SEC$M_WRT .OR. SEC$M_DZRO
SEC_LEN =
! (address of last - address of first + size of last + 511)/512
2 ( (%LOC(TOTAL_HOUSES) - %LOC(PERSONS_HOUSE(1)) + 4 + 511)/512 )
OPEN (UNIT=STATS_LUN,
2 FILE='STATS.SAV',
2 STATUS='NEW',
2 INITIALSIZE = SEC_LEN,
2 USEROPEN = UFO_CREATE)
END IF
! Free logical unit number and map section
CLOSE (STATS_LUN)
! ********
! MAP DATA
! ********
STATUS = LIB$GETSYI(SYI$_PAGE_SIZE, PAGE_MAX,,,,)
IF (.NOT. STATUS) CALL LIB$STOP (%VAL (STATUS))
! Specify first and last address of section
ADDR(1) = %LOC(PERSONS_HOUSE(1))
! Section will always be smaller than page_max bytes
ADDR(2) = ADDR(1) + PAGE_MAX -1
! Map the section
STATUS = SYS$CRMPSC (ADDR,
2 RET_ADDR,
2 ,
2 %VAL(MASK),
2 ,,,
2 %VAL(CHAN),
2 ,,,)
IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS))
! Check for correct mapping
IF (ADDR(1) .NE. RET_ADDR (1))
2 CALL LIB$SIGNAL (%VAL (%LOC(INCOME_BADMAP)))
.
.
.
! Reference data using the
! data structures listed
! in the common block
.
.
.
! Close and update STATS.SAV
STATUS = SYS$DELTVA (RET_ADDR,,)
IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS))
STATUS = SYS$DASSGN (%VAL(CHAN))
IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS))
END
|
28.4.1.2 Using the User-Open Routine
When you open a file for mapping in Fortran, for example, you must
specify a user-open routine ( Section 28.6 discusses user-open
routines) to perform the following operations:
- Set the user-file open bit (FAB$V_UFO) in the file access block
(FAB) options mask.
- Open the file using SYS$OPEN for an existing file or SYS$CREATE for
a new file. (Do not invoke SYS$CONNECT if you have set the user-file
open bit.)
- Return the channel number to the program unit that started the OPEN
operation. The channel number is in the additional status longword of
the FAB (FAB$L_STV) and must be returned in a common block.
- Return the status of the open operation (SYS$OPEN or SYS$CREATE) as
the value of the user-open routine.
After setting the user-file open bit in the FAB options mask, you
cannot use language I/O statements to access data in that file.
Therefore, you should free the logical unit number associated with the
file. The file is still open. You access the file with the channel
number.
Example 28-3 shows a user-open routine invoked by the sample program
in Section 28.4.1.1 if the file STATS.SAV exists. (If STATS.SAV does not
exist, the user-open routine must invoke SYS$CREATE rather than
SYS$OPEN.)
Example 28-3 Using a User-Open Routine |
!UFO_OPEN.FOR
INTEGER FUNCTION UFO_OPEN (FAB,
2 RAB,
2 LUN)
! Include Open VMS RMS definitions
INCLUDE '($FABDEF)'
INCLUDE '($RABDEF)'
! Declare dummy arguments
RECORD /FABDEF/ FAB
RECORD /RABDEF/ RAB
INTEGER LUN
! Declare channel
INTEGER*4 CHAN
COMMON /CHANNEL/ CHAN
! Declare status variable
INTEGER STATUS
! Declare system procedures
INTEGER SYS$OPEN
! Set useropen bit in the FAB options longword
FAB.FAB$L_FOP = FAB.FAB$L_FOP .OR. FAB$M_UFO
! Open file
STATUS = SYS$OPEN (FAB)
! Read channel from FAB status word
CHAN = FAB.FAB$L_STV
! Return status of open operation
UFO_OPEN = STATUS
END
|
28.4.1.3 Initializing a Mapped Database
The first time you map a file you must perform the following operations
in addition to those listed at the beginning of Section 28.4.1:
- Specify the size of the file---SYS$CRMPSC maps data based on the
size of the file. Therefore, when creating a file that is to be mapped,
you must specify in your program a file large enough to contain all of
the expected data. Figure the size of your database as follows:
- Find the size of the common block (in bytes)---Subtract the
location of the first variable in the common block from the location of
the last variable in the common block and then add the size of the last
element.
- Find the number of blocks in the common block---Add 511 to the size
and divide the result by 512 (512 bytes = 1 block).
- Initialize the file when you map it---The blocks allocated to a
file might not be initialized and therefore contain random data. When
you first map the file, you should initialize the mapped area to zeros
by setting the SEC$V_DZRO bit in the mask argument of SYS$CRMPSC.
The user-open routine for creating a file is the same as the user-open
routine for opening a file except that SYS$OPEN is replaced by
SYS$CREATE.
28.4.1.4 Saving a Mapped File
To close a data file that was opened for user I/O, you must deassign
the I/O channel assigned to that file. Before you can deassign a
channel assigned to a mapped file, you must delete the virtual memory
associated with the file (the memory used by the common block). When
you delete the virtual memory used by a mapped file, any changes made
while the file was mapped are written back to the disk file. Use the
Delete Virtual Address Space (SYS$DELTVA) system service to delete the
virtual memory used by a mapped file. Use the Deassign I/O Channel
(SYS$DASSGN) system service to deassign the I/O channel assigned to a
file.
The program segment shown in Example 28-4 closes a mapped file,
automatically writing any modifications back to the disk. To ensure
that the proper locations are deleted, pass SYS$DELTVA the addresses
returned to your program by SYS$CRMPSC rather than the addresses you
passed to SYS$CRMPSC. If you want to save modifications made to the
mapped section without closing the file, use the Update Section File on
Disk (SYS$UPDSEC) system service. To ensure that the proper locations
are updated, pass SYS$UPDSEC the addresses returned to your program by
SYS$CRMPSC rather than the addresses you passed to SYS$CRMPSC.
Typically, you want to wait until the update operation completes before
continuing program execution. Therefore, use the efn
argument of SYS$UPDSEC to specify an event flag to be set when the
update is complete, and wait for the system service to complete before
continuing. For a complete description of the SYS$DELTVA, SYS$DASSGN,
and SYS$UPDSEC system services, see the OpenVMS System Services Reference Manual.
Example 28-4 Closing a Mapped File |
! Section address
INTEGER*4 ADDR(2),
2 RET_ADDR(2)
! Event flag
INTEGER*4 FLAG
! Status block
STRUCTURE /IO_BLOCK/
INTEGER*2 IOSTAT,
2 HARDWARE
INTEGER*4 BAD_PAGE
END STRUCTURE
RECORD /IO_BLOCK/ IOSTATUS
.
.
.
! Get an event flag
STATUS = LIB$GET_EF (FLAG)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL(STATUS))
! Update the section
STATUS = SYS$UPDSEC (RET_ADDR,
2 ,,,
2 %VAL(FLAG)
2 ,
2 IOSTATUS,,)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL(STATUS))
! Wait for section to be updated
STATUS = SYS$SYNCH (%VAL(FLAG),
2 IOSTATUS)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL(STATUS))
.
.
.
|
28.5 Opening and Updating a Sequential File
This section provides an example, written in Compaq Fortran, of how to
open and update a sequential file on a VAX system. A sequential file
consists of records arranged one after the other in the order in which
they are written to the file. Records can only be added to the end of
the file. Typically, sequential files are accessed sequentially.
Creating a Sequential File
To create a sequential file, use the OPEN statement and specify the
following keywords and keyword values:
- STATUS ='NEW'
- ACCESS = 'SEQUENTIAL'
- ORGANIZATION = 'SEQUENTIAL'
The file structure keyword ORGANIZATION also accepts the value
'INDEXED' or 'RELATIVE'.
Example 28-5 creates a sequential file of fixed-length records.
Example 28-5 Creating a Sequential File of
Fixed-Length Records |
.
.
.
INTEGER STATUS,
2 LUN,
2 LIB$GET_INPUT,
2 LIB$GET_LUN,
2 STR$UPCASE
INTEGER*2 FN_SIZE,
2 REC_SIZE
CHARACTER*256 FILENAME
CHARACTER*80 RECORD
! Get file name
STATUS = LIB$GET_INPUT (FILENAME,
2 'File name: ',
2 FN_SIZE)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS))
! Get free unit number
STATUS = LIB$GET_LUN (LUN)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS))
! Open the file
OPEN (UNIT = LUN,
2 FILE = FILENAME (1:FN_SIZE),
2 ORGANIZATION = 'SEQUENTIAL',
2 ACCESS = 'SEQUENTIAL',
2 RECORDTYPE = 'FIXED',
2 FORM = 'UNFORMATTED',
2 RECL = 20,
2 STATUS = 'NEW')
! Get the record input
STATUS = LIB$GET_INPUT (RECORD,
2 'Input: ',
2 REC_SIZE)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS))
DO WHILE (REC_SIZE .NE. 0)
! Convert to uppercase
STATUS = STR$UPCASE (RECORD,RECORD)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS))
WRITE (UNIT=LUN) RECORD(1:REC_SIZE)
! Get more record input
STATUS = LIB$GET_INPUT (RECORD,
2 'Input: ',
2 REC_SIZE)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS))
END DO
END
|
Updating a Sequential File
To update a sequential file, read each record from the file, update it,
and write it to a new sequential file. Updated records cannot be
written back as replacement records for the same sequential file from
which they were read.
Example 28-6 updates a sequential file, giving the user the option of
modifying a record before writing it to the new file. The same file
name is used for both files; because the new update file was opened
after the old file, the new file has a higher version number.
Example 28-6 Updating a Sequential File |
.
.
.
INTEGER STATUS,
2 LUN1,
2 LUN2,
2 IOSTAT
INTEGER*2 FN_SIZE
CHARACTER*256 FILENAME
CHARACTER*80 RECORD
CHARACTER*80 NEW_RECORD
INCLUDE '($FORDEF)'
INTEGER*4 LIB$GET_INPUT,
2 LIB$GET_LUN,
2 STR$UPCASE
! Get file name
STATUS = LIB$GET_INPUT (FILENAME,
2 'File name: ',
2 FN_SIZE)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS))
! Get free unit number
STATUS = LIB$GET_LUN (LUN1)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS))
! Open the old file
OPEN (UNIT=LUN1,
2 FILE=FILENAME (1:FN_SIZE),
2 ORGANIZATION='SEQUENTIAL',
2 ACCESS='SEQUENTIAL',
2 RECORDTYPE='FIXED',
2 FORM='UNFORMATTED',
2 RECL=20,
2 STATUS='OLD')
! Get free unit number
STATUS = LIB$GET_LUN (LUN2)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS))
! Open the new file
OPEN (UNIT=LUN2,
2 FILE=FILENAME (1:FN_SIZE),
2 ORGANIZATION='SEQUENTIAL',
2 ACCESS='SEQUENTIAL',
2 RECORDTYPE='FIXED',
2 FORM='UNFORMATTED',
2 RECL=20,
2 STATUS='NEW')
! Read a record from the old file
READ (UNIT=LUN1,
2 IOSTAT=IOSTAT) RECORD
IF (IOSTAT .NE. IOSTAT_OK) THEN
CALL ERRSNS (,,,,STATUS)
IF (STATUS .NE. FOR$_ENDDURREA) THEN
CALL LIB$SIGNAL (%VAL(STATUS))
END IF
END IF
DO WHILE (STATUS .NE. FOR$_ENDDURREA)
TYPE *, RECORD
! Get record update
STATUS = LIB$GET_INPUT (NEW_RECORD,
2 'Update: ')
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS))
! Convert to uppercase
STATUS = STR$UPCASE (NEW_RECORD,
2 NEW_RECORD)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS))
! Write unchanged record or updated record
IF (NEW_RECORD .EQ. ' ' ) THEN
WRITE (UNIT=LUN2) RECORD
ELSE
WRITE (UNIT=LUN2) NEW_RECORD
END IF
! Read the next record
READ (UNIT=LUN1,
2 IOSTAT=IOSTAT) RECORD
IF (IOSTAT .NE. IOSTAT_OK) THEN
CALL ERRSNS (,,,,STATUS)
IF (STATUS .NE. FOR$_ENDDURREA) THEN
CALL LIB$SIGNAL (%VAL(STATUS))
END IF
END IF
END DO
END
|
|