Document revision date: 30 March 2001

## OpenVMS Programming Concepts Manual

Previous | Contents | Index

## Chapter 28
File Operations

This chapter describes file operations that support file input/output (I/O) and file I/O instructions of the operating system's high-level languages. This chapter contains the following sections:

Section 28.1 describes file attributes.

Section 28.2 describes strategies to access files.

Section 28.3 describes protection and access of files.

Section 28.4 describes file mapping.

Section 28.5 describes how to open and update a sequential file.

Section 28.6 describes using the Fortran user-open routines.

I/O statements transfer data between records in files and variables in your program. The I/O statement determines the operation to be performed; the I/O control list specifies the file, record, and format attributes; and the I/O list contains the variables to be acted upon.

Note Some confusion might arise between records in a file and record variables. Where this chapter refers to a record variable, the term record variable is used; otherwise, record refers to a record in a file.

## 28.1 File Attributes

Before writing a program that accesses a data file, you must know the attributes of the file and the order of the data. To determine this information, see your language-specific programming manual.

File attributes (organization, record structure, and so on) determine how data is stored and accessed. Typically, the attributes are specified by keywords when you open the data file.

Ordering of the data within a file is not important mechanically. However, if you attempt to read data without knowing how it is ordered within the file, you are likely to read the wrong data; if you attempt to write data without knowing how it is ordered within the file, you are likely to corrupt existing data.

### 28.1.1 Specifying File Attributes

You can specify large sets of attributes using the File Definition Language utility (FDL). You can specify all of the file attributes using OpenVMS RMS in a user-open routine (see Section 28.6). Typically, you need only programming language file specifiers. Use FDL only when language specifiers are unavailable.

Refer to the appropriate programming language reference manual for information about the use of language specifiers.

For complete information about how to use FDL, see the OpenVMS Record Management Utilities Reference Manual.

### 28.1.2 Using Extended File Specifications (Alpha Only)

On Alpha systems running OpenVMS V7.2, extended file specifications support disk file specifications of greater length, a larger character set, and deeper directories than was supported on previous versions of Alpha OpenVMS. For information about extended file specifications, see the OpenVMS Guide to Extended File Specifications and the Guide to OpenVMS File Applications for a description of these features.

## 28.2 File Access Strategies

When determining the file attributes and order of your data file, consider how you plan to access that data. File access strategies fall into the following categories:

- Complete access
  If your program processes all or most of the data in the file and especially if many references are made to the data, you should read the entire file into memory. Put each record in its own variable or set of variables.
  If your program is larger than the amount of virtual memory available (including the additional memory you get by using memory allocation routines), you must declare fewer variables and process your file in pieces. To determine the size of your program, add the number of bytes in each program section. The DCL command LINK/MAP produces a listing that includes the length of each program section (PSECT).
- Record-by-record access
  If your program accesses records one after another, or if you cannot fit the entire file into memory, you should read one record into memory at a time.
- Discrete records access
  If your program processes only a selection of the file's records, you should read only the necessary records into memory.
- Sequential and indexed file access
  If your program demands speed and needs to conserve disk space, use an unformatted sequential file. Use indexed files either to process selected sets of records or to access records directly. Use either a sequential file with fixed-length records, a relative file, or an indexed file to access records directly.

## 28.3 File Protection and Access

Files are owned by the process that creates them and receive the default protection of the creating process. To create a file with ownership and protection other than the default, use the File Definition Language (FDL) attributes OWNER and PROTECTION in the file.

### 28.3.1 Read-Only Access

By default, the user of your program must have write access to a file in order for your program to open that file. However, if you specify use of the Fortran READONLY specifier when opening the file, the user needs only read access to the file to open it. The READONLY specifier does not set the protection on a file. The user cannot write to a file opened with the READONLY specifier.

### 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:

1. 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:

       PSECT_ATTR = name, PAGE

   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.
2. 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.
3. Map the data file to the common block.
4. Process the records using the program variables in the common block.
5. 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:

1. Set the user-file open bit (FAB$V_UFO) in the file access block (FAB) options mask.
2. 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.)
3. 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.
4. 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:

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).
2. 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.

Previous | Next | Contents | Index

| privacy and legal statement |
5841PRO_076.HTML