Script(1) for System 5

Dennis Bednar dennis at rlgvax.UUCP
Fri Jul 4 12:15:34 AEST 1986


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 at 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



More information about the Comp.sources.unix mailing list