doc@s.cc.purdue.edu (Craig Norborg) (06/19/87)
Here is the latest version of cmd from Carolyn Scheppner over at Commodore. The fixes/upgrades are best explained by Carolyn, so... >Cmd allows redirection of serial and parallel output (such as printer output) >to a file. It can be installed for single or multiple files. >This new version (v3) has a bug fix (now handles CMD_WRITE's of -1 length) >and also provides much faster file capture for programs that send >multiple small writes to printer, serial, or parallel. This is implemented >with a 2K output buffer and was suggested by Allen Norskog. > >carolyn # This is a shell archive. # Remove everything above and including the cut line. # Then run the rest of the file through sh. #----cut here-----cut here-----cut here-----cut here----# #!/bin/sh # shar: Shell Archiver # Run the following text with /bin/sh to create: # cmd.doc # cmd.with # cmd.asm # cmd.c # This archive created: Thu Jun 18 11:29:00 1987 # By: Craig Norborg (Purdue University Computing Center) cat << \SHAR_EOF > cmd.doc TITLE: Cmd.uu v3 (exec. and icon) - Redirects serial or parallel to file For those of you who have asked for the ability to capture printer output in a file... What follows are two uuencoded files for my Cmd.info and Cmd. Cmd redirects serial.device or parallel.device output to a file. This allows you to capture the printer output of many applications (including screen dumps) in a file. CLI Usage: [run] Cmd [-s] [-m] [-n] parallel|serial filename (or Cmd ? for help) Example: run Cmd -m -n parallel ram:pfile To end a multiple (-m) Cmd, CTRL-C, or use BREAK if run Workbench: Just double click. All args specified in .info ToolTypes. Workbench defaults: DEVICE=parallel FILE=ram:CMD_file SKIP=FALSE MULTIPLE=FALSE NOTIFY=FALSE To end a MULTIPLE Cmd, double-click CMD again Options (CLI or Workbench ToolTypes): -s (ToolTypes: SKIP=TRUE) If the -s (SKIP) option is specified, Cmd will ignore an initial short (less than 8 bytes) write to the device and not save that write in the file. This option is useful when capturing a screen dump because most of our drivers currently send a Reset string to the printer before the dump. The driver then delays so that subsequent data sent is not lost. When the dump is captured in a file, this programmatic delay is lost and the following bytes are endangered by the reset. In most cases, this option should prevent the Reset string from being stored in the Cmd file. You should be able to copy the resulting file directly to PAR: (or SER:) for a quick dump. -m (Tooltypes: MULTIPLE=TRUE) If the -m (MULTIPLE) option is not specified, the file is closed and the redirection removed when the device is closed by the application. With the -m option, Cmd remains installed and opens a new file (with .n extension) each time the device is opened. -n (Tooltypes: NOTIFY=TRUE) The -n (NOTIFY) option causes output of progress messages about the redirection. I like noisy utilities but many others don't, so it's an option. Cmd - Copyright (c) 1987 Commodore Business Machines - All Rights Reserved This program may be freely non-commercially redistributed. Only warranty is works fine for me --- Carolyn Scheppner This icon already has some default Tooltypes set up. Executable follows. SHAR_EOF cat << \SHAR_EOF > cmd.with FROM LIB:Astartup.obj, cmd.o, cmda.o TO cmd LIBRARY LIB:Amiga.lib,LIB:LC.lib SHAR_EOF cat << \SHAR_EOF > cmd.asm * cmda.asm --- assembler interface for Cmd.c * Carolyn Scheppner --- CBM 05/87 INCLUDE 'exec/types.i' XREF _AbsExecBase XREF _MyBeginIO XREF _MyClose XREF _RealClose XDEF _myBeginIO XDEF _myClose XDEF _myExpunge CODE _myBeginIO: movem.l d0-d7/a0-a6,-(a7) ;save registers move.l a1,-(a7) ;push ptr to io request jsr _MyBeginIO addq.l #4,a7 movem.l (a7)+,d0-d7/a0-a6 ;restore registers rts _myClose: movem.l d0-d7/a0-a6,-(a7) ;save registers move.l a1,-(a7) ;push ptr to io request jsr _MyClose addq.l #4,a7 movem.l (a7)+,d0-d7/a0-a6 ;restore registers move.l _RealClose,a0 ;continue to real Close jmp (a0) _myExpunge: moveq.l #0,d0 ;means unable to expunge rts ;keep changed device from being expunged END SHAR_EOF cat << \SHAR_EOF > cmd.c /* Cmd.c --- v3 --- Carolyn Scheppner CBM 05/87 * * Copyright (c) 1987 Commodore Business Machines - All Rights Reserved * This code may be freely non-commercially redistributed. * * Redirects exec serial or parallel device CMD_WRITEs to a file * (for the purpose of capturing printer output in a file) * Built upon fragments of Read (author?) and NoFastMem (Andy Finkel) * * CLI Usage: [run] cmd [-s] [-m] [-n] devicename filename * -s (Skip) skips any short initial write (usually a Reset if screendump) * -m (Multiple) causes cmd to remain installed for multiple files * -n (Notify) enables helpful progress messages * devicename serial or parallel * * WB Usage: Just doubleclick. * Specify the args in your icon's ToolTypes (use WB Info) * Built-in defaults are: * DEVICE=parallel * FILE=ram:CMD_file * SKIP=FALSE * MULTIPLE=FALSE * NOTIFY=FALSE * * Note: On a screen dump, first CMD_WRITE is usually a printer RESET. * The printer device then delays long enough for the reset * to complete, to prevent the loss of subsequent output data. * When the dump is instead captured in a file, this delay * is of course lost. If your printer driver outputs a reset * at the start of a dump (as ours do), use the -s (SKIP) option * to keep the initial CMD_WRITE out of the file. * * Sorry about the busywait synchronization of the device wedge * and the main process. The purpose was to avoid unnecessary * meddling with the message structures and the device's signals. * * v2 mods: changes to MyBeginIO for -1 and 0 length CMD_WRITES, usage * v3 mods: added buffering of small writes to speed file IO * * Linkage info (requires assembler module cmda): * FROM LIB:Astartup.obj, cmd.o, cmda.o * TO cmd * LIBRARY LIB:Amiga.lib,LIB:LC.lib */ #include "exec/types.h" #include "exec/memory.h" #include "exec/io.h" #include "exec/libraries.h" #include "libraries/dos.h" #include "libraries/dosextens.h" #include "workbench/startup.h" #include "workbench/workbench.h" #include "devices/serial.h" #include "devices/parallel.h" #define TOUPPER(c) ((c)>='a'&&(c)<='z'?(c)-'a'+'A':(c)) #define HIGHER(x,y) ((x)>(y)?(x):(y)) #define WBUFLEN 2048L #define INBUFLEN 40L #define REQSIZE 120L /* should be big enough or any OpenDevice */ #define DEV_CLOSE LIB_CLOSE #define DEV_EXPUNGE LIB_EXPUNGE /* DEV_BEGINIO (-30) defined in exec/io.h */ #define OPEN_SIG SIGBREAKF_CTRL_E #define WRITE_SIG SIGBREAKF_CTRL_F #define CLOSE_SIG SIGBREAKF_CTRL_D #define BREAK_SIG SIGBREAKF_CTRL_C #define SHORT_WRITE (8L) extern VOID myBeginIO(); /* The assembler entry */ extern VOID myClose(); /* The assembler entry */ extern VOID myExpunge(); /* The assembler routine */ extern struct MsgPort *CreatePort(); extern struct WBStartup *WBenchMsg; ULONG RealBeginIO, NewBeginIO; ULONG RealClose, NewClose; ULONG RealExpunge, NewExpunge; char *noMem = "Out of memory\n"; char *portName = "cas_TMP_CMD_PORT"; char *conSpec = "CON:20/20/600/40/ CMD v3"; char u1[]={"\nCLI Usage: [run] Cmd [-s] [-m] [-n] devicename filename\n"}; char u2[]={" devicename = serial or parallel\n"}; char u3[]={" -s = SKIP any short initial write (usually a reset if screendump)\n"}; char u4[]={" -m = installed for MULTIPLE files until Break or CTRL_C\n"}; char u5[]={" -n = enables NOTIFY (helpful progress messages)\n\n"}; char u6[]={"WB Tooltypes: DEVICE, FILE, and booleans SKIP,MULTIPLE,NOTIFY\n"}; char u7[]={" Cancel installation for multiple files by reclicking\n\n"}; char *us[7] = {u1,u2,u3,u4,u5,u6,u7}; char *morehelp = "Type cmd ? for more help\n\n"; char *prevTaskName = NULL; char *outFileName, *deviceName; char mainTaskName[40]; char wbDev[INBUFLEN], wbFile[INBUFLEN]; char sbuf[120], *wbuf = 0; struct Device *TheDevice; struct Task *otherTask, *mainTask; struct IOStdReq *myReq, *ioR; struct MsgPort *port; LONG wLen = 1, outFile = NULL; ULONG total = 0; ULONG IconBase = NULL; BOOL Error1 = TRUE, Skip = FALSE, Multiple = FALSE, Notify = FALSE; BOOL Done = FALSE, FromWb = FALSE, MainBusy = FALSE; int reqcnt = 0, writecnt = 0, filecnt = 0; fnLen, wi; VOID MyBeginIO(ior) struct IOStdReq *ior; { reqcnt += 1; if(ior->io_Command == CMD_WRITE) { if(!writecnt) { while(MainBusy); MainBusy = TRUE; Signal(mainTask,OPEN_SIG); while(MainBusy); } /* If device CMD_WRITE uses length -1, convert to actual length */ if(ior->io_Length==-1) ior->io_Length = strlen(ior->io_Data); if((!Skip)||(writecnt>0)||(ior->io_Length > SHORT_WRITE)) { if(ior->io_Length) { while(MainBusy); MainBusy = TRUE; ioR = ior; Signal(mainTask,WRITE_SIG); /* Signal write */ while(MainBusy); } } writecnt += 1; ior->io_Actual = ior->io_Length; } if(!(ior->io_Flags & IOF_QUICK)) ReplyMsg(ior); } VOID MyClose(ior) struct IOStdReq *ior; { /* Note - Exec has us in a forbid here */ if(reqcnt) /* Ignores DOS's initial Open/Close/Open */ { Signal(mainTask,CLOSE_SIG); /* Signal Close */ } } main(argc, argv) UWORD argc; TEXT *argv[]; { ULONG signals; int k; FromWb = (argc==0) ? TRUE : FALSE; if(FromWb) { getWbArgs(WBenchMsg); deviceName = wbDev; outFileName = wbFile; } else { if(strEqu(argv[1], "?")) usageHelpExit(); if(argc<3) usageExit(); for(k=1; argv[k][0]=='-'; k++) { if(argv[k][1] == 's') Skip = TRUE; if(argv[k][1] == 'm') Multiple = TRUE; if(argv[k][1] == 'n') Notify = TRUE; } if(argc-k < 2) usageExit(); deviceName = argv[k++]; outFileName = argv[k]; } fnLen = strlen(outFileName); /* Used if Multiple extension added */ /* Result will be mainTaskName = "cas_CMD_whatever.device" * with deviceName pointing to the eighth character */ strcpy(&mainTaskName[0],"cas_CMD_"); strcpy(&mainTaskName[strlen(mainTaskName)],deviceName); strcpy(&mainTaskName[strlen(mainTaskName)],".device"); deviceName = &mainTaskName[8]; Forbid(); if(otherTask = (struct Task *)FindTask(mainTaskName)) { Permit(); if(FromWb) Signal(otherTask,BREAK_SIG); else printf("Device already redirected... exiting\n"); cleanexit(); } mainTask = (struct Task *)FindTask(NULL); prevTaskName = mainTask->tc_Node.ln_Name; mainTask->tc_Node.ln_Name = mainTaskName; Permit(); /* initialize */ if(!(wbuf = (char *)AllocMem(WBUFLEN, MEMF_PUBLIC|MEMF_CLEAR))) cleanexit("Can't allocate write buffer\n"); if(!(port = CreatePort(portName, 0))) cleanexit("Can't open port\n"); myReq = (struct IOStdReq *)AllocMem(REQSIZE,MEMF_CLEAR|MEMF_PUBLIC); if (!myReq) cleanexit(noMem); myReq->io_Message.mn_Node.ln_Type = NT_MESSAGE; myReq->io_Message.mn_ReplyPort = port; if(OpenDevice(deviceName, 0, myReq, 0)) { sprintf(sbuf,"Can't open %s\n",deviceName); cleanexit(sbuf); } TheDevice = myReq->io_Device; /* Install device IO redirection */ Forbid(); RealBeginIO = SetFunction(TheDevice, DEV_BEGINIO, myBeginIO); RealClose = SetFunction(TheDevice, DEV_CLOSE, myClose); RealExpunge = SetFunction(TheDevice, DEV_EXPUNGE, myExpunge); Permit(); /* Expunge disabled, CloseDevice so another can open it */ CloseDevice(myReq); if(Notify) { sprintf(sbuf,"Cmd redirection of %s installed\n",deviceName); message(sbuf); } while(!Done) { signals = Wait(OPEN_SIG|WRITE_SIG|CLOSE_SIG|BREAK_SIG); if(signals & OPEN_SIG) /* Open */ { if(!outFile) /* No output file currently open */ { if(Multiple) /* If Multiple, add .n extension to filename */ { filecnt++; sprintf(&outFileName[fnLen],".%ld",filecnt); } /* open output file */ outFile = Open(outFileName, MODE_NEWFILE); wLen = 1; total = 0; wi = 0; /* Index into write buffer */ Error1 = TRUE; if(Notify) { sprintf(sbuf,"Redirecting %s to %s\n", deviceName,outFileName); message(sbuf); } } } if(signals & WRITE_SIG) /* Write */ { if((outFile)&&(wLen > -1)) { wLen = bufOrWrite(outFile,ioR->io_Data,ioR->io_Length); } else if(Error1) { message("Cmd file error: Cancel device output\n"); Error1 = FALSE; } } if(signals & (CLOSE_SIG|BREAK_SIG)) { /* Close file now so user can copy even if something is wrong */ /* Null the handle - to prevent Write or re-Close */ if(!Multiple) signals |= BREAK_SIG; if(outFile) { /* Write buffer contents */ if((wi>0)&&(wLen>-1)) wLen = myWrite(outFile,wbuf,wi); Forbid(); Close(outFile); outFile = NULL; writecnt = 0; reqcnt = 0; Permit(); if((!Multiple)||(Notify)) { sprintf(sbuf,"Redirected %ld bytes from %s to %s\n", total,deviceName,outFileName); message(sbuf); } } } if(signals & BREAK_SIG) { while(!Done) { /* Wait till we can reopen the device */ while(OpenDevice(deviceName, 0L, myReq, 0L)) Delay(50L); /* If it's been re-loaded, we can leave */ /* Shouldn't be possible since we disabled Expunge */ if((ULONG)myReq->io_Device != (ULONG)TheDevice) { Done = TRUE; } else { Forbid(); NewBeginIO = SetFunction(TheDevice, DEV_BEGINIO, RealBeginIO); NewClose = SetFunction(TheDevice, DEV_CLOSE, RealClose); NewExpunge = SetFunction(TheDevice, DEV_EXPUNGE, RealExpunge); if((NewBeginIO != (ULONG)myBeginIO) ||(NewClose != (ULONG)myClose) ||(NewExpunge != (ULONG)myExpunge)) { /* Someone else has changed the vectors */ /* We put theirs back - can't exit yet */ SetFunction(TheDevice, DEV_BEGINIO, NewBeginIO); SetFunction(TheDevice, DEV_CLOSE , NewClose); SetFunction(TheDevice, DEV_CLOSE, NewClose); SetFunction(TheDevice, DEV_CLOSE , NewClose); SetFunction(TheDevice, DEV_EXPUNGE, NewExpunge); } else { Done = TRUE; } Permit(); } CloseDevice(myReq); if(!Done) message("Vectors have changed - can't restore\n"); } } MainBusy = FALSE; } sprintf(sbuf,"\nCmd redirection of %s removed\n", deviceName); cleanexit(sbuf); } /* Output buffering */ bufOrWrite(fh,data,len) LONG fh; char *data; int len; { int k, wlen; wlen = len; /* If possible, just buffer the output data */ if(len < WBUFLEN - wi) { for(k=0; k<len; k++, wi++) wbuf[wi] = data[k]; } else { /* Else output any buffered data to the file */ if(wi>0) wlen = myWrite(fh,wbuf,wi); wi = 0; /* Then either buffer or write out current request */ if(wlen > -1) { if(len < WBUFLEN) { for(k=0; k<len; k++, wi++) wbuf[wi] = data[k]; wlen = len; } else { wlen = myWrite(fh,data,len); } } } return(wlen); } /* myWrite also updates total */ myWrite(fh,data,len) LONG fh; char *data; int len; { int wlen; wlen = Write(fh,data,len); if (wlen > -1) total += wlen; return(wlen); } /* Cleanup and exits */ usageHelpExit() { int k; for(k=0; k<7; k++) printf(us[k]); exit(RETURN_OK); } usageExit() { printf(u1); printf(morehelp); exit(RETURN_OK); } cleanexit(s) char *s; { message(s); cleanup(); exit(RETURN_OK); } cleanup() { if(myReq) FreeMem(myReq,REQSIZE); if(port) DeletePort(port); if(outFile) Close(outFile); if(wbuf) FreeMem(wbuf,WBUFLEN); Forbid(); if(prevTaskName) mainTask->tc_Node.ln_Name = prevTaskName; Permit(); } message(s) char *s; { LONG con; if((!FromWb)&&(*s)) printf(s); if((FromWb)&&(*s)&&(con = Open(conSpec,MODE_OLDFILE))) { Write(con,s,strlen(s)); Delay(120L); Close(con); } } getWbArgs(wbMsg) struct WBStartup *wbMsg; { struct WBArg *wbArg; struct DiskObject *diskobj; char **toolarray; char *s; /* Defaults */ strcpy(wbDev,"parallel"); strcpy(wbFile,"ram:CMD_file"); Skip = FALSE; Multiple = FALSE; Notify = FALSE; wbArg = wbMsg->sm_ArgList; if((IconBase = OpenLibrary("icon.library", 0))) { diskobj=(struct DiskObject *)GetDiskObject(wbArg->wa_Name); if(diskobj) { toolarray = (char **)diskobj->do_ToolTypes; if(s=(char *)FindToolType(toolarray,"DEVICE")) strcpy(wbDev,s); if(s=(char *)FindToolType(toolarray,"FILE")) strcpy(wbFile,s); if(s=(char *)FindToolType(toolarray,"SKIP")) { if(strEqu(s,"TRUE")) Skip = TRUE; } if(s=(char *)FindToolType(toolarray,"MULTIPLE")) { if(strEqu(s,"TRUE")) Multiple = TRUE; } if(s=(char *)FindToolType(toolarray,"NOTIFY")) { if(strEqu(s,"TRUE")) Notify = TRUE; } FreeDiskObject(diskobj); } CloseLibrary(IconBase); } } /* String functions */ strEqu(p, q) TEXT *p, *q; { while(TOUPPER(*p) == TOUPPER(*q)) { if (*(p++) == 0) return(TRUE); ++q; } return(FALSE); } strlen(s) char *s; { int i = 0; while(*s++) i++; return(i); } strcpy(to,from) char *to, *from; { do { *to++ = *from; } while(*from++); } /* end */ SHAR_EOF # End of shell archive exit 0