      PROGRAM TIMESUM
      IMPLICIT NONE
C
C John Hasstedt
C Physics Department
C State University of New York
C Stony Brook, New York  11794-3800
C Telephone: (516) 632-8154
C HASSTEDT@SUNYSBNP.BITNET
C
C This program generates a summary of an accounting file giving the number
C of simultaneous processes as a function of time.  It reads each record of
C the file and if it is a process termination record finds the start and end
C times.  It increments a counter for each second between these times.  When
C it has processed all the records in the file, it finds the maximum within
C each time period and outputs it.  This gives a count of the maximum number
C of simultaneous processes on the system over time (excluding the system
C processes that do not do accounting and any processes that did not exit
C until after the accounting file is read).  You can use the ACCOUNTING
C utility to create a binary file of specific types of processes and use
C this program to generate summaries.  Some examples:
C
C 1. These commands generate a summary giving the number of interactive
C    processes as a function of time.
C
C    $ ACCOUNTING ACCOUNTNG /TYPE=PROCESS /PROCESS=INTERACTIVE -
C         /BINARY /OUTPUT=INTERACTIVE
C    $ TIMESUM INTERACTIVE
C
C 2. Suppose your modems are on terminal lines TXD0:, TXD1:, and TXD2:.
C    These commands generate a summary giving the number of dial in lines
C    that are in use as a function of time.
C
C    $ ACCOUNTING ACCOUNTNG /TYPE=PROCESS /PROCESS=INTERACTIVE -
C         /TERMINAL=(TXD0:,TXD1:,TXD2:) /BINARY /OUTPUT=DIALIN
C    $ TIMESUM DIALIN
C
C TIMESUM is defined as a foreign command:
C
C   TIMESUM :== $dev:[dir]TIMESUM
C
C It is executed with the command:
C
C   TIMESUM [<infile>] [qualifiers]
C
C Parameter:
C
C   <infile> is the input file name.  If it is omitted, ACCOUNTNG is used.
C   (This default is specified in TIMESUMCLD.CLD.)  The default extension
C   is .DAT.
C
C Qualifiers:
C
C   /OUTPUT=<outfile>  specifies the output file name.  If omitted, the
C   program uses a file with the same name as the input and an extension
C   of .LIS.  The default extension is .LIS.
C
C   /INCREMENT=<inc>  specifies the increment in minutes in the output.
C   It must be an integral divisor of 60.  The default (specified in
C   TIMESUMCLD.CLD) is 10.
C
C   /THRESHOLD=<thres>  specifies the flag value.  Any value greater than
C   or equal to <thres> has a * beside it.  The default is 32767 (specified
C   in TIMESUMCLD.CLD), which eliminates the threshold.
C
C   /TITLE="<string>"  specifies the title to go at the top of the page.  The
C   default (specified in TIMESUMCLD.CLD) is "Time Summary".  This string is
C   centered at the top of the output.  Immediately below it is the starting
C   date.
C
C A sample of output is shown below.  It is cut off on the right hand side.
C The actual output is 129 columns wide and lists 31 days.  It was generated
C from a file containing only interactive process records, and so gives the
C number of simultaneous interactive users.  The increment was 10, so the
C values are given for 10 minute periods.  The threshold was 15, so every
C value that is 15 or greater has a * by it.  The 4 on the first at 00:00
C means that at some time between 00:00:00 and 00:09:59 there were 4 users
C on simultaneously.  It might have been for only 1 second, and several users
C may have logged in and logged out, but there were at most 4 users logged
C in at any one time.  Zeros are not printed.
C
C         1   2   3   4   5   6   7   8   9  10  11  12  13 . . .
C       Thu Fri Sat Sun Mon Tue Wed Thu Fri Sat Sun Mon Tue
C      ----------------------------------------------------
C 00:00  4       2   3   1   2   4   5   3   2           2
C 00:10  4       3   2   1   2   3   5   2   2           3
C .
C .
C .
C
C 10:00 13   9   3      15* 10   7   9  12   4       7   7
C 10:10 14  11   3      14   9   8  12  14   2       8   7
C 10:20 16*  7   2   1  14   9  10   9  10   4   1   8   8
C 10:30 14  10   3   1  13  12  12  10  13   4   1   6   8
C 10:40 13  10   4   4  12  11  12  10  11   5   1   7   8
C 10:50 13  11   4   3  12  10  14  13  10   5   1  11  10
C 11:00 12  11   5   2  14  10  14  17* 10   5   1  15* 10
C 11:10 13  10   6   3  12  12  14  16* 10   5   1  16* 11
C 11:20 13  10   6   3  13  12  12  11  11   5   2  17* 11
C 11:30 15* 13   5   3  14   8  11   8  10   3   1  14  14
C 11:40 16* 12   6   4  10   7  14   7  10   3   2  12  12
C 11:50 11  11   6   4  10   7  14  14  11   4   3  11  12
C .
C .
C .
C
C 23:30  1   3   3   1   2   4   4   4   3   1   1   3   3
C 23:40  1   2   3   1   2   3   6   4   4       1   5   2
C 23:50  1   3   3   1   3   4   5   3   3           4   2
C
C The program is designed for an accounting file containing records for
C one month.  I use bytes for the counters to reduce the storage space
C required, so the maximum number of simultaneous processes to 255.
C
C Define constants for converting times.
C
      INTEGER DAYPMON
      PARAMETER (DAYPMON = 31)
      INTEGER HOURPDAY
      PARAMETER (HOURPDAY = 24)
      INTEGER HOURPMON
      PARAMETER (HOURPMON = HOURPDAY * DAYPMON)
      INTEGER MINPHOUR
      PARAMETER (MINPHOUR = 60)
      INTEGER MINPDAY
      PARAMETER (MINPDAY = MINPHOUR * HOURPDAY)
      INTEGER MINPMON
      PARAMETER (MINPMON = MINPDAY * DAYPMON)
      INTEGER SECSPMIN
      PARAMETER (SECSPMIN = 60)
      INTEGER SECSPHOUR
      PARAMETER (SECSPHOUR = SECSPMIN * MINPHOUR)
      INTEGER SECSPDAY
      PARAMETER (SECSPDAY = SECSPHOUR * HOURPDAY)
      INTEGER SECSPMON
      PARAMETER (SECSPMON = SECSPDAY * DAYPMON)
C
C Define unit numbers for the input and output files.
C
      INTEGER INUNIT, OUTUNIT
      PARAMETER (INUNIT = 1, OUTUNIT = 2)
C
C Define the structure for the accounting record.
C
      INTEGER RECSIZ
      PARAMETER (RECSIZ = 300)
      INCLUDE '($ACRDEF)'
      STRUCTURE /ACCREC/
        UNION
          MAP
            BYTE BYTES(0:RECSIZ-1)
          ENDMAP
          MAP
            RECORD /ACRDEF/ HEADER
          ENDMAP
          MAP
            RECORD /ACRDEF1/ HEADER1
          ENDMAP
        ENDUNION
      ENDSTRUCTURE
C
C Declare functions.
C
      INTEGER GET          ! To read records from the file
C
C Declare error codes.
C
      EXTERNAL TIMESUM_INVRECORD, RMS$_EOF
      INTEGER TIMESUM_INVRECORD, RMS$_EOF
C
C Names for the days of the week.
C
      CHARACTER*4 DAY(0:6) /' Sun', ' Mon', ' Tue', ' Wed',
     1                      ' Thu', ' Fri', ' Sat'/
C
C Local variables.
C
      INTEGER STATUS                ! Return status
      CHARACTER*80 INPUT            ! Input file name
      CHARACTER*80 OUTPUT           ! Output file name
      INTEGER MINPPER               ! Minutes in the period
      INTEGER SECSPPER              ! Seconds in the period
      INTEGER THRESHOLD             ! Threshold value
      CHARACTER*132 TITLE           ! Title for the output
      INTEGER TITLELEN              ! Length of title
      BYTE ARRAY(0:SECSPMON-1)      ! Array of counters
      RECORD /ACCREC/ REC           ! Accounting record
      INTEGER LENGTH                ! Length of the record
      INTEGER TYPE                  ! Packet type
      CHARACTER*11 ASCZER           ! Starting date
      INTEGER ZERTIM(2)             ! Starting time in internal format
      INTEGER BEGTIM(2)             ! Process start time in internal format
      INTEGER BEGSEC                ! Process start time in seconds
      INTEGER ENDSEC                ! Process end time in seconds
      INTEGER I                     ! Loop counter
      BYTE VAL(0:DAYPMON-1)         ! Maximum number for a period
      CHARACTER*1 FLAG(0:DAYPMON-1) ! Flag for the maximum number
      INTEGER H, M, D, S            ! Counters for the output loop
C
C Get the information from the command line.
C
      CALL GETCMD (INPUT, OUTPUT, MINPPER, THRESHOLD, TITLE, TITLELEN)
      SECSPPER = 60 * MINPPER
C
C Open the accounting file.
C
        OPEN (UNIT=INUNIT, FILE=INPUT, DEFAULTFILE='.DAT', STATUS='OLD',
     1     READONLY, FORM='FORMATTED', RECORDTYPE='VARIABLE')
C
C Read the first record from the file and verify that it is valid.  The
C read should return the number of bytes specified in the record, the
C flag in the header should be 0, and it should be a version 3 packet.
C
      STATUS = GET (INUNIT, REC.BYTES, RECSIZ, LENGTH)
      IF (.NOT. STATUS) THEN
        CLOSE (UNIT=INUNIT)
        CALL LIB$STOP (%VAL(STATUS))
      ENDIF
      IF (
     1   REC.HEADER.ACR$W_LENGTH .NE. LENGTH
     2 .OR.
     3   IBITS (REC.HEADER.ACR$W_TYPE, ACR$V_PACKET, ACR$S_PACKET)
     4        .NE. 0
     5 .OR.
     6   IBITS (REC.HEADER.ACR$W_TYPE, ACR$V_VERSION, ACR$S_VERSION)
     7        .NE. ACR$K_VERSION3
     8   ) THEN
        CLOSE (UNIT=INUNIT)
        CALL LIB$STOP (TIMESUM_INVRECORD)
      ENDIF
C
C Convert the time from the first record to ASCII.  ASCZER is 11 characters,
C so just the date is returned.  Zero hours is appended to this and it is
C converted to binary.  ZERTIM is then midnight on the day of the first
C record in the accounting file.
C
      CALL SYS$ASCTIM (, ASCZER, REC.HEADER1.ACR$Q_SYSTIME, )
      CALL SYS$BINTIM (ASCZER//' 00:00:00.00', ZERTIM)
C
C Jump over the READ to process the record already read.
C
      GOTO 160
C
C Start of loop.
C
150   CONTINUE
C
C Read a record.  Each record is checked here as it is above.
C
      STATUS = GET (INUNIT, REC.BYTES, RECSIZ, LENGTH)
      IF (STATUS .EQ. %LOC(RMS$_EOF)) GOTO 410
      IF (.NOT. STATUS) THEN
        CLOSE (UNIT=INUNIT)
        CALL LIB$STOP (%VAL(STATUS))
      ENDIF
      IF (
     1   REC.HEADER.ACR$W_LENGTH .NE. LENGTH
     2 .OR.
     3   IBITS (REC.HEADER.ACR$W_TYPE, ACR$V_PACKET, ACR$S_PACKET)
     4        .NE. 0
     5 .OR.
     6   IBITS (REC.HEADER.ACR$W_TYPE, ACR$V_VERSION, ACR$S_VERSION)
     7        .NE. ACR$K_VERSION3
     8   ) THEN
        CLOSE (UNIT=INUNIT)
        CALL LIB$STOP (TIMESUM_INVRECORD)
      ENDIF
160   CONTINUE
C
C Check that it is a process deletion record and if so, process it.
C
      IF (IBITS (REC.HEADER.ACR$W_TYPE, ACR$V_TYPE, ACR$S_TYPE)
     1   .EQ. ACR$K_PRCDEL) THEN
C
C Step through the record, looking at each packet until I find the resource
C packet.  GETINFO returns the packet type and the length.  If I reach the
C end of the record without finding a resource packet, it is an invalid record.
C
        I = ACR$K_HDRLEN
170     CALL GETINFO (REC.BYTES(I), TYPE, LENGTH)
        IF (TYPE .NE. ACR$K_RESOURCE) THEN
          I = I + LENGTH
          IF (I .GE. REC.HEADER.ACR$W_LENGTH) THEN
            CLOSE (UNIT=INUNIT)
            CALL LIB$STOP (TIMESUM_INVRECORD)
          ENDIF
          GOTO 170
        ENDIF
C
C Get the start time and convert to seconds since ZERTIM.  I round the start
C time up and the end time down so that process times will not overlap due to
C rounding.
C
        CALL GETTIME (REC.BYTES(I), BEGTIM)
        CALL GETSEC (ZERTIM, BEGTIM, BEGSEC)
        BEGSEC = BEGSEC + 1
C
C Convert the end time to seconds since ZERTIM.
C
        CALL GETSEC (ZERTIM, REC.HEADER1.ACR$Q_SYSTIME, ENDSEC)
C
C Check that the start and end times are within the bounds of the array
C and increment the array.  GETSEC will not return a negative number, so
C it is not necessary to check for less than 0.
C
        IF (BEGSEC .LT. SECSPMON) THEN
          IF (ENDSEC .GE. SECSPMON) ENDSEC = SECSPMON - 1
          DO I = BEGSEC, ENDSEC
            ARRAY(I) = ARRAY(I) + 1
          ENDDO
        ENDIF
C
C End of processing for a process deletion record.
C
      ENDIF
C
C Go back to read next record.
C
      GOTO 150
C
C Processing is complete.  Close the input file.
C
410   CLOSE (UNIT=INUNIT)
C
C Open the output file.
C
      OPEN (UNIT=OUTUNIT, FILE=OUTPUT, DEFAULTFILE='.LIS',
     1          STATUS='NEW', CARRIAGECONTROL='LIST')
C
C Write the title and starting time.
C
      WRITE (UNIT=OUTUNIT, FMT=9100) TITLE, ASCZER
9100  FORMAT (<66-TITLELEN/2>X, A<TITLELEN>, /, 56X, 'Starting ', A11)
C
C Write the column headings.
C
      READ (UNIT=ASCZER(1:2), FMT='(I2)') D
      CALL DAYS_IN_MONTH (ASCZER(4:6), ASCZER(8:11), M)
      WRITE (UNIT=OUTUNIT, FMT=9400) (I,I=D,M), (I,I=1,DAYPMON-1-M+D)
9400  FORMAT (5X, <DAYPMON>I4)
      CALL LIB$DAY_OF_WEEK (ZERTIM, D)
      WRITE (UNIT=OUTUNIT, FMT=9500) (DAY(MOD(I,7)),I=D,D+DAYPMON-1)
      WRITE (UNIT=OUTUNIT, FMT=9500) ('----',I=1,DAYPMON)
9500  FORMAT (5X, <DAYPMON>A4)
C
C Loop to write the information.
C
      DO H = 0, HOURPDAY-1    
        DO M = 0, MINPHOUR-1, MINPPER
          DO D = 0, DAYPMON-1
            VAL(D) = 0
            S = D * SECSPDAY + H * SECSPHOUR + M * SECSPMIN
            DO I = S, S + SECSPPER - 1
              IF (ARRAY(I) .GT. VAL(D)) VAL(D) = ARRAY(I)
            ENDDO
            IF (VAL(D) .GE. THRESHOLD) THEN
              FLAG(D) = '*'
            ELSE
              FLAG(D) = ' '
            ENDIF
          ENDDO
          WRITE (UNIT=OUTUNIT, FMT=9600)
     1          H, M, (VAL(I), FLAG(I), I=0,DAYPMON-1)
9600      FORMAT (I2.2, ':', I2.2, <DAYPMON>(I3.0,A1))
        ENDDO
      ENDDO
C
C Close the output file and exit.
C
      CLOSE (UNIT=OUTUNIT)
      CALL EXIT
C
      END
C===========================================================
      SUBROUTINE GETCMD (INPUT, OUTPUT, INCREMENT, THRESHOLD,
     1       TITLE, TITLELEN)
      IMPLICIT NONE
      CHARACTER*(*) INPUT     ! Input file name
      CHARACTER*(*) OUTPUT    ! Output file name
      INTEGER INCREMENT       ! Time period for the output
      INTEGER THRESHOLD       ! Threshold for flagging the output
      CHARACTER*(*) TITLE     ! Title for the output
      INTEGER TITLELEN        ! Length of title
C
C This subroutine parses the command line and returns the parameters from it.
C
C Declare system services.
C
      INTEGER LIB$GET_FOREIGN ! To get the command line
      INTEGER CLI$DCL_PARSE   ! To parse the command line
      INTEGER CLI$GET_VALUE   ! To get the values
C
C Declare the command table.
C
      EXTERNAL TIMESUMCLD
      INTEGER TIMESUMCLD
C
C Declare return status values.
C
      EXTERNAL CLI$_ABSENT
      INTEGER CLI$_ABSENT
      EXTERNAL STS$K_WARNING
      INTEGER STS$K_WARNING
      EXTERNAL TIMESUM_INVINCR
      INTEGER TIMESUM_INVINCR
C
C Local variables.
C
      INTEGER STATUS          ! Return status
      CHARACTER*256 CMDLINE   ! Command line
      DATA CMDLINE /'TIMESUM'/
      CHARACTER*8 STRING      ! String returned for numbers
      INTEGER*2 LENGTH        ! Length of returned string
      INTEGER I, J            ! Indexes to string
C
C Get the command line.
C
      STATUS = LIB$GET_FOREIGN (CMDLINE(9:), , , )
      IF (.NOT. STATUS) CALL LIB$STOP (%VAL(STATUS))
C
C Parse the command line.  CLI$DCL_PARSE signals warnings, so only errors
C are signalled here.
C
      STATUS = CLI$DCL_PARSE (CMDLINE, TIMESUMCLD, , , )
      IF (.NOT. STATUS) THEN
        IF (IBITS (STATUS, 0, 3) .EQ. %LOC(STS$K_WARNING)) THEN
          CALL EXIT
        ELSE
          CALL LIB$STOP (%VAL(STATUS))
        ENDIF
      ENDIF
C
C Get the input file name.
C
      STATUS = CLI$GET_VALUE ('INPUT', INPUT, )
      IF (.NOT. STATUS) CALL LIB$STOP (%VAL(STATUS))
C
C Get the output file name.  If it is not specified, use the input file
C name with any extension removed.
C
      STATUS = CLI$GET_VALUE ('OUTPUT', OUTPUT, )
      IF (STATUS .EQ. %LOC(CLI$_ABSENT)) THEN
        OUTPUT = INPUT
        I = INDEX(OUTPUT,']')
        IF (I .EQ. 0) I = 1
        J = INDEX(OUTPUT(I:),'.')
        IF (J .NE. 0) OUTPUT(I+J-1:) = ' '
      ELSE IF (.NOT. STATUS) THEN
        CALL LIB$STOP (%VAL(STATUS))
      ENDIF
C
C Get the increment.  It is present by default, so should always return
C a value.  Check that it is an integral divisor of 60.
C
      STATUS = CLI$GET_VALUE ('INCREMENT', STRING, LENGTH)
      IF (.NOT. STATUS) CALL LIB$STOP (%VAL(STATUS))
      READ (UNIT=STRING(1:LENGTH), FMT='(I<LENGTH>)') INCREMENT
      IF (INCREMENT .LE. 0 .OR. INCREMENT .GT. 60
     1     .OR. MOD (60, INCREMENT) .NE. 0) THEN
        CALL LIB$STOP (TIMESUM_INVINCR, %VAL(1), %VAL(INCREMENT))
      ENDIF
C
C Get the threshold.  It is present by default, so should always return
C a value.
C
      STATUS = CLI$GET_VALUE ('THRESHOLD', STRING, LENGTH)
      IF (.NOT. STATUS) CALL LIB$STOP (%VAL(STATUS))
      READ (UNIT=STRING(1:LENGTH), FMT='(I<LENGTH>)') THRESHOLD
C
C Get the title.  It is present by default, so should always return a value.
C
      STATUS = CLI$GET_VALUE ('TITLE', TITLE, LENGTH)
      IF (.NOT. STATUS) CALL LIB$STOP (%VAL(STATUS))
      TITLELEN = LENGTH
C
      RETURN
      END
C===========================================================
      SUBROUTINE GETTIME (REC, TIME)
      IMPLICIT NONE
      INCLUDE '($ACRDEF)'
      RECORD /ACRDEF3/ REC   ! Accounting packet
      INTEGER TIME(2)        ! Location to return time
C
C This subroutine gets the time from the resource packet in REC and returns
C it in TIME.
C
      TIME(1) = REC.ACR$Q_LOGIN(1)
      TIME(2) = REC.ACR$Q_LOGIN(2)
      RETURN
      END
C===========================================================
      SUBROUTINE GETINFO (REC, TYPE, LENGTH)
      IMPLICIT NONE
      INCLUDE '($ACRDEF)'
      RECORD /ACRDEF/ REC    ! Accounting packet
      INTEGER TYPE           ! Location to return type
      INTEGER LENGTH         ! Location to return length
C
C This subroutine returns the type and length of an accounting packet.
C
      TYPE = IBITS (REC.ACR$W_TYPE, ACR$V_TYPE, ACR$S_TYPE)
      LENGTH = REC.ACR$W_LENGTH
      RETURN
      END
C===========================================================
      SUBROUTINE GETSEC (ZERTIM, TIME, SEC)
      IMPLICIT NONE
      INTEGER ZERTIM(2)      ! Zero time
      INTEGER TIME(2)        ! Later time
      INTEGER SEC            ! Location to return seconds
C
C This subroutine subtracts ZERTIM from TIME and converts it to seconds.
C
C Define parameters for time conversions.
C
      INTEGER HOURPDAY
      PARAMETER (HOURPDAY = 24)
      INTEGER MINPHOUR
      PARAMETER (MINPHOUR = 60)
      INTEGER SECSPMIN
      PARAMETER (SECSPMIN = 60)
C
C Define parameters for words in the time buffer.
C
      INTEGER YEAR
      PARAMETER (YEAR = 1)
      INTEGER MONTH
      PARAMETER (MONTH = 2)
      INTEGER DAY
      PARAMETER (DAY = 3)
      INTEGER HOUR
      PARAMETER (HOUR = 4)
      INTEGER MINUTE
      PARAMETER (MINUTE = 5)
      INTEGER SECOND
      PARAMETER (SECOND = 6)
      INTEGER HUNDREDTH
      PARAMETER (HUNDREDTH = 7)
C
C Local variables.
C
      INTEGER TIME1(2)       ! To hold time difference
      INTEGER*2 TIMBUF(7)    ! Buffer returned by SYS$NUMTIM
C
C Subtract TIME from ZERTIM.  If time is later than ZERTIM, this give a
C negative number.  Negative times are incremental, so SYS$NUMTIM will
C return the year and month as 0.  If the year is not 0, TIME is earlier
C than ZERTIM and 0 is returned for the number of seconds.  TIME can be
C earlier than ZERTIM since records are written to the file when the
C process is deleted.  A process can log in before the accounting file
C is created and log out afterwards.
C
      CALL LIB$SUBX (ZERTIM, TIME, TIME1, 2)
      CALL SYS$NUMTIM (TIMBUF, TIME1)
      IF (TIMBUF(YEAR) .NE. 0) THEN
        SEC = 0
      ELSE
        SEC = TIMBUF(DAY)
        SEC = SEC * HOURPDAY + TIMBUF(HOUR)
        SEC = SEC * MINPHOUR + TIMBUF(MINUTE)
        SEC = SEC * SECSPMIN + TIMBUF(SECOND)
      ENDIF
      RETURN
      END
C===========================================================
      SUBROUTINE DAYS_IN_MONTH (MONTH, YEAR, DAY)
      IMPLICIT NONE
      CHARACTER*3 MONTH      ! Name of month
      CHARACTER*4 YEAR       ! Year
      INTEGER DAY            ! Location to return number of days
C
C This subroutine returns the number of days in the month specified by MON
C and YEAR.  It returns the number of days in DAY.
C
      CHARACTER*36 MONTHS /'JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC'/
      INTEGER DAYS(12) /31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31/
      INTEGER I

      I = INDEX (MONTHS, MONTH)
      DAY = DAYS((I+2)/3)
      IF (DAY .EQ. 28) THEN
        READ (UNIT=YEAR, FMT='(I4)') I
        IF (MOD(I,4) .EQ. 0) THEN
          IF (MOD(I,400) .NE. 0) THEN
            DAY = DAY + 1
          ENDIF
        ENDIF
      ENDIF
      RETURN
      END
C===========================================================
      INTEGER FUNCTION GET (UNIT, BUFFER, BUFSIZ, RECSIZ)
      INTEGER UNIT
      BYTE BUFFER(1)
      INTEGER BUFSIZ
      INTEGER RECSIZ
C
C This function reads a record from a file.  It calls GET__ with the address
C of the record access block.  It returns an RMS status code.
C
      INTEGER GET__
C
      GET = GET__ (%VAL(FOR$RAB(UNIT)), BUFFER, BUFSIZ, RECSIZ)
      RETURN
      END
C===========================================================
      INTEGER FUNCTION GET__ (RAB, BUFFER, BUFSIZ, RECSIZ)
      IMPLICIT NONE
      INCLUDE '($RABDEF)'
      RECORD /RABDEF/ RAB
      BYTE BUFFER(1)
      INTEGER BUFSIZ
      INTEGER RECSIZ
C
C This function reads a record from a file using SYS$GET.  It puts the buffer
C address and size into the RAB, clear the LOC bit, and calls SYS$GET.  It
C returns the number of bytes read.  When the LOC bit is set, RMS does not
C copy the data into the user buffer.  It just returns the address of the
C record in the RMS internal buffer.  Fortran sets this bit to save some
C overhead time -- it can decode the data directly from the RMS buffer.
C The bit must be cleared for this routine, or nothing will be returned.
C
      INTEGER SYS$GET
C
      RAB.RAB$L_UBF = %LOC(BUFFER)
      RAB.RAB$W_USZ = BUFSIZ
      RAB.RAB$L_ROP = IBCLR (RAB.RAB$L_ROP, RAB$V_LOC)
      GET__ = SYS$GET (RAB)
      RECSIZ = RAB.RAB$W_RSZ
      RETURN
      END
