home *** CD-ROM | disk | FTP | other *** search
- /*
- * Script for MSDOS
- * Version 1.1
- * Written Nov 1987 by graham@sce.carleton.ca (Doug Graham)
- *
- * This program is similar to the UNIX command of the same name.
- * When running it, output that appears on the console, will also
- * be saved into a file for later perusal.
- *
- * usage is: "script [-f outputfile] [-a] [command]"
- *
- * default outputfile is "typescript" in the current directory.
- *
- * -a means to append to outfile.
- *
- * Script optionally takes a command as argument. If one is given,
- * this command is executed rather than "command.com".
- * This saves having an extra copy of command.com wasting space
- * in memory.
- *
- * BUGS:
- *
- * 1) 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.
- *
- * 2) On input, script calls DOS using it's own stack and with
- * the flag "onintstack" set. (See int21.asm) If a ^C is typed
- * in response to the input request, the calling program is
- * aborted leaving "onintstack" set. This causes script to stop
- * saving output to the output file.
- */
-
- /*
- * Modification History:
- * Sept 2/89 Doug Graham.
- * Modified to work with Turbo C 2.0 compiler.
- * Also fiddled it a bit to reduce size.
- */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <fcntl.h>
- #include <sys/stat.h>
- #include <process.h>
- #include <dos.h>
- #include <time.h>
-
- static int fd;
- static int mypsp;
- static int criterr_occurred;
- static char far *dosflag;
- extern char far *getdosflag();
-
- /*
- * It might be that heaplen should be increased if
- * there is a lot of stuff in the environment, because the TC startup
- * routines mallocate space for both the environment, and the command
- * line arguments.
- */
- unsigned _heaplen = 1000;
- unsigned _stklen = 1000;
-
- main(argc, argv)
- char **argv;
- {
- int oflags = O_WRONLY|O_CREAT|O_TRUNC|O_TEXT;
- char *ofile = "typescript";
- char *command;
- char *GetTime();
-
- for (--argc,++argv; argc && (argv[0][0] == '-'); --argc,++argv) {
- switch(argv[0][1]) {
- case 'f':
- if (! --argc)
- usage();
- ofile = *++argv;
- break;
- case 'a':
- oflags = (oflags & ~O_TRUNC) | O_APPEND;
- break;
- default:
- usage();
- }
- }
-
- command = argc ? *argv : getenv("COMSPEC");
- if (! command)
- command = "command";
-
- if ((fd = open(ofile, oflags, S_IREAD|S_IWRITE)) < 0) {
- mprintf("Can't open %s for writing\r\n", ofile);
- exit(1);
- }
-
- dosflag = getdosflag();
- mypsp = getpsp();
-
- if (! grab21()) {
- mprintf("I think script is already active\r\n");
- exit(1);
- }
-
- mprintf("Script V1.1 session started %s\r\n", GetTime());
-
- if (spawnvp(P_WAIT, command, argv) == -1)
- mprintf("Can't execute %s\r\n", command);
-
- mprintf("Script completed %s\r\n", GetTime());
-
- rstr21();
- flushbuf();
- close(fd);
-
- mprintf("Output file is %s\r\n", ofile);
- exit(0);
- }
-
- usage()
- {
- mprintf("usage: script [-f outputfile] [-a] [command]\r\n");
- exit(1);
- }
-
- #define BUFFERSIZE 4096
-
- static char buffer[BUFFERSIZE];
- static int bufslots = BUFFERSIZE;
- static char *bufp = buffer;
-
- #define _putc(c) \
- {*bufp++ = c; if (! --bufslots) flushbuf();}
-
- /*
- * When flushing the buffer to disk, we use the undocumented DOS function
- * 50h (set PSP) so that script's file handles are used rather
- * than the the calling program's. I'm not sure in which versions
- * of DOS this function exists.
- *
- * Control break checking is turned off in case the user types a
- * ^C just as we are about to do the write to disk. Since we switched
- * PSP's, DOS thinks we are the active program, and if ^C is typed
- * with break checking enabled, it is us that will be aborted rather
- * than the program running under us. This causes major havoc.
- * The DOS critical error vector is intercepted for the same reason.
- * Our handler simply sets a flag if an error occurs; this flag is
- * checked at a later time.
- */
- struct tcframe {int bp, di, si, ds, es, dx, cx, bx; unsigned char al, ah;};
-
- void interrupt
- my_criterr_handler(regs)
- struct tcframe regs;
- {
- criterr_occurred = 1;
- regs.al = 0; /* Zero means ignore the error. */
- }
-
- #define CRITERR_VECT 0x24
-
- flushbuf()
- {
- int hispsp;
- int hiscbrk;
- void interrupt (* old_criterr_handler)();
-
- hiscbrk = getcbrk();
- setcbrk(0);
- old_criterr_handler = getvect(CRITERR_VECT);
- setvect(CRITERR_VECT, my_criterr_handler);
- hispsp = getpsp();
- setpsp(mypsp);
- criterr_occurred = 0;
-
- _write(fd, buffer, bufp - buffer);
- bufslots = BUFFERSIZE;
- bufp = buffer;
-
- setpsp(hispsp);
- setvect(CRITERR_VECT, old_criterr_handler);
- setcbrk(hiscbrk);
- if (criterr_occurred)
- mprintf("\r\n\r\nSCRIPT: WARNING: disk write failed\r\n\r\n");
- }
-
- #define isconsole(handle) ((ioctl(handle, 0) & 0x82) == 0x82)
-
- union MYFRAME {
- struct {unsigned int ax, bx, cx, dx, ds, es;} x;
- struct {unsigned char al, ah, bl, bh, cl, ch, dl, dh;} h;
- };
-
- /*
- * DOS output functions. There are probably more, but these seem
- * to do the job for me.
- */
- #define CHAR_OUT 0x02
- #define DIRECT_OUT 0x06
- #define STRING_OUT 0x09
- #define WRITE_FILE 0x40
-
- /*
- * DOS input functions. The input functions which also echo the
- * input to the console must be intercepted, because otherwise
- * this echo output would not be saved in the script file. There
- * are probably more of these type of functions, but my documentation
- * is not clear on which functions echo, and which do not, and I
- * don't have the patience to go through and try each one.
- */
- #define CHAR_IN_ECHO 0x01
- #define READ_FILE 0x3F
- #define BUFFERED_INPUT 0x0A
-
- /*
- * Returning the ZERO_FLAG to the first level handler tells
- * it to chain to the old int 21 handler. If this bit is
- * not set in the returned value, no such chaining occurs.
- */
- #define ZERO_FLAG 0x40
- #define CARRY_FLAG 0x01
-
- /*
- * Called by the assembly language first level handler. The first level handler
- * first switches stacks, builds a stack frame that looks "union MYFRAME"
- * and then calls "int21handler".
- */
- unsigned
- int21handler(regs)
- union MYFRAME regs;
- {
- unsigned char far *fp;
- int c, len, flags;
-
- /*
- * A bit of paranoia below. Since this function is only called
- * when a program is trying to call DOS, it could possibly be
- * safely assumed that it is not already in DOS. Just to be
- * sure, I check the undocumented "indos" flag. It is important
- * that nobody is in DOS when this procedure executes because
- * it calls DOS itself, and DOS is not re-entrant.
- */
- if (*dosflag)
- return (ZERO_FLAG);
-
- switch (regs.h.ah) {
- case CHAR_OUT:
- if (isconsole(1))
- _putc(regs.h.dl);
- break;
- case DIRECT_OUT: /* This ones a real crock!! */
- if ((isconsole(1)) && (regs.h.dl != 0xFF))
- _putc(regs.h.dl);
- break;
- case STRING_OUT:
- if (isconsole(1)) {
- fp = (unsigned char far *)MK_FP(regs.x.ds, regs.x.dx);
- while ((c = *fp++) != '$')
- _putc(c);
- }
- break;
- case WRITE_FILE:
- if (isconsole(regs.x.bx)) {
- fp = (unsigned char far *)MK_FP(regs.x.ds, regs.x.dx);
- for (len = regs.x.cx; len--; )
- _putc(*fp++);
- }
- break;
- case READ_FILE:
- if (isconsole(regs.x.bx)) {
- fp = (unsigned char far *)MK_FP(regs.x.ds, regs.x.dx);
- flags = callDOS(®s);
- if (! (flags & CARRY_FLAG))
- for (len = regs.x.ax; len--; )
- _putc(*fp++);
- return (flags & ~ZERO_FLAG);
- }
- break;
- case CHAR_IN_ECHO:
- if (isconsole(0)) {
- flags = callDOS(®s);
- _putc(regs.h.al);
- return (flags & ~ZERO_FLAG);
- }
- break;
- case BUFFERED_INPUT:
- if (isconsole(0)) {
- flags = callDOS(®s);
- fp = (unsigned char far *)MK_FP(regs.x.ds, regs.x.dx);
- for (len = *++fp; len--; )
- _putc(*++fp);
- return (flags & ~ZERO_FLAG);
- }
- break;
- }
- return (ZERO_FLAG);
- }
-
- /*
- * Use my own printf in order to save a couple of Kbytes in the executable.
- * The real one will work if necessary. This should be using varargs and
- * vsprintf.
- */
- mprintf(fmt, a1)
- char *fmt, *a1;
- {
- char lbuf[128];
-
- _write(1, lbuf, sprintf(lbuf, fmt, a1));
- }
-
- #if 0
-
- char *GetTime()
- {
- long tyme;
-
- time(&tyme);
- return (ctime(&tyme));
- }
-
- #else
-
- /*
- * Use my own gettime, in order to save about 4K in the output file.
- * Also had to write GetDate in assembler, because for some strange
- * reason, the Turbo C libarary routine getdate does not appear to return
- * the day of the week.
- */
- struct Date {
- int da_year;
- char da_day;
- char da_mon;
- char da_weekday;
- };
-
- struct Time {
- char ti_hund;
- char ti_sec;
- char ti_min;
- char ti_hour;
- };
-
- char Days[] = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat\0???\0";
- char Mons[] = "???\0Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec\0";
-
- char *GetTime()
- {
- static char timebuf[32];
- struct Date d;
- struct Time t;
-
- Getdate(&d);
- Gettime(&t);
-
- sprintf(timebuf, "%s %s %02d %02d:%02d:%02d %4d",
- &Days[(d.da_weekday & 0x7) << 2],
- &Mons[d.da_mon << 2],
- d.da_day,
- t.ti_hour,
- t.ti_min,
- t.ti_sec,
- d.da_year);
- return (timebuf);
- }
-
- #endif
-