|  | OpenVMS Utility Routines Manual
 
 16.3.2.2 Symbiont Processing of Carriage Control
Each input record can be thought of as consisting of three parts:
leading carriage control, data, and trailing carriage control. Taken
together, these three parts are called the composite data record.
 
Leading and trailing carriage control are determined by the type of
carriage control used in the file and explicit carriage-control
information returned with each record. For embedded carriage control,
however, leading and trailing carriage control is always null.
 
The type of carriage control returned by the main input routine on the
PSM$K_OPEN request code determines, for that invocation of the input
routine, how the symbiont applies carriage control to each record that
the main input routine returns on the PSM$K_READ request code.
 
Note that, for all four carriage control types, the first character
returned on the first PSM$K_READ call to an input routine receives
special processing. If that character is a line feed or a form feed and
if the symbiont is currently at line 1, column 1 of the current page,
then the symbiont discards that line feed or form feed.
 
The Four Types of Carriage Control
 
The following table briefly describes each type of carriage control and
how the symbiont's main input routine processes it. For a detailed
explanation of each type of carriage control, refer to the description
of the FAB$B_RAT field of the FAB block in the OpenVMS Record Management Services  Reference Manual.
 
  
    | Type of Carriage Control | Symbiont Processing |  
    | Embedded | Leading and trailing carriage control are embedded in the data portion
      of the input record. Therefore, the symbiont supplies no special
      carriage control processing; it assumes that leading and trailing
      carriage control are null. |  
    | Fortran | The first byte of each data record contains a Fortran carriage-control
      character. This character specifies both the leading and trailing
      carriage control for the data record. The symbiont extracts the first
      byte of each data record and interprets that byte as a Fortran
      carriage-control character. If the data record is empty, the symbiont
      generates a leading carriage control of line feed and a trailing
      carriage control of carriage return. |  
    | PRN | Each data record contains a 2-byte header that contains the
      carriage-control specifier. The first byte specifies the carriage
      control to apply before printing the data portion of the record. The
      second byte specifies the carriage control to apply after printing the
      data portion. The abbreviation PRN stands for print-file format.  Unlike other types of carriage control, PRN carriage control
      information is returned through the
      funcarg argument of the main input routine; this
      occurs with the PSM$K_READ request. The
      funcarg argument specifies a longword; your routine
      writes the 2-byte PRN carriage control specifier into the first two
      bytes of this longword.
     |  
    | Implied | The symbiont provides a leading line feed and a trailing carriage
      return. But if the data record consists of a single form feed, the
      symbiont sets to null the leading and trailing carriage control for
      that record, and the leading carriage control for the record that
      follows it. |  16.3.3 Writing a Format Routine
To write a format routine, follow the modification procedure described
in Section 16.3. Do not replace the symbiont's main format routine.
Instead, modify its action by writing input and output filter routines.
These execute immediately before and after the main format routine,
respectively. The main formatting routine uses an undocumented and
nonpublic interface; you cannot replace the main formatting routine.
The DCL command PRINT/PASSALL bypasses the main format routine of the
print symbiont.
 
See Section 16.3.5 for additional information about other function codes
used in the user-written formatting routine.
16.3.3.1 Internal Logic of the Symbiont's Main Format Routine 
The main format routine contains all the logic necessary to convert
composite data records to a data stream for output. Actions taken by
the format routine include the following:
 
  Tracking the current column and line
  Implementing the special processing of the first character of the
  first record
  Implementing the alignment data mask specified by the DCL command
  START/QUEUE/ALIGN=MASK
  Handling margins as specified by the forms definition
  Initiating processing of page headers when specified by the DCL
  command PRINT/HEADER
  Expanding leading and trailing carriage control
  Handling line overflow
  Handling page overflow
  Expanding tab characters to spaces for some devices
  Handling escape sequences
  Accumulating accounting information
  Implementing double-spacing when specified by the DCL command
  PRINT/SPACE
  Implementing automatic page ejection when specified by the DCL
  command PRINT/FEED
 
The symbiont's main format routine uses a special rule when processing
the first character of the first composite data record returned by an
input routine. (A composite data record is the input data record and a
longword that contains carriage-control information for the input data
record.) This rule is that if the first character is a vertical format
effector (form feed or line feed) and if the symbiont has processed no
printable characters on the current page (that is, the current position
is column 1, line 1), then that vertical format effector is discarded.
16.3.4 Writing an Output Routine 
To write an output routine, follow the modification procedure described
in Section 16.3.
 
The print symbiont calls your output routine. Input arguments are
supplied by the print symbiont; output arguments and status values are
returned by your routine to the print symbiont. For this reason, your
output routine must have the call interface that is described in the
USER-OUTPUT-ROUTINE routine.
 
When the print symbiont calls your routine, it specifies in one of the
input arguments---the func argument---the reason for
the call. Each reason has a corresponding function code.
 
There are several function codes that the print symbiont can supply
when it calls your output routine. Your routine must contain the logic
to respond to the following function codes: PSM$K_OPEN, PSM$K_WRITE,
PSM$K_WRITE_NOFORMAT, and PSM$K_CLOSE.
 
It is not required that your output routine contain the logic to
respond to the other function codes, but you can provide this logic if
you want to.
 
A complete list and description of all relevant function codes for
output routines is provided in the description of the
func argument of the USER-OUTPUT-ROUTINE routine.
 
See Section 16.3.5 for additional information about other function codes.
16.3.4.1 Internal Logic of the Symbiont's Main Output Routine 
When the symbiont calls the main output routine with the PSM$K_OPEN
function code, the main output routine takes the following steps:
 
  Allocates the print device
  Assigns a channel to the device
  Obtains the device characteristics
  Returns the device-status longword in the funcarg
  argument (for more information, see the description of the
  SMBMSG$K_DEVICE_STATUS message item in Chapter 17)
  Returns an error if the device is not a terminal or a printer
 
When this routine receives a PSM$K_WRITE service request code, it sends
the contents of the symbiont output buffer to the device for printing.
 
When this routine receives a PSM$K_WRITE_NOFORMAT service request code,
it sends the contents of the symbiont output buffer to the device for
printing and suppresses device drive formatting as appropriate for the
device in use.
 
When this routine receives a PSM$K_CANCEL service request code, it
requests the device driver to cancel any outstanding output operations.
 
When this routine receives a PSM$K_CLOSE service request code, it
deassigns the channel to the device and deallocates the device.
16.3.5 Other Function Codes 
A status PSM$_PENDING might not be returned whenever the symbiont
notifies user-written input, output, and format routines using the
following message function codes:
 
  
    | Function Code | Description |  
    | PSM$K_START_STREAM | Job controller sends a message to the symbiont to start a queue |  
    | PSM$K_START_TASK | Symbiont parses a message from job controller directing it to start a
      queue |  
    | PSM$K_PAUSE_TASK | Job controller sends a message to the symbiont to suspend processing of
      the current task |  
    | PSM$K_STOP_STREAM | Job controller sends a message to the symbiont to stop the queue |  
    | PSM$K_STOP_TASK | Job controller sends a message to the symbiont to stop the task |  
    | PSM$K_RESUME_TASK | Job controller sends a message to the symbiont to resume processing of
      the current task |  
    | PSM$K_RESET_STREAM | Same as PSM$K_STOP_STREAM |  16.3.6 Writing a Symbiont Initialization Routine
Writing a symbiont initialization routine involves writing a program
that calls the following:
 
  PSM$REPLACE once for each routine (input, output, or format) that
  you have written. PSM$REPLACE identifies your routines to the symbiont.
  PSM$PRINT exactly once after you have identified all your service
  routines using PSM$REPLACE.
 
Table 16-1 lists all routine codes that you can specify in the
PSM$REPLACE routine. Choosing the correct routine code is important
because the code specifies when the symbiont will call your routine.
The functions of these routines are described further in the
description of the PSM$REPLACE routine.
 
For those input routines that execute in a predefined sequence, the
second column contains a number showing the order in which that input
routine is called relative to the other input routines for a single
file job. If the routine does not execute in a predefined sequence, the
second column contains the character x.
 
Column three specifies whether the routine is an input, format, or
output routine; this information directs you to the section describing
how to write a routine of that type.
 
Column four specifies whether there is a symbiont-supplied routine
corresponding to that routine code. The codes for the input-filter and
output-filter routines, which have no corresponding routines in the
symbiont, allow you to specify new routines for inclusion in the
symbiont.  
 
  Table 16-1 Routine Codes for Specification to PSM$REPLACE
  
    | Routine Code | Sequence | Function | Supplied |  
    | PSM$K_JOB_SETUP | 1 | Input | Yes |  
    | PSM$K_FORM_SETUP | 2 | Input | Yes |  
    | PSM$K_JOB_FLAG | 3 | Input | Yes |  
    | PSM$K_JOB_BURST | 4 | Input | Yes |  
    | PSM$K_FILE_SETUP | 5 | Input | Yes |  
    | PSM$K_FILE_FLAG | 6 | Input | Yes |  
    | PSM$K_FILE_BURST | 7 | Input | Yes |  
    | PSM$K_FILE_SETUP_2 | 8 | Input | Yes |  
    | PSM$K_MAIN_INPUT | 9 | Input | Yes |  
    | PSM$K_FILE_INFORMATION | 10 | Input | Yes |  
    | PSM$K_FILE_ERRORS | 11 | Input | Yes |  
    | PSM$K_FILE_TRAILER | 12 | Input | Yes |  
    | PSM$K_JOB_RESET | 13 | Input | Yes |  
    | PSM$K_JOB_TRAILER | 14 | Input | Yes |  
    | PSM$K_JOB_COMPLETION
      1 | 15 | Input | Yes |  
    | PSM$K_PAGE_SETUP | x | Input | Yes |  
    | PSM$K_PAGE_HEADER | x | Input | Yes |  
    | PSM$K_LIBRARY_INPUT | x | Input | Yes |  
    | PSM$K_INPUT_FILTER | x | Formatting | No |  
    | PSM$K_MAIN_FORMAT | x | Formatting | Yes |  
    | PSM$K_OUTPUT_FILTER | x | Formatting | No |  
    | PSM$K_OUTPUT
      1 | x | Output | Yes |  1The job completion (PSM$K_JOB_COMPLETION) and output
(PSM$K_OUTPUT) routines are not replaceable when using the LAT protocol
option.
 
 
 16.3.7 Integrating a Modified Symbiont
To integrate your user routine and the symbiont initialization routine,
perform the following steps; note that the sequence of steps described
here assumes that you will be debugging the modified symbiont:
 
  Compile or assemble the user routine and the symbiont
  initialization routine into an object module.
  Enter the following DCL command:
 
  
    | 
 
$ LINK/DEBUG your-symbiont
 |  The file name your-symbiont is the object module built in
    Step 1. Symbols necessary for this link operation are located in the
    shareable images SYS$SHARE:SMBSRVSHR.EXE and SYS$LIBRARY:IMAGELIB.EXE.
    The linker automatically searches these shareable images and extracts
    the necessary information.
Place the resulting executable symbiont image in SYS$SYSTEM.
  Locate two unallocated terminals: one at which to issue DCL
  commands and one at which to debug the symbiont image.
  Log in on one of the terminals under UIC [1,4], which is the system
  manager's account. This terminal is the one at which you enter DCL
  commands. Do not log in at the other terminal.
  Enter the following DCL command:
 
  
    | 
 
$ SET TERMINAL/NODISCONNECT/PERMANENT _TTcu:
 |  The variable _TTcu: is the physical terminal name of the
    terminal at which you want to debug (the terminal at which you are not
    logged in). You must specify the underscore (_) and colon (:)
    characters.
Enter the following DCL commands:
 
  
    | 
 
$ DEFINE/GROUP DBG$INPUT  _TTcu:
$ DEFINE/GROUP DBG$OUTPUT _TTcu:
 |  The variable _TTcu: specifies the physical terminal name
    of the terminal at which you will be debugging. Note that other users
    having a UIC with group number 1 should not use the debugger at the
    same time.
Initialize the queue by entering the following DCL command:
 
  
    | 
 
$ INITIALIZE/QUEUE/PROCESSOR= your-symbiont /ON= printer_name
 |  The symbiont image specified by the file name
    your-symbiont must reside in SYS$SYSTEM. Note too that the
    /PROCESSOR qualifier accepts only a file name; the device, directory,
    and file type default to SYS$SYSTEM:.EXE.
 The /ON qualifier
    specifies the device that will be served by the symbiont while you
    debug the symbiont.
Enter the following DCL command to execute the modified symbiont
  routine:
 
  
    | 
 
$ PRINT/HEADER/QUEUE=queue-id
 |  Enter the following DCL command to start the queue and invoke the
    debugger:
 
After you debug your symbiont, relink the symbiont by entering the
  following DCL command:
 
  
    | 
 
$ LINK/NOTRACEBACK/NODEBUG your-symbiont
 | Deassign the logical names DBG$INPUT and DBG$OUTPUT so that they
  will not interfere with other users in UIC group 1.
 16.4 Using the PSM Routines: An Example
Example 16-1 shows how to use PSM routines to supply a page header
routine in a VAX MACRO program.
 
 
  
    | Example 16-1 Using PSM Routines to Supply a
    Page Header Routine in a VAX MACRO Program |  
  
    | 
 
        .TITLE  EXAMPLE - Example user modified symbiont
        .IDENT  'V03-000'
;++
;       THIS PROGRAM SUPPLIES A USER WRITTEN PAGE HEADER
;       ROUTINE TO THE STANDARD SYMBIONT.  THE PAGE HEADER
;       INCLUDES THE SUBMITTER'S ACCOUNT NAME AND USER NAME,
;       THE FULL FILE SPECIFICATION, AND THE PAGE NUMBER.
;       THE HEADER LINE IS UNDERLINED BY A ROW OF DASHES
;       PRINTED ON A SECOND HEADER LINE.
;--
        .LIBRARY  /SYS$LIBRARY:LIB.MLB/
;
; System definitions
;
        $PSMDEF                                 ; Symbiont definitions
        $SMBDEF                                 ; Message item definitions
        $DSCDEF                                 ; Descriptor definitions
;
; Define argument offsets for user supplied services called by symbiont
;
        CONTEXT         = 04                    ; symbiont context
        WORK_AREA       = 08                    ; user context
        FUNC            = 12                    ; function code
        FUNC_DESC       = 16                    ; function dependent descriptor
        FUNC_ARG        = 20                    ; function dependent argument
;
; Macro to create dynamic descriptors
;
        .MACRO  D_DESC
                .WORD   0                       ; DSC$W_LENGTH = 0
                .BYTE   DSC$K_DTYPE_T           ; DSC$B_DTYPE = STRING
                .BYTE   DSC$K_CLASS_D           ; DSC$B_CLASS = DYNAMIC
                .LONG   0                       ; DSC$A_POINTER = 0
        .ENDM
;
; Storage for page header information
;
        FILE:           D_DESC                  ; file name descriptor
        USER:           D_DESC                  ; user name descriptor
        ACCOUNT:        D_DESC                  ; account name descriptor
        PAGE:           .LONG   0               ; page number
        LINE:           .LONG   0               ; line number
;
; FAO control string and work buffer.  Header format:
;       "[account,name] filename ........ Page 9999"
;
        FAO_Ctrl:       .ASCID  /!71<[!AS, !AS] !AS!>Page 9999/
        FAO_Ctrl_2:     .ASCID  /!4UL/
        FAO_DESC:       .LONG   80              ; work buffer descriptor
                        .ADDRESS FAO_BUFF
        FAO_BUFF:       .BLKB   80              ; work buffer
;
; Own storage for values passed by reference
;
        CODE:           .LONG   0               ; service or item code
        STREAMS:        .LONG   1               ; number of simultaneous streams
        BUFSIZ:         .LONG   2048            ; output buffer size
        LINSIZ:         .WORD   81              ; line size for underlines
;
; Main routine -- invoked at image startup
;
START:  .WORD   0       ; save nothing because this routine uses only R0 and R1
;
; Supply private page header routine
;
        MOVZBL  #PSM$K_PAGE_HEADER,CODE         ; set the service code
        PUSHAL  HEADER                          ; address of modified routine
        PUSHAL  CODE                            ; address of service code
        CALLS   #2,G^PSM$REPLACE                ; replace the routine
        BLBC    R0,10$                          ; exit if any errors
;
; Transfer control to the standard symbiont
;
        PUSHAL  BUFSIZ                          ; address of output buffer size
        PUSHAL  STREAMS                         ; address of number of streams
        CALLS   #2,G^PSM$PRINT                  ; invoke standard symbiont
10$:    RET
;
; Page header routine
;
HEADER: .WORD   0                               ; save nothing
;
; Check function code
;
        CMPL    #PSM$K_START_TASK,@FUNC(AP)     ; new task?
        BEQL    20$                             ; branch if so
        CMPL    #PSM$K_READ,@FUNC(AP)           ; READ function?
        BNEQ    15$
        BRW     50$                             ; branch if so
15$:    CMPL    #PSM$K_OPEN, @FUNC(AP)          ; OPEN function?
        BNEQ    16$
        BRW     66$                             ; branch if so
16$:    MOVL    #PSM$_FUNNOTSUP,R0              ; unsupported function
        RET                                     ; return to symbiont
;
; Starting a new file
;
20$:
        CLRL    PAGE                            ; reset the page number
        MOVZBL  #2,LINE                         ; and the line number
;
; Get the account name
;
        MOVZBL  #SMBMSG$K_ACCOUNT_NAME,CODE     ; set item code
        PUSHAL  ACCOUNT                         ; address of descriptor
        PUSHAL  CODE                            ; address of item code
        PUSHAL  @CONTEXT(AP)                    ; address of symbiont ctx value
        CALLS   #3,G^PSM$READ_ITEM_DX           ; read it
        BLBC    R0,40$                          ; branch if any errors
;
; Get the file name
;
        MOVZBL  #SMBMSG$K_FILE_SPECIFICATION,CODE       ; set item code
        PUSHAL  FILE                            ; address of descriptor
        PUSHAL  CODE                            ; address of item code
        PUSHAL  @CONTEXT(AP)                    ; address of symbiont ctx value
        CALLS   #3,G^PSM$READ_ITEM_DX           ; read it
        BLBC    R0,40$                          ; branch if any errors
;
; Get the user name
;
        MOVZBL  #SMBMSG$K_USER_NAME,CODE        ; set item code
        PUSHAL  USER                            ; address of descriptor
        PUSHAL  CODE                            ; address of item code
        PUSHAL  @CONTEXT(AP)                    ; address of symbiont ctx value
        CALLS   #3,G^PSM$READ_ITEM_DX           ; read it
        BLBC    R0,40$                          ; branch if any errors
;
; Set up the static header information that is constant for the task
;
        $FAO_S  CTRSTR  = FAO_Ctrl, -           ; FAO control string desc
                OUTBUF  = FAO_DESC, -           ; output buffer descriptor
                P1      = #ACCOUNT, -           ; account name descriptor
                P2      = #USER, -              ; user name descriptor
                P3      = #FILE                 ; file name descriptor
        BLBC    R0,40$                          ; branch if any errors
        MOVL    #PSM$_FUNNOTSUP,R0              ; unsupported function
40$:    RET                                     ; return usupported status or error
;
; Read a page header
;
50$:
        DECL    LINE                            ; decrement the line number
        BEQL    60$                             ; branch if second read
        BLSS    70$                             ; branch if third read
;
; Insert the page number into the header
;
        INCL    PAGE                            ; increment the page number
        MOVAB   FAO_BUFF+76,FAO_DESC+4          ; point to page number buffer
        $FAO_S  CTRSTR  = FAO_Ctrl_2, -         ; FAO control string desc
                OUTBUF  = FAO_DESC, -           ; output buffer descriptor
                P1      = PAGE                  ; page number
        MOVAB   FAO_BUFF,FAO_DESC+4             ; point to work buffer
        BLBC    R0,55$                          ; return if error
;
; Copy the line to the symbiont's buffer
;
        PUSHAB  FAO_DESC                        ; work buffer descriptor
        PUSHL   FUNC_DESC(AP)                   ; symbiont descriptor
        CALLS   #2,G^STR$COPY_DX                ; copy to symbiont buffer
55$:    RET                                     ; return success or any error
;
; Second line -- underline header
;
60$:
        PUSHL   FUNC_DESC(AP)                   ; symbiont descriptor
        PUSHAL  LINSIZ                          ; number of bytes to reserve
        CALLS   #2,G^STR$GET1_DX                ; reserve the space
        BLBC    R0,67$                          ; exit if error
        MOVL    FUNC_DESC(AP),R1                ; get address of descriptor
        MOVL    4(R1),R1                        ; get address of buffer
        MOVAB   80(R1),R0                       ; set up transfer limit
65$:    MOVB    #^A/-/,(R1)+                    ; fill with dashes
        CMPL    R0,R1                           ; reached limit?
        BGTRU   65$                             ; branch if not
        MOVB    #10,(R1)+                       ; extra line feed
66$:    MOVZBL  #SS$_NORMAL,R0                  ; set success
67$:    RET                                     ; return
;
; Done with this page header
;
70$:
        MOVL    #PSM$_EOF,R0                    ; return end of input
        MOVZBL  #2,LINE                         ; reset line counter
        RET                                     ; return
        .END    START
 |  
 
   |