[net.sources] Script

dennis@rlgvax.UUCP (Dennis Bednar) (07/04/86)

Here is a version of the 4.2 BSD script(1) for System 5.
ONLY THIS VERSION DOESN'T WORK COMPLETELY. READ ON.

This version works marginally, but has two giant problems:

If you run a program such as an editor, which expects to
do ioctl()'s to condition the terminal, script will fail.
The reason is that the shell's file descriptor 0 is attached
to the end of a UNIX pipe, not to a /dev/tty.  Weird stuff
happens at this point.

And when you hit interrupt, script stops.

Anyway this tool can be useful for saving the output of
*simple* commands, and provided you don't hit DELETE.

To stop script, type ^D or hit Interrupt.

Any solutions or ideas on how to fix these problems are welcome.

PS, I call this version scriptd, because here at rlgvax we
already have script, and I didn't want a name conflict.  I
wrote this version for fun and for self-education.

-dennis bednar

#--------------- CUT HERE ---------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	scriptd.c
# This archive created: Thu Jul  3 22:07:33 EDT 1986
#
if test -f scriptd.c
then
echo shar: will not over-write existing file 'scriptd.c'
else
echo x - scriptd.c
# ............    F  I   L   E      B  E  G  .......... scriptd.c
cat << '\SHAR_EOF' > scriptd.c
/*
 * scriptd.c
 * Public domain script.c command.
 * Dennis Bednar
 * rlgvax!dennis (dennis@rlgvax.UUCP)
 * June 10 1986
 *
 * DOESN'T WORK: some applications which run under the shell and
 * do ioctl()'s to fd 0, 1, or 2 are really trying to do an ioctl
 * to a regular old file descriptor (for the end of a pipe), which
 * unfortunately doesn't work.
 * Examples: stty, ev, pro.
 *
 * ALSO the interrupt key is fielded by the shell, and he dies (at
 * least I think).
 *
 *
 *	KB input   ---> pipe2sh  --->  shell input
 *	SCR output <--- pipe2tty <---  shell output
 *		      /
 *	typescript <-/
 *	  file
 *
 * There are 3 processes:
 *	kbinput() reads from keyboard input until EOF,
 *		and passes through a pipe (pipe2sh) to the shell input.
 *		This process terminates when EOF is typed in.
 *	shell_proc() a UNIX shell that is reading from one pipe, and writing
 *		to another pipe.  This process terminates when EOF
 *		is read from pipe2sh, because kbinput() has gone away.
 *	kboutput() reads from pipe2tty, and writes both to the screen,
 *		and to a file. Because the shell is echoing, the file
 *		captures both input and output.  This process terminates
 *		when EOF is read because the shell has gone away.
 *
 * Note currently that the 'typescript' output file is written to by
 * 2 processes (kbinput() and kboutput()).
 * The output to the 'typescript' outfd file descriptor
 * is raw (ie uses write(), not fwrite()).  This is because there are two
 * processes competing to write to the same file.  Because of the fork(),
 * both processes are appending to the very end of the same typescript file
 * without interference (neat UNIX trick, huh?).
 *
 *
 * Also note that the kbinput() task is echoing, *NOT* the
 * shell.  If you thing about it, its not the shell that normally
 * echoes your commands when you type, its the tty driver.
 *
 *
 */

#include <stdio.h>
#include <errno.h>
#include <signal.h>

/* globals */
char	*cmd;		/* name of argv[0] for error messages */
int	pipe2sh  [2];	/* pipe from kb input to shell input */
int	pipe2tty [2];	/* pipe from sh output to screen output plus file */
char	*filename = "typescript";
int	filfp;		/* for above output file */
char	*shellname = "/bin/sh";

/* int pipefd[2] indices */
#define	P_READ	0
#define	P_WRITE	1

/* forward non-int functions */
char	*u_errmesg();	/* error string for perror() like error messages */

extern	char	*getenv();

main(argc, argv)
	int	argc;
	char	*argv[];
{
	char	*cp;

	/* save the command name in case of error */
	cmd = argv[0];

	/* if SHELL is an enviroment variable, use it, else use default */
	if ( (cp = getenv("SHELL")) != NULL)
		shellname = cp;


	/* the output file MUST be unbuffered, otherwise stdio causes
	 * collision of arbitrary() lower level writes.
	 */
	filfp = creat(filename, 0660);
	if (filfp == -1)
		{
		fprintf(stderr, "%s: %s: %s\n", cmd, filename, u_errmesg());
		exit(1);
		}

	/* create the 2 single directional pipes */
	(void) makepipe( pipe2sh );
	(void) makepipe( pipe2tty );

	/* at this point there will be 0,1,2, plus 4 pipe file descriptors,
	 * plus one file descriptor for the 'typescript' file.
	 */

	/* start the 3 tasks */
	/* the first two routines fork() a child.  The third routine
	 * loops until EOF.
	 */
	printf("Script started, file is typescript\n");
	spawn_in();
	signal(SIGINT, SIG_IGN);	/* so shell doesn't die on INTERRUPT */
	spawn_sh();
	task_output();
}

/*
 * create a unix pipe.  Added a little error checking.
 */
makepipe(pipefd)
	int	pipefd[];
{
	register	int	rtn;

	rtn = pipe( pipefd );
	if (rtn == -1)
		{
		fprintf( stderr, "%s: Cannot make interprocess channel (pipe): %s\n", cmd, u_errmesg());
		exit(1);
		}
}

/*
 * The output task reads the pipe2tty pipe (output from the shell),
 * and writes the same to both stdout, and to the 'typescript' file.
 */
task_output()
{
	register	int	in,
				out,
				numread,
				numwrote;
	char	buf [20];

	/* close unused fd's */
	close(0);
	out = 1;			/* keep 1 open */
	close(2);
	close( pipe2sh[ P_READ ] );
	close( pipe2sh[ P_WRITE ] );
	in = pipe2tty[ P_READ ];	/* keep pipe2tty[ P_READ ] open */
	close( pipe2tty[ P_WRITE ] );

	/* copy output from shell to stdout and to the file */
	while ( (numread = read(in, buf, sizeof(buf))) > 0)
		{
		write(out, buf, numread);
		write(filfp, buf, numread);
		}

	close(filfp);
	printf("Script done, file is typescript\n");
	exit(0);
}

/*
 * spawn input task.
 * create an input child task that reads from the keyboard, and writes
 * through the pipe2sh pipe.
 */
spawn_in()
{
	int	child;
	int	infd,
		outfd;

	child = do_fork();
	if (child < 0)
		{
		fprintf(stderr, "%s: spawn_in could not fork: %s", cmd, u_errmesg());
		exit(1);
		}
	if (child > 0)
		return;			/* parent returns */
	infd = 0;			/* 0 is input */
	close(1);
/*	close(2); */
	close( pipe2sh[ P_READ ] );
	outfd = pipe2sh[P_WRITE];	/* kept open */
	close( pipe2tty[ P_READ ] );
	close( pipe2tty[ P_WRITE ] );
/*	fixtty();				/* must be in raw mode */

	/* read from kb write to shell, and to file */
	do_copy(infd, outfd);		/* child does input task */
}


/*
 * copy task from fd 'in' to fd 'out'
 * PLUS write to the 'typescript' file.
 * This copy loop is used both by kb input, and screen output.
 */
do_copy(in, out)
	int	in;	/* fd for tty input */
	int	out;	/* fd for output to shell */
{
	char	buf[20];
	int	numread;
	int	numwrote;

	while ( (numread = read(in, buf, sizeof(buf)) ) > 0)
		{
		numwrote = write(out, buf, numread);
		if (numread != numwrote)
			exit(0);	/* stderr is closed */
		write(filfp, buf, numread);
		}

	exit(0);

}


/*
 * spawn a shell.  The shell is attached to 2 ends of UNIX pipes.
 */

spawn_sh()
{
	int	child;

	child = do_fork();
	if (child < 0)
		{
		fprintf(stderr, "%s: spawn_in could not fork: %s", cmd, u_errmesg());
		exit(1);
		}
	if (child > 0)
		return;		/* parent returns */

	/* close unused fd's */
	close(0);
	close(1);
	close(2);
	/* pipe2sh[ P_READ ] kept open */
	close( pipe2sh[ P_WRITE ] );
	close( pipe2tty[ P_READ ] );
	/* close(pipe2tty[P_WRITE]); */

	/* redirect all shell stdin, stdout, and stderr to the pipes */
	dup2( pipe2sh[ P_READ ], 0);
	dup2( pipe2tty [ P_WRITE ], 1);
	dup2( pipe2tty [ P_WRITE ], 2);

/*	do_copy(0, 1);		/* stub for now */

	/* the shell should die when it reads EOF, right?? */
	execl(shellname, "sh", "-i", (char *)0);
}



/*
 * can beef this up to retry in the future if desired
 */
do_fork()
{
	return fork();
}

/****** library routines ****/


/*
 * return basename of full path name
 */
char *
basename(path)
	char	*path;
{
	char	*cp;		/* general char pointer */
	char	*strrchr();

	if ((cp = strrchr(path, '/')) == NULL)	/* no rightmost slash */
		return path;
	else
		return cp+1;
}


/*
 * return UNIX error message associated with errno
 * more flexible than perror(3)
 */

char *
u_errmesg()
{
	extern	int	errno;
	extern	int	sys_nerr;
	extern	char	*sys_errlist[];
static	char	buffer[50];

	if (errno < 0 || errno >= sys_nerr)
		{
		sprintf( buffer, "errno %d undefined (%d=max)", errno, sys_nerr);
		return(buffer);
		}

	return( sys_errlist[errno] );
}
\SHAR_EOF
# ............    F  I   L   E      E  N  D  .......... scriptd.c
fi # end of overwriting check
# end of shell archive
exit 0
-- 
-Dennis Bednar
{decvax,ihnp4,harpo,allegra}!seismo!rlgvax!dennis	UUCP

sgt@alice.UucP (Steve Tell) (07/10/86)

>[ a program that takes the output from a shell session and makes 
>  a file of it]

Supposedly, System V, release 3 has the necessary things to
do this right. (Without the limitations cited in the original article)

Has anyone out there figured out in any detail exactly how one
uses the Streams system to accomplish things like this in SVR3?

(I'm working through the 1-foot high stack of manuals, but
 its going slowly)

Thanks



-- 
Steve Tell
AT&T Bell Laboratories, Murray Hill, NJ  (... is not responsible for
										any of these opinions)