From: lwall@netlabs.com (Larry Wall)
Newsgroups: comp.lang.perl
Subject: Re: perl monitor
Date: 7 Oct 92 17:25:47 GMT
Article-I.D.: netlabs.1992Oct7.172547.20463
Organization: NetLabs, Inc.

In article <simon.718381184@giaeb> simon@giaec.cc.monash.edu.au (simon shields) writes:
: 
: Hi
: I'm running on a 
: HP-UX giaec A.08.07 A 9000/720 2001133857
: I want to write a simple monitor perl script and was wondering if
: anyone here could give me a kick start.
: 
: It has to be transparent to the user. It simply records the commands
: typed in and files a report when the user logs off. It reports stats
: like, the commands used, frequency of use, most frequently used
: command, the time the user was logged on for etc..

To do that sort of thing you have to let the user run on a pseudo-terminal
and capture the input.  You could do this yourself (chat2.pl might give
you some ideas how to start), or you could run the script program (if
available) and strain the output of that, presuming you could recognize
the prompt and extract the command after it:

    #!/usr/bin/perl

    system("script /tmp/scriptout$$");

    open(SCRIPT, "/tmp/scriptout$$");
    while (<SCRIPT>) {
	next unless s/^$ //;		# XXX presuming bourne shell prompt
	($cmd) = split(' ');
	$count{$cmd}++;
    }

    foreach $cmd (sort keys %count) {
	printf "%6d  %s\n", $count{$cmd}, $cmd;
    }

    unlink "/tmp/scriptout$$";

This can spoofed by programs that output things looking like prompts.
It's a pity you can't get script to log just the commands that are
typed, and not the output.  I'd probably end up rolling my own.
The following is close to what you want, but you'll have to fiddle
with stty modes a bit more than I want to do at the moment:

#!/usr/bin/perl

$next = "aa";
$handle = &open_proc("/bin/sh");
if ($child = fork) {
    system("stty raw");
    while (sysread($handle,$foo,1024)) {
	print $foo;
    }
    system("stty cooked");
    kill TERM, $child;
}
else {
    open(LOG, ">/tmp/log$$");
    while (sysread(STDIN, $chars, 1024)) {
	print LOG $chars;
	print $handle $chars;
    }
}
exit;

# ($pty,$tty) = $chat'_getpty(PTY,TTY):
# internal procedure to get the next available pty.
# opens pty on handle PTY, and matching tty on handle TTY.
# returns undef if can't find a pty.

sub getpty { ## private
	local($_PTY,$_TTY) = @_;
	$_PTY =~ s/^([^']+)$/(caller)[$[]."'".$1/e;
	$_TTY =~ s/^([^']+)$/(caller)[$[]."'".$1/e;
	local($pty,$tty);
	for $bank (112..127) {
		next unless -e sprintf("/dev/pty%c0", $bank);
		for $unit (48..57) {
			$pty = sprintf("/dev/pty%c%c", $bank, $unit);
			open($_PTY,"+>$pty") || next;
			select((select($_PTY), $| = 1)[0]);
			($tty = $pty) =~ s/pty/tty/;
			open($_TTY,"+>$tty") || next;
			select((select($_TTY), $| = 1)[0]);
#			system "stty nl>$tty";
			return ($pty,$tty);
		}
	}
	undef;
}

## $handle = &chat'open_proc("command","arg1","arg2",...);
## opens a /bin/sh on a pseudo-tty

sub open_proc { ## public
	local(@cmd) = @_;

	*S = ++$next;
	local(*TTY) = "__TTY" . time;
	local($pty,$tty) = &getpty(S,TTY);
	die "Cannot find a new pty" unless defined $pty;
	local($pid) = fork;
	die "Cannot fork: $!" unless defined $pid;
	unless ($pid) {
		close STDIN; close STDOUT; close STDERR;
		setpgrp(0,$$);
		if (open(DEVTTY, "/dev/tty")) {
		    ioctl(DEVTTY,0x20007471,0);		# XXX s/b &TIOCNOTTY
		    close DEVTTY;
		}
		open(STDIN,"<&TTY");
		open(STDOUT,">&TTY");
		open(STDERR,">&STDOUT");
		die "Oops" unless fileno(STDERR) == 2;	# sanity
		close(S);
		exec @cmd;
		die "Cannot exec @cmd: $!";
	}
	close(TTY);
	$PID{$next} = $pid;
	$next; # return symbol for switcharound
}

Larry