        .TITLE  SEND_FUNCTIONS -- Macro functions for the SEND command.
        .IDENT  /01/
        .LIBRARY        "SYS$LIBRARY:LIB.MLB"
;
; Author:  Mark Snititly
; Date:    June 15, 1980
;
; This file contains three functions that are employed by the Pascal SEND
; program:
;       1) GET_COMMANDLINE:
;            Returns a string descriptor containing the current command line.
;       2) FIND_USER_NAME:
;            Given the process's name, attempts to locate a unique process that
;            has that name. Called only if process's name is not within the
;            sending process's UIC group.  Returns PID if successful.
;       3) FIND_USER_TERM:
;            Given the PID of a process, returns a string descriptor containing
;            the device name of the terminal allocated to the process.
;*******************************************************************************
; GET_COMMANDLINE:
; This routine returns the command line to the calling process. The only
; parameter is a string descriptor.
;
; Pascal external declaration:
;
; PROCEDURE GET_COMMANDLINE(VAR CMD_DSCR: STRDSCR); EXTERN;
;

        $CLIDEF         ; command language interpreter definition
        $CLISERVDEF
COM:    $CLIREQDESC     CLI$K_GETCMD

FILL:   .BYTE   ^A/ /

        .ENTRY  GET_COMMANDLINE,^M<R2,R3,R4,R5>
        PUSHAB  COM                     ; Push CLI request descriptor.
        CALLS   #1,SYS$CLI              ; Get the command line descriptor.
        MOVAQ   COM+CLI$Q_RQDESC,R0     ; Put addr of command line dscr in R0.
        MOVL    4(AP),R1                ; Put addr of user's dscr in R1.
        MOVL    (R0),(R1)               ; Copy length of command line.
        MOVC3   (R0),@4(R0),@4(R1)      ; Copy the command line dscr to
                                        ;    user's dscr.
        RET
;*******************************************************************************
; FIND_USER_NAME:
; This function tries to find a unique process from a specified user name.
; Only invoked if user name is not within group. If more than one name is
; found, then SS$_TOOMUCHDATA is returned.
;
; Pascal external declaration:
;
; FUNCTION FIND_USER_NAME(VAR USER_PID: INTEGER;
;                         VAR USER_DSCR: STRDSCR): INTEGER; EXTERN;
;

        $IPLDEF         ; Interrupt priority level definition.
        $PCBDEF         ; Process control block definition.

ARGLIST1:       .LONG   2               ; Two parameter.
                .BLKL   2               ; Parameters for Kernel Mode routine.

        .ENTRY  FIND_USER_NAME,^M<R2>
        MOVQ    4(AP),ARGLIST1+4        ; Save params for Kernel mode routine.
        MOVL    8(AP),R1                ; Move address of device name to R1.
        BNEQ    TEST_PID1               ; Continue if specified.
BAD_PARAM1:
        MOVZWL  #SS$_BADPARAM,R0        ; Specify a bad parameter.
        BRB     EXIT1
TEST_PID1:      ; Test for access to PID.
        IFNOWRT #4,@4(AP),ACCVIO1       ; Abort if PID can't be written.
        CLRL    @4(AP)                  ; Clear contents of PID parameter.
        IFRD    #8,(R1),GET_LENGTH1     ; Continue if desc can be accessed.
ACCVIO1:MOVZWL  #SS$_ACCVIO,R0          ; Abort with access violation.
        BRB     EXIT1
GET_LENGTH1:    ; Test length in the dscriptor.
        MOVQ    (R1),R1                 ; Move descriptor to R1 and R2.
        CMPL    R1,#15                  ; Test against maximum length.
        BGTR    BAD_LENGTH1             ;    Abort if too long.
        TSTL    R1                      ; Test against minimum length
        BGTR    TEST_NAME1              ;    Continue if > 0.
BAD_LENGTH1:
        MOVZWL  #SS$_IVLOGNAM,R0        ; Invalid logical name.
        BRB     EXIT1
TEST_NAME1:
        IFNORD  R1,(R2),ACCVIO1         ; Abort if string can't be read.
        $CMKRNL_S FIND_NAME,ARGLIST1    ; Execute kernal mode routine.
EXIT1:  RET                             ; Return.


FIND_NAME:   ; Kernel mode routine that finds the PCB given process name.
        .WORD   ^M<R2,R3,R4,R5>         ; Entry mask.
        SETIPL  #IPL$_ASTDEL            ; Disable (some) interrupts.
        MOVL    8(AP),R2                ; Put address of name in R2.
        MOVQ    (R2),R2                 ; Put length in R2 & string addr in R3.
        MOVL    G^SCH$GL_MAXPIX,R4      ; Initialize process index.
        BSB     PIXLOOP                 ; Find first occurance of process name.
        BLBC    R0,DONE                 ; Abort if not found.
        MOVL    R1,R5                   ; Save PID of process for later.
        BSB     PIXLOOP                 ; Any more occurrances of the same name?
        BLBC    R0,ONLY_ONE_NAME        ;    No, go set success status.
        MOVZWL  #SS$_TOOMUCHDATA,R0     ;    Yes, set error status
        BRB     DONE                    ;         and go abort.
ONLY_ONE_NAME:
        MOVL    PCB$L_PID(R5),@4(AP)    ; Return the PID.
        MOVZWL  #SS$_NORMAL,R0          ; Set success status code.
DONE:   SETIPL  #0                      ; Set user's IPL
        RET                             ;    and return.

FIND_NEXT_NAME: ; Subroutine that finds nest occurrance of specified name.
        TSTL    R4                      ; Have all PCB's been tested?
        BLSS    NONE                    ;    Yes, exit immediately.
PIXLOOP:  
        MOVAB   G^SCH$GL_PCBVEC,R1      ; Move address of vector to R1.
        MOVL    @(R1)[R4],R1            ; Get PCB from vector.
        CMPB    R2,PCB$T_LNAME(R1)      ; Are string length equal?
        BNEQ    NEXPIX                  ;    No, advance to next PCB.
        PUSHR   #^M<R0,R1,R2,R3>        ; Save registers.
        CMPC    R2,(R3),PCB$T_LNAME+1(R1); Are the strings equal?
        POPR    #^M<R0,R1,R2,R3>        ; Restore registers.
        BEQL    FOUND_PCB               ;    Yes, exit loop.
NEXPIX: SOBGEQ  R4,PIXLOOP              ;    No, go try the next PCB.
NONE:   MOVZWL  #SS$_NONEXPR,R0         ; Process doesn't exist,
        BRB     RTN                     ;   so abort.
FOUND_PCB:
        DECL    R4                      ; Advance R4 for next call.
        MOVZWL  #SS$_NORMAL,R0          ; Set success return status
RTN:    RSB                             ;    and return.
;*******************************************************************************
; FIND_USER_TERM
; This routine attemps to find the terminal that is allocated to a specified
; user given its PID.  If successful, then the device name of the terminal is
; returned in the string descriptor parameter.  
;
; If no terminal is allocated to the user, then the error status SS$_DEVOFFLINE
; is returned.  If more than one terminal is allocated to the user, then the
; error status SS$_DEVICEFULL is returned.
;
; Pascal external declaration:
;
; FUNCTION FIND_USER_TERM(%IMMED PID: INTEGER
;                          VAR DEVICE_DSCR: STRDSCR): INTEGER; EXTERN;
;
        $DCDEF          ; Device class definition.
        $DDBDEF         ; Device data block definition.
        $IPLDEF         ; Interrupt priority level definition.
        $PCBDEF         ; Process control block definition.
        $UCBDEF         ; Unit control block definition.

ARGLIST:        .LONG   2               ; Two parameters.
                .BLKL   2               ; Parameters for Kernel Mode routine.
DDB:            .BLKL   1
UCB:            .BLKL   1
STATUS:         .BLKL   1

        .ENTRY  FIND_USER_TERM,^M<R2>

        MOVQ    4(AP),ARGLIST+4         ; Save params for Kernel mode routine.
        MOVL    8(AP),R1                ; Move address of device name to R1.
        BNEQ    TEST_PID                ; Continue if specified.
BAD_PARAM:
        MOVZWL  #SS$_BADPARAM,R0        ; Specify a bad parameter.
        BRB     EXIT
TEST_PID:       ; Test for non-zero PID.
        TSTL    4(AP)                   ; If PID = 0,
        BEQL    BAD_PARAM               ;   then abort.
TEST_DSCR:      ; Test if descriptor is valid.
        IFRD    #8,(R1),GET_LENGTH      ; Continue if desc can be accessed.
ACCVIO: MOVZWL  #SS$_ACCVIO,R0          ; Abort with access violation.
        BRB     EXIT
GET_LENGTH:     ; Test length in the dscriptor.
        MOVQ    (R1),R1                 ; Move descriptor to R1 and R2.
        CMPL    R1,#63                  ; Test against maximum length.
        BGTR    BAD_LENGTH              ;    Abort if too long.
        CMPL    R1,#5                   ; Test against minimum length
        BGTR    TEST_NAME               ;    Continue if > 5.
BAD_LENGTH:
        MOVZWL  #SS$_IVLOGNAM,R0        ; Invalid logical name.
        BRB     EXIT
TEST_NAME:
        IFNORD  R1,(R2),ACCVIO          ; Abort if string can't be read.
        $CMKRNL_S FIND_TT,ARGLIST       ; Execute kernal mode routine.
EXIT:   RET                             ; Return.


FIND_TT:        ; Kernel mode routine that finds the user's terminal.
        .WORD   ^M<R2,R3,R4,R5>         ; Entry mask.

CHECK_PID:      ; Test if process is valid.
        SETIPL  #IPL$_SYNCH             ; Disable interrupts.
        MOVZWL  4(AP),R1                ; Put process index in R1.
        CMPL    R1,G^SCH$GL_MAXPIX      ; Test against maximum index.
        BGTRU   NONEX                   ; Not there if greater than max.
        MOVAB   G^SCH$GL_PCBVEC,R0      ; Move address of vector to R0.
        MOVL    @(R0)[R1],R1            ; Get PCB from vector.
        CMPL    4(AP),PCB$L_PID(R1)     ; Is the PID valid?
        BEQL    GET_1ST_DDB             ;   Yes, continue.
NONEX:  MOVZWL  #SS$_NONEXPR,R0         ; Set nonexistent process status
        BRW     ABORT                   ;    and go abort.

GET_1ST_DDB:    ; Get the address of the first terminal Device Data Block (DDB).
        MOVL    @#SCH$GL_CURPCB,R4      ; Get address of current PCB.
        JSB     SCH$IOLOCKR             ; Lock data base for read.
        MOVL    @#IOC$GL_DEVLIST,R2     ; Get address of first DDB.

        MOVL    DDB$L_UCB(R2),R5        ; Put address of next UCB in R5.
        BSB     FIND_PID                ; Attempt to find user's PID.
        MOVL    R0,STATUS               ; Save status in case of error.
        BLBC    R0,UNLOCK               ; Abort if user not found.
        MOVL    R2,DDB                  ; Save DDB address.
        MOVL    R4,UCB                  ; Save UCB address.
        BSB     FIND_PID                ; See if user has another terminal.
        BLBC    R0,ONLY_1_TERM          ; Continue if only one terminal.
        MOVZWL  #SS$_DEVICEFULL,STATUS  ; More than one terminal: Set error
        BRB     UNLOCK                  ;   and abort.

ONLY_1_TERM:    ; Only one terminal was found so set the device descriptor.
        MOVL    8(AP),R1                ; Put address of dscr into R1.
        MOVZBL  #6,(R1)                 ; Set length of device name.
        MOVL    4(R1),R1                ; Put address of string into R1.
        MOVQ    DDB,R2                  ; Put DDB addr in R2 & UCB addr in R3.
        MOVL    DDB$T_NAME(R2),(R1)     ; Put chars 2-->4 into string.
        MOVB    #^X<5F>,(R1)            ; Put "_" into first char of string.
; Note the use of R4 is current UCB & R5 is next UCB.
        ADDB3   UCB$W_UNIT(R3),-        ; Put unit number into fifth char
                #^X<30>,4(R1)           ;    of the string.
        MOVB    #^X<3A>,5(R1)           ; Put colon into sixth char.
        MOVZWL  #SS$_NORMAL,STATUS      ; Set status code for success.

UNLOCK: MOVL    @#SCH$GL_CURPCB,R4      ; Get address of current PCB.
        JSB     G^SCH$IOUNLOCK          ; Unlock data base.
        MOVZWL  STATUS,R0               ; Set return code.
ABORT:  SETIPL  #0                      ; Set to user IPL
        RET                             ;    and return.


; FIND_PID -- Searches for specified PID within terminal I/O Data Base.
;
; Inputs:  4(AP) = PID
;          R5 = next UCB to test.
;          R2 = current DDB.
;
; Output: if found R4 = UCB of terminal. (SS$_NORMAL)
;         Not found returns status of SS$_DEVOFFLINE.
;         Inputs are invarient.
;
FIND_PID:
        MOVL    R5,R4                   ; Current UCB <-- Next UCB
        BNEQ    SET_NEXT_UCB            ; Don't advance DDB if valid.
        MOVL    DDB$L_LINK(R2),R2       ; DDB <-- DDB^.next
        BNEQ    VALID_DDB               ; If valid DDB then continue
        MOVZWL  #SS$_DEVOFFLINE,R0      ;    else User not found: set error code
        RSB                             ;         and return.
VALID_DDB:      ; Advanced to a new terminal DDB.
        MOVL    DDB$L_UCB(R2),R4        ; Current UCB <-- DDB^.UCB
SET_NEXT_UCB:   ; Set up next UCB for next iteration.
        MOVL    UCB$L_LINK(R4),R5       ; Next UCB <-- Current UCB^.next
        CMPB    #DC$_TERM,-             ; Is this UCB
                UCB$B_DEVCLASS(R4)      ;    a terminal?
        BNEQ    FIND_PID                ;    No, try next UCB.
        CMPL    UCB$L_PID(R4),4(AP)     ; Do the PID's match?
        BNEQ    FIND_PID                ;    No, try next UCB.
        MOVZWL  #SS$_NORMAL,R0          ;    Yes, set success status code
        RSB                             ;         and return.

        .END
