

	SCRIPT Version 1.1 - Save console output to a file

I hereby release this program into the public domain. Do with
it as you please. I would appreciate it if my name were left
in the source code somewhere.

DISCLAIMER:

    I have had no problems with this program to date, but I have
no doubt that there are TSR's and other beasties lurking out there that
will interact in weird and wonderful ways with this program.
I accept no responsibility for any trashed FAT's or otherwise,
that might result from a war of the above sort. I have tested the
program only under MS-DOS 3.2.

WHAT IT DOES:

    SCRIPT is similar to the UNIX program of the same name.
While running SCRIPT, output that appears on the console,
will also be saved into a file for later perusal.

USAGE:

    SCRIPT is called from the DOS command line thusly:

	"script [-f outputfile] [-a] [command]"

    Output is saved into the file "outputfile" if is given. Default
is the file "typescript" in the current directory. If the "-a"
option is given, SCRIPT appends to "outputfile" rather than
truncating it first.

    SCRIPT optionally takes a command as argument. If one is given,
this command is executed rather than a copy of "command.com". If
"command" is a command.com builtin, it must be given as argument to
"command /c". An example usage is:

	script -f junk command /c echo hello world

SCRIPT terminates when the command running under it terminates.

PROGRAM OPERATION:

    SCRIPT works by intercepting the DOS function call interrupt
"int 21". When a program running under SCRIPT executes an "int 21",
SCRIPT examines the DOS function call to determine whether it will
result in output going to the console. If such is the case, SCRIPT
will save this output in an internal buffer and pass the call on to
the real DOS so that it does the actual console output. When the
internal buffer is full, SCRIPT flushes it to disk. To do this,
SCRIPT uses the undocumented DOS function call 50h - SETPSP.
I have no idea which versions of DOS support this function call.

NOTES:

   SCRIPT keeps the output file open throughout it's entire operation.
If a chkdsk is run while SCRIPT is active, it may report lost clusters
and other such things. Do not reclaim these clusters; they probably
belong the SCRIPT's output file.

   Do not install any TSR's while running script.
   
   Do not forget that script is running. If you do, you may run out
of disk space fairly quickly. You can tell whether script is active
by trying to run it again. If it was active, the new invocation
will abort with an error message.

BUGS:

    On output, SCRIPT first writes all the data to the output
file, and then chains to DOS so that DOS does the actual console
output. If a ^C is hit while this console output is taking place,
console output is stopped. However this data has already been
written to the output file. The result is that more data can appear
in the output file than actually appeared on the screen.

    If a ^C is typed while a program running under SCRIPT is waiting
for input, SCRIPT can become confused and may stop saving output
to the output file. See the source code for an explanation of this
phenomenon.

    If a program outputs to the console by calling the BIOS,
or by writing directly to the hardware, this output will not
be saved in the output file. There are probably even ways of
doing output through DOS that SCRIPT does not handle properly.

COMPILING:

    SCRIPT was written using Turbo C version 2.0 and TASM 1.0.
    A makefile is provided, although it may not work with Borlands
    make program.

Suggestions and Bug reports will be much appreciated.
graham@sce.carleton.ca (Doug Graham)


=====================================================================
Script 11 MAKEFILE :

#
# Makefile for script V1.1 using Turbo C 2.0
# I don't think this makefile will work with the make utility
# provided with Turbo C. Use a better one.
#
AS = tasm
ASFLAGS= /mx
CC= tcc
CFLAGS= -mt -M
LIBDIR= \tc\lib

script.com:	script.obj int21.obj
	tlink ${LIBDIR}\c0t script int21 /t/c/m,script,script,${LIBDIR}\cs.lib

=====================================================================
Script 11 INT21.ASM :

	name	int21
_text	segment	byte public 'code'
dgroup	group	_data,_bss
	assume	cs:_text,ds:dgroup
_text	ends
_data	segment word public 'data'
_d@	label	byte
_data	ends
_bss	segment word public 'bss'
_b@	label	byte
_bss	ends

;
; I switch to my own stack when handling an int 21 request on behalf
; of a process. This is done because the amount of stack available to
; the calling process is not known. The flag "onintstack" indicates
; whether this special stack is currently in use so that we do not
; inadvertantly try to use it again. If an int 21 call is made while
; this stack is in use (as can occur for instance when we are flushing
; a buffer to disk, or calling ioctl to find out whether the current
; handle refers to the console), we do not call the C function
; "int21handler", but rather chain to DOS immediately.
;
_bss	segment word public 'bss'
	db	512 dup (?)
intstack	label	byte
_bss	ends

_text      segment byte public 'code'
	public	_grab21, _rstr21, _getpsp, _setpsp, _getdosflag, _callDOS
	extrn	_int21handler:near

savesp		dw	?	; saved ss:sp of calling process
savess		dw	?
onintstack	dw	?

;
; psp = getpsp(void)
;
_getpsp proc near
	mov	ah, 51h
	int	21h
	mov	ax, bx
	ret
_getpsp endp

;
; void setpsp(psp)
;
; This function uses undocumented DOS function 50h
;
_setpsp proc near
	push	bp
	mov	bp, sp
	mov	bx, [bp+4]
	mov	ah, 50h
	int	21h
	pop	bp
	ret
_setpsp endp

;
; char far *dosflag = getdosflag(void);
;
; Returns a pointer to the "indos" flag. This is used to prevent
; any attempt to re-enter DOS.
;
_getdosflag proc near
	mov	ah,34h
	int	21h
	mov	ax, bx
	mov	dx, es
	ret
_getdosflag endp

;
; int grab21(void)
;
; Intercept DOS function calls by installing our own
; int 21 handler. Zero is returned if the current int 21
; handler has the same offset as the one being installed.
; This is used as a quick check to see if script
; is already active. It is far from a foolproof check,
; for instance it is possible that the DOS interrupt
; handler has the same offset as our own handler in which
; case this function would indicate that script was already
; active when in fact it was not. The probability is reasonably
; small that this won't occur.
;
_grab21 proc near
	mov	ax, 3521h			; save current int 21 handler
	int	21h
	mov	word ptr cs:oldvct21, bx
	mov	word ptr cs:oldvct21+2, es
	mov	dx, offset int21		; set up our int 21 handler
	cmp	bx, dx
	je	nogo
	mov	ax, 2521h
	int	21h
	mov	cs:onintstack,0
	mov	ax,1
	ret
nogo:
	mov	ax,0
	ret
_grab21 endp

;
; rstr21()
;
; Repair the damage done above before exiting.
;
_rstr21 proc near
	mov	cs:onintstack,1
	push	ds
	lds	dx, dword ptr cs:oldvct21
	mov	ax, 2521h
	int	21h
	pop	ds
	ret
_rstr21 endp

;
; The int 21 first level handler.
; This function sets up a stack, pushes the processor registers
; on this stack, and then calls C. In order that the C handler
; can make DOS calls without recursing, the flag "onintstack"
; is set. If the following ISR is called when this flag is set,
; it will not call the C handler again, but instead will chain
; immediately to DOS
;
int21	proc	far
	cmp	cs:onintstack, 0
	jne	chain
	mov	cs:onintstack, 1
	mov	word ptr cs:savesp, sp		; switch stacks
	mov	word ptr cs:savess, ss
	push	cs
	pop	ss
	mov	sp, offset dgroup:intstack
	sti
	cld
;
	push	es				; Save regs on the new stack.
	push	ds				; The C function "int21hander"
	push	dx				; expects the registers
	push	cx				; in this order.
	push	bx				; They must look like a
	push	ax				; "union MYFRAME"

	push	cs
	pop	ds

	call	near ptr _int21handler
	mov	ah,al
	sahf
	pop	ax
	pop	bx
	pop	cx
	pop	dx
	pop	ds
	pop	es
;
	mov	cs:onintstack, 0
	mov	ss, word ptr cs:savess ; Restore the original stack
	mov	sp, word ptr cs:savesp

	je	chain		; Nothing between the sahf above, and here
				; is allowed to affect the flags.
	ret	2		; Carry flag still set from sahf above.
chain:
	db	0EAh	; opcode for FAR JUMP
oldvct21	dd	?
int21	endp

;
; flags = callDOS(regp)
; union MYFRAME *regp;
;
; This function loads the registers from "regp", executes an int 21
; and then copies the modified registers back into "regp"
; The processor flags are returned as the value of the function
;
; This function is currently used to read console input on behalf
; of a process and is called while the special stack is in use.
; This means that "onintstack" is set. A problem arises if the
; user types ^C in response to this input request. In this case,
; the calling process is aborted by DOS and never returns to this
; procedure. This means that we never get a chance to clear
; "onintstack". The effects of this are that no further output
; will be written to the output file. A possible solution may
; be to chain to the termination vector (int 22h), but this would
; have to done every time "callDOS" is called and not just once
; when script starts up, because DOS fiddles with this vector
; whenever a new process is created, or an old one destroyed.
;
_callDOS proc near
	push	bp
	mov	bp,sp
	mov	bx,[bp+4]

	mov	es, [bx+10]
	mov	ax,[bx+8]
	push	ax
	mov	dx, [bx+6]
	mov	cx, [bx+4]
	mov	ax, [bx+0]
	mov	bx, [bx+2]
	pop	ds

	pushf				; Fake interrupt
	call oldvct21
	pushf				; Preserve flags from DOS

	push	ds
	push	cs
	pop	ds
	push	bx
	mov	bx,[bp+4]
	mov	[bx+0],ax
	pop	[bx+2]
	mov	[bx+4],cx
	mov	[bx+6],dx
	pop	[bx+8]
	mov	[bx+10],es

	pop	ax			; Return the flags from the DOS call
	pop	bp
	ret
_callDOS endp

;
; void _Getdate(struct date *)
;
public _Getdate
_Getdate	proc near
	push	bp
	mov	bp,sp
	mov	ah, 2Ah
	int	21h
	mov	bx,[bp+4]
	mov	[bx],cx
	mov	[bx+2],dx
	mov	[bx+4],al
	pop	bp
	ret
_Getdate	endp

;
; void _Gettime(struct time *)
;
public _Gettime
_Gettime	proc near
	push	bp
	mov	bp,sp
	mov	ah, 2Ch
	int	21h
	mov	bx,[bp+4]
	mov	[bx],dx
	mov	[bx+2],cx
	pop	bp
	ret
_Gettime	endp

_text	ends


_data	segment word public 'data'
_s@	label byte
_data	ends

	end

=====================================================================

