[comp.os.vms] Command input from a file continued, and maybe a bug in VMS

RALPH@UHHEPG.BITNET (08/07/87)

Date:  5-AUG-1987 20:29:29.00
From: Ralph Becker-Szendy RALPH AT UHHEPG
To:   B_INFOVAX,RALPH
Subj: Command input from a file continued, and maybe a bug in VMS
Hi everyone out there

Thanks a lot for all the help i got for my little problem with input/output
redirection:

> I want something like the DCL @ command: If the command is @filespec,
> the command interpreters should open this file, and present it (line
> by line) to CLI$DCL_PARSE and other routines, which read special input
> from a terminal.

Unfortunately, none of the help got me anywhere (or rather: some of them
seem to get me into bug-land). I was offered really big packages for this
purpose (mostly written in straight FORTRAN), but they are bulky, and would
require a lot of recoding (since they would completely replace all the
CLI$... routines), and a lot of understanding them , and i wanted a quick
afternoon-hack.

Several fellow netpeople pointed mo towards the LIB$SET_LOGICAL routine,
which seems to do what i want: assign SYS$INPUT or SYS$OUTPUT to a file
(instead of the terminal) for a while while the programs runs. Unfortunately,
i found some quite strange behaviour. Look at the following short FORTRAN
program. It should read one line from the terminal, print it, and then
redirect SYS$INPUT to the file INPUT.FILE, read one line from there, print it,
and exit:

      PROGRAM MINITEST ! mini version without loops or error handling

      IMPLICIT NONE
      CHARACTER INPUT*80
      INTEGER LENGTH, STATUS

      INTEGER  LIB$GET_INPUT, LIB$SET_LOGICAL
      EXTERNAL LIB$GET_INPUT, LIB$SET_LOGICAL
      INCLUDE '($RMSDEF)' ! not yet needed, for recognizing EOFs later

      STATUS = LIB$GET_INPUT(INPUT, 'From Terminal: ', LENGTH)
      IF (.NOT.STATUS) CALL LIB$SIGNAL(%VAL(STATUS))
      PRINT *, 'Input from terminal was ', INPUT(1:LENGTH)

      STATUS = LIB$SET_LOGICAL('SYS$INPUT', 'INPUT.FILE')
      IF (.NOT.STATUS) CALL LIB$SIGNAL(%VAL(STATUS))

      STATUS = LIB$GET_INPUT(INPUT, 'From File: ', LENGTH)
      IF (.NOT.STATUS) CALL LIB$SIGNAL(%VAL(STATUS))
      PRINT *, 'Input from file was ', INPUT(1:LENGTH)

      STOP
      END

Well, it does the following:
- it reads one line from the terminal and prints it
- it does reassign SYS$INPUT to INPUT.FILE (you can check it by inserting
  a call to LIB$SYS_TRNLOG('SYS$INPUT'... after the assign)
- it reads ANOTHER LINE FROM THE TERMINAL and prints it !
- Now, back at DCL, a SHOW TRANS SYS$INPUT will yield INPUT.FILE (obviously),
  so i try running the program again:
- it reads the first line from the file (obviously) and prints it
- it reads the second line from the file (still obvious) and prints it.
All variations of the program (like read many lines instead of one, check
for EOFs and such, which i ommited here for the sake of brevity) seem
completely consistent: what counts for LIB$INPUT is not the translation of
SYS$INPUT at the time you call it, but AT THE TIME THE PROGRAM WAS INVOKED.
If SYS$INPUT points to the terminal at the beginning of the program, all
the LIB$SET_LOGICALS can't change the mind of LIB$GET_INPUT.

The only question it: why is this so ? What use is LIB$SET_LOGICAL in this
case ? Is this a bug ? Should i SPR it ? Am i stupid ? Is there something
tricky (maybe using the right logical name table) i overlooked ?

signed: confused

Ralph Becker-Szendy
University of Hawaii / High Energy Physics Group

rankin@EQL.Caltech.EDU (Pat Rankin) (08/08/87)

< attempting to redefine SYS$INPUT inside a program which uses LIB$GET_INPUT >

> All variations of the program (like read many lines instead of one, check
> for EOFs and such, which i ommited here for the sake of brevity) seem
> completely consistent: what counts for LIB$INPUT is not the translation of
> SYS$INPUT at the time you call it, but AT THE TIME THE PROGRAM WAS INVOKED.
> If SYS$INPUT points to the terminal at the beginning of the program, all
> the LIB$SET_LOGICALS can't change the mind of LIB$GET_INPUT.
>
> The only question it: why is this so ? What use is LIB$SET_LOGICAL in this
> case ? Is this a bug ? Should i SPR it ? Am i stupid ? Is there something
> tricky (maybe using the right logical name table) i overlooked ?

     The logical name is used by RMS the first time (and only the first
time) that the routine is called (not at image activation time).
     I haven't looked into LIB$GET_INPUT, but I have disassembled
LIB$PUT_OUTPUT and I can tell you that it works in the following manner:
  -- the first time it is called, it uses $CREATE and $CONNECT to open a
file called "SYS$OUTPUT" (a logical name referencing something).  It
saves the RMS ISI (Internal Stream Identifier, which is essentially a
pointer to the open file).  It then uses $PUT to write to the file.
  -- on subsequent calls, it retreives the ISI, builds a temporary RAB
structure, and issues a $PUT.
     Undoubtedly LIB$GET_INPUT functions similarly.  This should explain
your problem, but it also illustrates that LIB$GET_INPUT cannot do the
job you want to make it do.
                                         Pat Rankin

carl@CITHEX.CALTECH.EDU (08/08/87)

 > Several fellow netpeople pointed mo towards the LIB$SET_LOGICAL routine,
 > which seems to do what i want: assign SYS$INPUT or SYS$OUTPUT to a file
 > (instead of the terminal) for a while while the programs runs. Unfortunately,
 > i found some quite strange behaviour. Look at the following short FORTRAN
 > program. It should read one line from the terminal, print it, and then
 > redirect SYS$INPUT to the file INPUT.FILE, read one line from there, print it,
 > and exit:
 > 
 >       PROGRAM MINITEST ! mini version without loops or error handling
 > 
 >       IMPLICIT NONE
 >       CHARACTER INPUT*80
 >       INTEGER LENGTH, STATUS
 > 
 >       INTEGER  LIB$GET_INPUT, LIB$SET_LOGICAL
 >       EXTERNAL LIB$GET_INPUT, LIB$SET_LOGICAL
 >       INCLUDE '($RMSDEF)' ! not yet needed, for recognizing EOFs later
 > 
 >       STATUS = LIB$GET_INPUT(INPUT, 'From Terminal: ', LENGTH)
 >       IF (.NOT.STATUS) CALL LIB$SIGNAL(%VAL(STATUS))
 >       PRINT *, 'Input from terminal was ', INPUT(1:LENGTH)
 > 
 >       STATUS = LIB$SET_LOGICAL('SYS$INPUT', 'INPUT.FILE')
 >       IF (.NOT.STATUS) CALL LIB$SIGNAL(%VAL(STATUS))
 > 
 >       STATUS = LIB$GET_INPUT(INPUT, 'From File: ', LENGTH)
 >       IF (.NOT.STATUS) CALL LIB$SIGNAL(%VAL(STATUS))
 >       PRINT *, 'Input from file was ', INPUT(1:LENGTH)
 > 
 >       STOP
 >       END
 > 
 > Well, it does the following:
 > - it reads one line from the terminal and prints it
 > - it does reassign SYS$INPUT to INPUT.FILE (you can check it by inserting
 >   a call to LIB$SYS_TRNLOG('SYS$INPUT'... after the assign)
 > - it reads ANOTHER LINE FROM THE TERMINAL and prints it !
 > - Now, back at DCL, a SHOW TRANS SYS$INPUT will yield INPUT.FILE (obviously),
 >   so i try running the program again:
 > - it reads the first line from the file (obviously) and prints it
 > - it reads the second line from the file (still obvious) and prints it.
 > All variations of the program (like read many lines instead of one, check
 > for EOFs and such, which i ommited here for the sake of brevity) seem
 > completely consistent: what counts for LIB$INPUT is not the translation of
 > SYS$INPUT at the time you call it, but AT THE TIME THE PROGRAM WAS INVOKED.
 > If SYS$INPUT points to the terminal at the beginning of the program, all
 > the LIB$SET_LOGICALS can't change the mind of LIB$GET_INPUT.
 > 
 > The only question it: why is this so ? What use is LIB$SET_LOGICAL in this
 > case ? Is this a bug ? Should i SPR it ? Am i stupid ? Is there something
 > tricky (maybe using the right logical name table) i overlooked ?
    
The reason is that the first and only the first call to LIB$GET_INPUT assigns a
channel to SYS$INPUT:; all subsequent calls use that channel.  If you were
to comment out the first call to LIB$GET_INPUT, you'd find that the program
would read from the file on the first invocation.  I don't know how to force
the routine to deassign the channel though, and you really wouldn't want
them to, since when you reopened the channel to the original SYS$INPUT,
you'd start reading at the beginning again.  In summary, this is not a bug,
but rather an implementation that does what you'd normally want it to do
most of the time.

LEICHTER-JERRY@YALE.ARPA (08/09/87)

    ...

    I want something like the DCL @ command: If the command is @filespec,
    the command interpreters should open this file, and present it (line
    by line) to CLI$DCL_PARSE and other routines, which read special input
    from a terminal.

    ...

    Several fellow netpeople pointed me towards the LIB$SET_LOGICAL routine,
    which seems to do what I want....  [I wrote a] short FORTRAN program
    [that] should read one line from the terminal, print it, and then redirect
    SYS$INPUT to the file INPUT.FILE, read one line from there, print it, and
    exit:

    [The program works as described, using LIB$GET_INPUT to read lines.  It
    ends up reading entirely from the terminal, or entirely from the file.]
    [W]hat counts for LIB$INPUT is not the translation of SYS$INPUT at the
    time you call it, but AT THE TIME THE PROGRAM WAS INVOKED....

    The only question is: Why is this so ? What use is LIB$SET_LOGICAL in this
    case ? Is this a bug ? Should i SPR it ? Am i stupid ? Is there something
    tricky (maybe using the right logical name table) i overlooked ?

The logic of LIB$GET_INPUT is simple:

	declare statically-allocated CHANNEL
	declare statically-allocated FILE_OPEN = FALSE

	if (not FILE_OPEN)
		CHANNEL = OPEN("SYS$INPUT:")
		FILE_OPEN = TRUE

	read-line-from-CHANNEL
	return-line-so-read

Thus, what matters to LIB$GET_INPUT is what SYS$INPUT pointed to at the time
LIB$GET_INPUT was first called.  The reason it does this is two-fold:

	- Opening a file is slow process, and not one you want to repeat
		for every line of input;

	- If SYS$INPUT were to point to a file on a disk, then re-opening
		each time would be not just slow, but WRONG:  Each time you
		open the file, you are positioned to the first line!

LIB$GET_INPUT has no way of tracking assignments you do to SYS$INPUT after
the file is open, nor does RMS ever do that for an opened file.

This kind of dynamic re-assignment works within DCL only as the result of a
special-case hack, added in V4.0:  Since user programs want to re-assign
logicals in supervisor mode, they have to ask DCL to do it for them.  That's
what LIB$SET_LOGICAL does that using the VMS $CRELNM cannot do for you.
Similarly, when you type ASSIGN or DEFINE to DCL, it obviously "has its hands
on" the assignments.  Hence, it's in a position to check for any logical names
of special interest to it, and treat them accordingly.  The names it watches
for are, I think, SYS$INPUT and SYS$OUTPUT:  DCL itself has channels open on
those files, and will re-open them when it seems those logicals being changed.
This special-case treatment as added to avoid some anomolies that could make
VMS look very puzzling in earlier versions.  (Not that it can't look very
puzzling now!)  This IS special-case treatment, however, and does NOT genere-
lize.  The correct general principle is:

	Logical names are used for FINDING files (at OPEN time),
	NOT for accessing them (at READ, WRITE time).

As for your original problem:  Sorry, there's no direct, doesn't-change-any-
of-the-code solution I'm aware of.  The "supported" approach is to write
your own routine that reads lines from wherever you want them to come from,
and pass its address to the routines you are using that are currently calling
LIB$GET_INPUT by default.  (I haven't by any means gone through the list, but
I think all the RTL routines work that way:  LIB$GET_INPUT is a default, not
the only option.)

Simply writing your own LIB$GET_INPUT doesn't work:  While routines that YOU
write would use it, the RTL routines are in pre-linked shareable images;
their code is already tied to the "real" LIB$GET_INPUT, which is also in the
shared image.  In fact, since this would give you TWO LIB$GET_INPUT's, the
Linker is going to get unhappy.

A "hack" solution someone might suggest is to over-write the LIB$GET_INPUT
vector to point to your replacement routine.  Unfortunately, even this won't
work:  Calls within the same shareable image as LIB$GET_INPUT will have been
resolved to the actual entry point, not the vector.  (See the discussion of
transfer vectors in either the Linker or MACRO books.)

							-- Jerry
-------