[comp.unix.ultrix] Xterminals and dxsession

mcba@newt.phys.unsw.OZ.AU (Michael C. B. Ashley) (02/27/91)

Has anyone managed to successfully run a number of Xterminals from a
DECstation using dxsession? If so, could use PLEASE send me your
/etc/ttys entries and any relevant setup files?

I have my Xterminals 99% working, my remaining problem is that if you
log into more than one Xterminal with the same username, then when you
quit dxsession from one terminal it clobbers the window manager on the
both terminals. This can cause major problems in our teaching labs where
we have accounts used by many people. The documentation on dxsession is
insufficiently detailed for me to work out what is wrong.

Thanks!
Michael Ashley mcba@newt.phys.unsw.oz.au Astrophysics Department
University of New South Wales

mcba@newt.phys.unsw.OZ.AU (Michael C. B. Ashley) (03/14/91)

A couple of weeks ago I asked 

| Has anyone managed to successfully run a number of Xterminals from a
| DECstation using dxsession? If so, could use PLEASE send me your
| /etc/ttys entries and any relevant setup files?
|
| I have my Xterminals 99% working, my remaining problem is that if you
| log into more than one Xterminal with the same username, then when you
| quit dxsession from one terminal it clobbers the window manager on the
| both terminals. This can cause major problems in our teaching labs where
| we have accounts used by many people. The documentation on dxsession is
| insufficiently detailed for me to work out what is wrong.
|
| Thanks!

I received a very helpful reply from Barry Davis <barry@ncd.com>
which enabled me to solve my problems.

The following setup will allow you to run a number of Xterminals
from a DECstation using dxsession. Each terminal will display the
DIGITAL login box, and behave just like the console. I have only
tried this with NCD 15b Xterminals (great little machines!).  The
system is a DS5000/200 running ULTRIX 4.0 with 88 MBytes of memory
and 23 Xterminals. There are no response problems and the system
runs like a rocket.
        
To begin with you add one entry in the /etc/ttys file for each
Xterminal.  I put the entries at the end of the file, and they should
look something like:

17 "/usr/bin/login -P /usr/local/ncd/Xprompter-ncd
    -C /usr/local/ncd/start-ncd -e" none on secure 
18 "/usr/bin/login -P /usr/local/ncd/Xprompter-ncd
    -C /usr/local/ncd/start-ncd -e" none on secure

(Note: this shows two entries. Each entry should be on a single line.)

These entries instruct init to invoke /usr/bin/login for each
terminal, with its stdin and stdout connected to
/usr/local/ncd/Xprompter-ncd (we shall see in a minute that this is a
script which starts up Xprompter for the terminal in question (which
results in a DIGITAL login box appearing)), and that when a login
succeeds, the script /usr/local/ncd/start-ncd is to be run (this
starts up dxsession). The "-e" switch enables an "extended protocol",
whatever that is (maybe it encrypts the password for transmission?).

The critical trick is that the first thing on the line ("17" and "18"
in the above examples) is sent to the scripts
/usr/local/ncd/Xprompter-ncd and /usr/local/ncd/start-ncd as an
argument, and it is this that enables these scripts to work out which
Xterminal to connect to. I am not sure what possible forms this first
argument can take, just choose something from which you can work out
the IP address of the Xterminal. Clearly there are special cases you
should avoid (e.g., tty01, ttyp0, and :0 (the console)).

You should also make an entry in /dev for each of the new "devices",
e.g.,

# cd /dev
# ln -s /dev/null 17
# ln -s /dev/null 18

or, maybe you could use the following 

# cd /dev
# ls -l /dev/null
crw-rw-rw-  1 root       3,   2 Feb 20 11:21 /dev/null
# mknod /dev/17 c 3 2
# chmod 666 /dev/17
# mknod /dev/18 c 3 2
# chmod 666 /dev/18
# ls -l /dev/null*

(Could someone advise me of the pros and cons of these approaches?)

The only reason that I add an entry to /dev is so that finger works
(otherwise it reports "can't stat device 17"). Presumably there are
other reasons to do this. Can someone offer advice?

Don't forget to do "kill -HUP 1" so that the entries in /etc/ttys
take effect.

Here are the other files that you need:

----------------------------------------------------------
/usr/local/ncd/Xprompter-ncd
#!/bin/sh
if [ $1 = "-e" ]
then
  shift
fi
DISPLAY=`grep '^'$1 /usr/local/ncd/ncdlist | awk '{print $2}'`
export DISPLAY
/usr/bin/Xprompter -e $DISPLAY

----------------------------------------------------------
/usr/local/ncd/start-ncd
#!/bin/sh
if [ $1 = "-e" ]
then
  shift
fi
DISPLAY=`grep '^'$1 /usr/local/ncd/ncdlist | awk '{print $2}'`
export DISPLAY
/usr/bin/dxsession $DISPLAY
/usr/local/ncd/stop-ncd -display $DISPLAY

----------------------------------------------------------
/usr/local/ncd/stop-ncd
#!/bin/sh
for i in `/usr/bin/X11/xlswins | grep -v '^   ' | awk '{print $1}'`
do
  /usr/bin/X11/xkill -id $i $@
done

----------------------------------------------------------

and the file /usr/local/ncd/ncdlist contains the correspondences
between the entries in /etc/ttys and the Xterminal addresses. For
example,

17    129.94.130.128:0
18    129.94.130.129:0

(you can use names rather than IP numbers if you want to).

When you successfully login, you are running "/usr/bin/dxsession
$DISPLAY" from the "/usr/local/ncd/start-ncd" script. When you logout
by choosing the "Quit" option from the "Session" menu of the "Session
Manager", the next line in the script is executed, which calls
"/usr/local/ncd/stop-ncd", which uses xkill to get rid of all your
remaining windows (otherwise the login box will not reappear). I'm a
bit worried about using xkill since the manual entry for it says
don't ever use it. The alternative is to let dxsession kill your
windows for you (this is what happens on the console), however,
dxsession does not appear to be intelligent enough to correctly
guess which windows to remove, and will happily kill window managers
that you may have running on other Xterminals. You persuade dxsession
not to kill your windows by giving it the argument $DISPLAY (actually
anything unique will do (I think)).

A final problem is what to do when a terminal fails to display a
login box.  I have only seen this occur if a user turns the terminal
off in the middle of a session, and then only rarely. Sometimes you
can fix this by using the "Restart Session" button from the NCD
Compose Setup main menu. Othertimes I have found that there is a
dxsession process still hanging which appears to ignore "Restart
Session", so you have to explicitly kill it. Rarely, the Xprompter
process itself needs to be killed and restarted. To allow users to
correct these problems even if they don't own the dxsession process
(e.g., the terminal was hung by someone who then walked off), I have
written the perl script which follows

/usr/local/bin/resetxterm.pl
#!/usr/local/bin/perl -w
print "This program will attempt to terminate your X session and
restore a DIGITAL login box. WARNING: you must run this program from
a Telnet Session created from the Main Menu that appears when you use
COMPOSE-F3. Also, you must only run this program from the X terminal
that you wish to restart.  If both of these conditions are met, type
\"yes\" to continue >";
chop($answer=<STDIN>);
if ($answer ne 'yes') {exit 0;}
$ENV{'IFS'} = '' if $ENV{'IFS'};
$ENV{'PATH'} = "/bin:/usr/bin:/usr/ucb/:/usr/local/bin";
open(TTY, '/usr/bin/tty|') || die "can't run tty!";
$tty = substr(<TTY>,8,2);
close(TTY);
open(W, '/usr/ucb/w|') || die "can't run w!";
$ip = 'invalid';
while(<W>) {
  if ($tty eq substr($_,9,2)) {
    $ip = substr($_,12,14);
    last;
    }
  }
close(W);
if (substr($ip,0,8) ne '129.94.1') {
  die "can't find the internet address for this X-terminal
  sorry, I can't help!";
  }
print "\nyour IP address appears to be $ip\n";
open(PS, "/bin/ps -ax|grep \"dxsession $ip\"|") 
  || die "can't run ps!";
$dxsession_pid='0';
while (<PS>) {
  if (/usr/) {
    print "the terminal is being controlled by the 
      following process\n$_";
    $dxsession_pid=substr($_,0,5);
    $pid=$dxsession_pid;
    $pid=~s/ //g;
    open(PS2, "/bin/ps -$pid|grep \"dxsession $ip\"|") 
      || die "can't run ps!";
    if ($dxsession_pid ne substr(<PS2>,0,5)) {
      print "hmm... the process appears to have vanished...\n";
      last;
      }
    print "now trying to kill the process...\n";
    kill 15, $dxsession_pid;
    sleep(5);
    kill 9, $dxsession_pid;
    last;
  }
}
close(PS);
close(PS2);
if ($dxsession_pid eq '0') {
  print "can't find the controlling dxsession for this X-terminal
maybe the problem is a stuck Xprompter ...\n";
  }
$Xprompter_pid = '0';
open(PS3, "/bin/ps -ax|grep \"/bin/Xprompter -e $ip\"|") 
  || die "can't run ps!";
while (<PS3>) {
  if (/usr/) {
    print "the terminal's Xprompter is being controlled 
      by the following process\n$_";
    $Xprompter_pid=substr($_,0,5);
    $pid=$Xprompter_pid;
    $pid=~s/ //g;
    open(PS4, "/bin/ps -$pid|grep \"/bin/Xprompter -e $ip\"|") 
      || die "can't run ps!";
    if ($Xprompter_pid ne substr(<PS4>,0,5)) {
      print "hmm... the process appears to have vanished...\n";
      last;
    }
    print "now trying to kill the process...\n";
    kill 15, $Xprompter_pid;
    last;
    }
  }
close(PS3);
close(PS4);
print "OK, I've done all that I can, click Main Menu, followed by
Done; the DIGITAL login box should appear shortly. If it doesn't, try
the Restart Session button in the Main Menu. As a last resort, try
turning the terminal off, leaving it for 30 seconds, and turning it
back on.

DON'T FORGET TO LOG OUT OF THIS TELNET SESSION WHEN YOU HAVE
FINISHED!!!\n";

-------------------------------------------------------------------------------

This script has to run setuid to root, so I call it with the
following program

main(argc,argv)
int argc;
char **argv;
{
  execv("/usr/local/bin/resetxterm.pl",argv);
}

the executable of this program is in /usr/local/bin/resetxterm with 
a protection mask of 4711, and owned by root.

A remaining problem is that the command "w" doesn't work out the
process idle time correctly and you get crazy answers like 20 hours
even though you have only been logged in for half an hour.

That's all folks! I hope that this has been useful to someone. If you
have any suggestions on how to improve this setup, or can correct my
possible misconceptions, please let me know.

Michael Ashley mcba@newt.phys.unsw.oz.au
Astrophysics Dept. / Uni of NSW / Sydney, Australia

mcm@rti.rti.org (Mike Mitchell) (03/15/91)

In article <1214@usage.csd.unsw.oz.au> Michael Ashley wrote about his
promblems with Xterminals and dxsession.  We have just received some
software that turns VAXstation 2000's into Xterminals, and I've had
many of the same problems.  With the help of a home-grown system-call
tracing facility and a friend with Ultrix 3.1 source, I've found some
serious bugs in dxsession.

The first bug is that it calls 'getpgrp' with the wrong number of
arguments.  It calls 'getpgrp' with no arguments, which is fine
IF THE PROGRAM WAS COMPILED IN THE POSIX ENVIRONMENT (which it is NOT).

The next bug is that it calls 'setpgrp' with the wrong number of
arguments.  It calls 'setpgrp' with one argument, which is WRONG for
both SYSV and Berkeley forms of the call.

The next problem is that dxsession doesn't try hard enough to kill off
its children.  It only sends a SIGHUP to the process group, when it should
send a SIGHUP, wait, send a SIGTERM, wait, then send a SIGKILL.

Back to the BIG problems.  The original intention of calling 'getpgrp'
is to find out if dxsession is in process group 0 or 1.  If it dxsession
is in either of these groups, a new process group is entered by calling
'setpgrp'.  Later when the quit box is clicked, dxsession does a
'killpg(1, getpgrp());' and then exits.  Well, the 'if' statement that
checks for process group 0 or 1 does not check for an error return,
so if the 'getpgrp()' fails, dxsession assumes it is already in a
process group.  The 'getpgrp()' will fail, because the pid to search
for was NOT passed in, and the system call gets garbage from the stack.
If they had said 'getpgrp(0)', the code would be correct.

Now then, if the dxsession is in process group 0 or 1, the setpgrp call
gets executed.  It is of the form 'setpgrp(getpid())'.  Under SYSV, 
setpgrp takes no arguments, and under Berkeley, it takes two.  This has
the desired effect I guess, as the process group of process id 'getpid()'
will be set to some random value that was left on the stack.  However,
if there is more than one dxsession running on your system, it is very
likely they took the same path to get to the 'setpgrp' call, so the
same value is probably on the stack.  The net result is that everyone
is running in the same process group.  This explains why when one person
is logged in twice and logs out in one window, both windows get killed.

To fix these problems, I put a wrapper program around the invocation
of dxsession.  This program forks, the child puts itself in its own
process group, and then execs dxsession.  The parent waits for dxsession
to terminate, then it does a killpg with a SIGTERM.  Five seconds later
it does a killpg with a SIGKILL, then it exits.  I'm including my scripts
and program below.

In my /etc/ttys file I have:

    xterm1:0 "/local/etc/startwindow xterm1::0" none on secure

Init will pass to the startwindow script the 'xterm1::0' argument.
The startwindow script looks like:

    #! /bin/sh
    #
    # `login' needs an extra argument as a tty name.  Without it, getttyname
    # dumps core.  Luckily, it passes the name in to the Xprompter, and the
    # Xprompter uses that as a display name.
    #
    DISPLAY=${1}
    export DISPLAY
    exec /usr/bin/login -P /usr/bin/Xprompter -C /local/etc/dxsession ${DISPLAY}

The whole reason for the script is to set the environment variable for the
display.

Our version of dxsession is again a script, which contains the following:

    #! /bin/sh
    exec /local/etc/newpgrp /usr/bin/dxsession ${1+"$@"}

And the program 'newpgrp' is the following:
    /*
     * This program forks and execs its argument list.  It puts
     * the child in its own process group.  It waits for the
     * child to terminate, and then sends a SIGTERM to the
     * process group.  Five seconds later it sends a SIGKILL
     * to the process group.  The parent exits with the
     * same status as the original child.
     *
     * The purpose of this program is to put an 'Xsession' program
     * in a process group, then kill off all of the clients when the
     * 'Xsession' program terminates.  This is used to support the
     * Xterminal software on the VaxStation 2000s.
     */
    #include <stdio.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <sys/wait.h>

    main(argc, argv)
    int argc;
    char *argv[];
    {
	int i, rc;
	long s;					/* wait status */

	if (argc > 1)
	{
	    if ((i = fork()) == 0)
	    {
		i = getpid();
		setpgrp(i, i);			/* set process group to pid */
		execvp(argv[1], &argv[1]);
		perror(argv[1]);		/* couldn't exec, tell why */
		exit(127);
	    }

	    /* wait for child to terminate */
	    while((rc = wait(&s)) > 0)
	    {
		/*
		 * if the pid is the one we are waiting for, and
		 * it is not stopped, break out of the loop.
		 */
		if (rc == i && !WIFSTOPPED(s))
		    break;
	    }

	    /*
	     * Send a SIGTERM, and if that succeeds, send a SIGKILL
	     */
	    if (killpg(i, SIGTERM) == 0)
	    {
		sleep(5);
		(void) killpg(i, SIGKILL);
	    }

	    /*
	     * if the child exited due to a signal, signal
	     * ourselves with the same signal.  Hopefully
	     * we will exit the same way.
	     */
	    if (WIFSIGNALED(s))
		(void) kill(getpid(), WTERMSIG(s));

	    /*
	     * Now exit with the same exit status as the child
	     */
	    exit(WEXITSTATUS(s));
	}

	/*
	 * Here there were no arguments, so exit with an error code.
	 */
	exit(127);
    }

-----------------------------------------------------------

	Mike Mitchell					mcm@rti.rti.org
							uunet!rti!mcm
"There's laughter where I used to see a tear.		(919) 541-6098
 It's all done with mirrors, have no fear."
-- 
	Mike Mitchell					mcm@rti.rti.org
							uunet!rti!mcm
"There's laughter where I used to see a tear.		(919) 541-6098
 It's all done with mirrors, have no fear."