ain@j.cc.purdue.edu (Patrick White) (09/07/88)
Submitted by: kddlab!ndsuvax!ncreed@uunet.uu.net (Walter Reed) Summary: An Intuitionized replacement for diskcopy and format Poster Boy: Patrick White (ain@j.cc.purdue.edu) Archive Name: sources/amiga/volume5/dfc.s.sh.Z Tested NOTES: Re-share'ed. The "readme" file in the binaries shar is taken from the comments at the top of the source. -- Pat White (co-moderator comp.sources/binaries.amiga) ARPA/UUCP: j.cc.purdue.edu!ain BITNET: PATWHITE@PURCCVM PHONE: (317) 743-8421 U.S. Mail: 320 Brown St. apt. 406, West Lafayette, IN 47906 [archives at: j.cc.purdue.edu.ARPA] ======================================== # 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: # dfc.c # This archive created: Tue Sep 6 13:04:48 1988 # By: Patrick White (PUCC Land, USA) cat << \SHAR_EOF > dfc.c /* * This is dfc, a disk copying and formatting utility written by Radical * Eye Software. This program is in the public domain; anyone can do * anything they want with it. This software is made available on an as-is * basis; don't come to me if you destroy your entire fish disk library * with it! Of course, it was tested rather extensively before it was * released . . . * * This code was written with Manx 3.4b on February 8, 1988. * * dfc is meant to replace both format and diskcopy, and be smaller than * either. It is meant to work exactly the same as either, depending on * the command line arguments you give it. In addition, it has several * additional options, such as buffering of a disk for a quick additional * copy, the ability to toggle verify mode on and off, and the ability to * write to multiple disks at the same time. It also has a nice Intuition * interface. It will not replace the `DiskCopy' or `Format' options from * the workbench, however; these have to work with the Workbench startup * message, which this program does not. * * This program can be invoked with no command line options. In this case, * it defaults to a diskcopy from df0: to df1: with verify on and buffering * on. All keyword options can be preceded by a dash, for you Unix lovers * out there, and can be abbreviated to one character. The following * parameters are accepted: * * f[rom] disk Use `disk' as the source drive. The format for disk * is completely free; only the numerals in the argument * are looked at. * t[o] disks Use `disks' as the destination drives. Again, the * format is unspecified. * v[erify] Verify all writes (default). * nov[erify] Turn off the verify mode. * b[uffer] Use extra buffer memory. If your system has enough * memory, an entire disk will be cached in RAM. * Otherwise, almost all of the system memory will be * used. * nob[uffer] Do not buffer; use minimum memory. (Default.) * n[ame] diskname Format the destination disk with name `diskname'. * Otherwise a diskcopy is assumed. * d[...] A drive argument. Might not have a d; simple numbers * work as well. The first occurance of such an argument * sets the source and destination fields; a subsequent * occurance only sets the destination. * * Thus, * dfc df0: name "Foo bar baz" * formats drive df0: and names the resultant disk "Foo bar baz". * dfc df0: df1: * diskcopies from df0: to df1:. * dfc -nov 0 123 * diskcopies from drive 0 to drives 1, 2, and 3, with verify turned off. * dfc from df0: to df0:,df1:,df2:,df3: * diskcopies from drive df0: to all four drives. * dfc name Foo * formats drive df1 with the name "Foo". * * Once the program opens its window up, you will have a bunch of gadgets. * The left-most column of gadgets is the source selection; here you can * choose either format, or one of the four drives. The next column is * the destination column; choose any combination of the four drives by * clicking on the gadgets. Lines will be drawn between these two columns * indicating the current selections. If a drive is not available, no line * will be drawn to it. * * The topmost gadget is the name gadget. This is used to set the name of * your disk while formatting. * * The `Go' gadget starts the program up; you had better have the disks in * the drives when you hit this gadget! The `Again' gadget makes another * copy of the last disk, if buffering is on and enough buffers were * allocated for an entire disk. This gadget requires all destination * disks to be in their drives already. * * The `Retry' gadget tells the system to retry after a disk error. The * `Quit' gadget tells the system to abort an operation; you can hit this * at an error, which aborts the current operation; you can hit this during * a copy or format operation, which does the same; or you can hit this * with the drives inactive, which exits the program. * * The `Verify' gadget turns the verify mode on and off. The `Buffer' * gadget turns buffering on and off. * * All of these gadgets have keyboard shortcuts which are simply the first * letter of the gadget name. The source gadgets can be selected with the * digits `0', `1', `2', or `3'; the destination gadgets with the shifted * versions of these keys (')', `!', `@', and `#'.) The carriage return * key is a synonym for `Go', for added diskcopy and format compatibility. * * Enjoy this program! Please send any bug reports to Tomas Rokicki, * Box 2081, Stanford, CA 94309. */ #define TITLE "dfc 1 Radical Eye Software" /* * A handful of includes for good luck. */ #include "intuition/intuition.h" #include "functions.h" #include <exec/exec.h> #include <exec/execbase.h> #include <devices/trackdisk.h> #include <libraries/dosextens.h> /* * These are the various globals this routine uses. */ struct StandardPacket *gpacket ; /* packet to send various things */ struct IntuiMessage *message ; /* the message we are working on */ struct Gadget *gadad ; /* address of gadget from message */ struct Window *window ; /* our window */ struct MsgPort *port ; /* I/O port for dos communication */ struct IntuitionBase *IntuitionBase ; /* we need Intuition */ struct GfxBase *GfxBase ; /* and graphics */ int havedisk ; /* do we have a full disk in RAM? */ int hibuf ; /* the highest buffer we have */ long output ; /* can we write output? */ char *buffers[81] ; /* pointers to our buffers */ struct IOExtTD *diskreq[4] ; /* our I/O request blocks */ long diskChangeCount[4] ; /* last time disk was changed */ int source = 1 ; /* the source disk, not a mask */ int dest = 2 ; /* all destination drives */ int verify = 1 ; /* verify writes? */ int buffer = 0 ; /* are we using lots of memory? */ char namebuf[32] ; /* this buffer holds disk names */ char df0[] = "DF0:" ; /* use only one copy of this name */ char df1[] = "DF1:" ; /* ditto */ char df2[] = "DF2:" ; /* ditto */ char df3[] = "DF3:" ; /* ditto */ char *df[]={df0, df1, df2, df3}; /* for easy access to drive names */ char blank[] = " " ; /* used to blank out the middle line */ char msg[] = "Disk df is not complete" ; /* error message for a disk */ char reading[] = "Reading track xx dfx" ; /* message for reading disks */ char writing[] = "Writing track xx dfx" ; /* message for writing disks */ char veriing[] = "Ver'ing track xx dfx" ; /* message for ver'ing disks */ char nobuf[] = "! couldn't get buffer" ; /* if we can't get a buffer */ /* * Always use topaz 80, and let's define an intuition buffer for our * general string writing stuff. */ struct TextAttr myfont = {(STRPTR)"topaz.font", TOPAZ_EIGHTY, 0, 0 }; struct IntuiText intuitext = {1, 0, JAM2, 0, 0, &myfont} ; /* * Some defines setting the sizes of various things. Play with these to * resize things. Too bad string gadgets still have problems with large * fonts. */ #define WIDEGADG 83 #define NARROWGADG 51 #define STRGADG 256 #define GADGHEIGHT 13 #define LINE1 13 #define LINE2 LINE1+11 #define LINE3 LINE2+GADGHEIGHT #define LINE4 LINE3+GADGHEIGHT #define LINE5 LINE4+GADGHEIGHT #define LINE6 LINE5+GADGHEIGHT #define WINDOWHEIGHT LINE6+GADGHEIGHT+2 #define COL1 4 #define LINESTART COL1+NARROWGADG #define STRSTART COL1+NARROWGADG+23 #define COL2 COL1+NARROWGADG+42 #define LINEEND COL2-1 #define COL3 COL2+NARROWGADG+10 #define COL4 COL3+WIDEGADG+10 #define WINDOWWIDTH COL4+WIDEGADG+4 #define TRACKSIZE (2*512*11) /* * Now, some defines to assist in declaring a few things, and * centering strings, and the like. These macros make the Intuition * crap a lot easier to put together. */ #define makeintuitext(name,string,size) struct IntuiText name={1,0,JAM2,\ (1+size-8*(sizeof(string)-1))/2,3,&myfont,(UBYTE*)string} #define makebox(name,tname,width,height,off) short tname[]={0,0,width,0,\ width,1,0,1,0,height-1,width,height-1,width,height,0,height,width,height,\ width,0};\ struct Border name={off,off,1,0,JAM2,10,tname} #define makengadg(name,prev,xpos,ypos,text,ch) struct Gadget name={prev,\ xpos,ypos,NARROWGADG,GADGHEIGHT,GADGHCOMP,RELVERIFY,BOOLGADGET,\ (APTR)&narrowbox,NULL,&text,NULL,NULL,ch} #define makewgadg(name,prev,xpos,ypos,text,ch) struct Gadget name={prev,\ xpos,ypos,WIDEGADG,GADGHEIGHT,GADGHCOMP,RELVERIFY,BOOLGADGET,\ (APTR)&widebox,NULL,&text,NULL,NULL,ch} /* * Now we declare all of our structures with the above macros. * First, the text for our gadgets, and then the gadgets. No sweat. */ makeintuitext(formattext,"Format",NARROWGADG); makeintuitext(df0text,df0,NARROWGADG); makeintuitext(df1text,df1,NARROWGADG); makeintuitext(df2text,df2,NARROWGADG); makeintuitext(df3text,df3,NARROWGADG); makeintuitext(againtext,"Again",WIDEGADG); makeintuitext(retrytext,"Retry",WIDEGADG); makeintuitext(quittext,"Quit",WIDEGADG); makeintuitext(verifyontext,"Verify On ",WIDEGADG); makeintuitext(verifyofftext,"Verify Off",WIDEGADG); makeintuitext(bufferontext,"Buffer On ",WIDEGADG); makeintuitext(bufferofftext,"Buffer Off",WIDEGADG); makeintuitext(gotext,"Go",WIDEGADG); makebox(narrowbox,tn1,NARROWGADG-1,GADGHEIGHT-1,0); makebox(widebox,tn2,WIDEGADG-1,GADGHEIGHT-1,0); makebox(strbox,tn3,STRGADG-1,GADGHEIGHT-1,-2); makengadg(formatgadg,NULL,COL1,LINE2,formattext,'f'); makengadg(sdf0gadg,&formatgadg,COL1,LINE3,df0text,'0'); makengadg(sdf1gadg,&sdf0gadg,COL1,LINE4,df1text,'1'); makengadg(sdf2gadg,&sdf1gadg,COL1,LINE5,df2text,'2'); makengadg(sdf3gadg,&sdf2gadg,COL1,LINE6,df3text,'3'); makengadg(ddf0gadg,&sdf3gadg,COL2,LINE3,df0text,')'); makengadg(ddf1gadg,&ddf0gadg,COL2,LINE4,df1text,'!'); makengadg(ddf2gadg,&ddf1gadg,COL2,LINE5,df2text,'@'); makengadg(ddf3gadg,&ddf2gadg,COL2,LINE6,df3text,'#'); makewgadg(gogadg,&ddf3gadg,COL3,LINE4,gotext,'g'); makewgadg(retrygadg,&gogadg,COL3,LINE5,retrytext,'r'); makewgadg(verifygadg,&retrygadg,COL3,LINE6,verifyontext,'v'); makewgadg(againgadg,&verifygadg,COL4,LINE4,againtext,'a'); makewgadg(quitgadg,&againgadg,COL4,LINE5,quittext,'q'); makewgadg(buffergadg,&quitgadg,COL4,LINE6,bufferontext,'b'); /* * We need one last gadget, the string gadget, and its associated * special info structure. */ struct StringInfo nameinfo={(UBYTE*)namebuf,NULL,0,31}; struct Gadget namegadg={&buffergadg,STRSTART+2,LINE2+2,STRGADG-4, GADGHEIGHT-4,GADGHCOMP,STRINGCENTER|RELVERIFY,STRGADGET,(APTR)&strbox, NULL,NULL,NULL,(APTR)&nameinfo,'n'}; /* * Now we have our window structure. Initially not resizeable. */ struct NewWindow newwindow = {200,20,WINDOWWIDTH,WINDOWHEIGHT,0,1, CLOSEWINDOW|VANILLAKEY|GADGETDOWN|GADGETUP, WINDOWDEPTH|WINDOWCLOSE|WINDOWDRAG|SMART_REFRESH|ACTIVATE, &namegadg,NULL,(UBYTE *)TITLE,NULL,NULL,-1,-1,-1,-1,WBENCHSCREEN}; /* * Now we start coding. This routine draws a string into the window at * a specific X, Y location. */ draw(s, x, y) char *s ; int x, y ; { intuitext.IText = (UBYTE *)s ; PrintIText(window->RPort, &intuitext, (long)x, (long)y) ; } /* * This routine gives us those pretty little DF0:BUSY things, which keep * AmigaDOS from futzing with the drives when we are playing with them. */ inhibit(d, t) int d ; long t ; { struct MsgPort *handler ; struct StandardPacket *packet = gpacket ; handler = (struct MsgPort *)DeviceProc(df[d]) ; if (handler == NULL || port == NULL) return ; packet->sp_Msg.mn_Node.ln_Name = (char *)&(packet->sp_Pkt) ; packet->sp_Pkt.dp_Link = &(packet->sp_Msg) ; packet->sp_Pkt.dp_Port = port ; packet->sp_Pkt.dp_Type = ACTION_INHIBIT ; packet->sp_Pkt.dp_Arg1 = t ; PutMsg(handler, packet) ; WaitPort(port) ; GetMsg(port) ; } /* * This routine looks at the global message and returns a character * indicating the selected gadget. This gives us easy equivalence * of gadgets to vanillakeys, for instance. This also replymsg()'s * the message. */ int getop() { register long class ; register int op ; short code ; class = message->Class ; code = message->Code ; op = ' ' ; gadad = (struct Gadget *)(message->IAddress) ; ReplyMsg(message) ; message = NULL ; if (class == CLOSEWINDOW) { op = 'q' ; } else if (class == GADGETDOWN || class == GADGETUP) { op = gadad->GadgetID ; } else if (class == VANILLAKEY) { op = code ; gadad = NULL ; } return(upcase(op)) ; } /* * We don't want to queue up messages, because the user might hit * 'g' 100 times accidentally. We flush all pending messages, and * return the last operation. This way, this routine can be used * to see if the user typed 'Q' to exit some operation. */ int disposemsgs() { register int op = 0 ; while (message = (struct IntuiMessage *) GetMsg(window->UserPort)) op = getop() ; return(op) ; } /* * This is put into a function to help make the program smaller. * It takes a character and makes sure it is uppercase. */ int upcase(c) register int c ; { if ('a' <= c && c <= 'z') return(c-32) ; else return(c) ; } /* * Here we wait for a quit or retry key. We also accept go, space, * and carriage return. */ int abortretry() { register int op ; disposemsgs() ; while (1) { while ((message = (struct IntuiMessage *) GetMsg(window->UserPort))==NULL) WaitPort(window->UserPort) ; op = getop() ; if (op == 'Q' || op == 'R' || op == ' ' || op == 'G' || op == 10) return(op) ; } } /* * This routine pads a string out to 40 characters. Used to write to * the top line of the window. */ static char ibuf[41] ; char *to40(s) register char *s ; { register int i = 0 ; register char *p = ibuf ; while (*s != 0) p[i++] = *s++ ; while (i < 40) p[i++] = ' ' ; return(p) ; } /* * This error routine draws a message up at the top of the screen, and * waits for a response. After it gets one, it returns the response. * It clears the top line afterwards. */ int error(s) register char *s ; { int op ; DisplayBeep(NULL) ; if (*s == '!' || !window) { if (output) { Write(output, s, (long)strlen(s)) ; Write(output, "\n", 1L) ; } cleanup() ; } else { draw(to40(s), COL1, LINE1+2) ; op = abortretry() ; } draw(to40(""), COL1, LINE1+2) ; return(op) ; } /* * If the user selects a set of drives, this routine first tries to * allocate them. Then, based on the success of the allocation, it * draws lines in the window indicating the source and destination * drives. */ redrawlines() { register int i, j ; allocdisks() ; SetAPen(window->RPort, 0L) ; RectFill(window->RPort, (long)LINESTART, (long)LINE2, (long)STRSTART-1, (long) LINE6+GADGHEIGHT) ; RectFill(window->RPort, (long)STRSTART, (long)LINE3, (long)LINEEND, (long)LINE6+GADGHEIGHT) ; SetAPen(window->RPort, 1L) ; i = LINE3 + (GADGHEIGHT+1)/2 + source * GADGHEIGHT ; for (j=0; j<4; j++) if (dest & (1 << j)) { Move(window->RPort, (long)LINESTART, (long)i) ; Draw(window->RPort, (long)LINEEND, (long)(LINE3 + (GADGHEIGHT+1)/2 + j * GADGHEIGHT)) ; } } /* * This routine parses a string, usually something like 'df0:' or * 'df0:,df1:,df2:', but can be even '012', into just a mask indicating * which drives were selected. It simply looks for the characters '0', * '1', '2', and '3'. */ int getmask(s) register char *s ; { register int t = 0 ; while (*s != 0) { if ('0' <= *s && *s <= '3') t |= 1 << (*s - '0') ; s++ ; } return(t) ; } /* * This is our exit routine. It frees the drives, deletes the ports, * buffers, closes the window, and libraries. Then it exits. */ cleanup() { register int i ; for (i=0; i<4; i++) FreeDisk(i) ; if (gpacket) FreeMem(gpacket, (long)sizeof(struct StandardPacket)) ; if (port) DeletePort(port) ; freebuffers() ; if (window) CloseWindow(window) ; if (GfxBase) CloseLibrary(GfxBase) ; if (IntuitionBase) CloseLibrary(IntuitionBase) ; exit(0) ; } /* * This routine attempts to allocate m buffers. We try and leave at * least 32K of memory, even with the allocations. We set hibuf at the * exit point. */ getbuffers(m) int m ; { register int i ; if (buffers[80]==NULL && (buffers[80]=(char *) AllocMem((long)TRACKSIZE, MEMF_CHIP | MEMF_PUBLIC))==NULL) error(nobuf) ; hibuf = 0 ; for (i=0; i<m; i++) if (buffers[i]==NULL && (AvailMem(MEMF_PUBLIC) < 32000 || (buffers[i]=(char *)AllocMem((long)TRACKSIZE, MEMF_PUBLIC))==NULL)) break ; else hibuf = i+1 ; if (hibuf < 1) error(nobuf) ; for (; i<80; i++) if (buffers[i] != NULL) { FreeMem(buffers[i], (long)TRACKSIZE) ; buffers[i] = NULL ; } } /* * And this routine lets them all go. */ int freebuffers() { register int i ; for (i=0; i<81; i++) if (buffers[i] != NULL) { FreeMem(buffers[i], (long)TRACKSIZE) ; buffers[i] = NULL ; } } /* * We attempt to create an I/O request. We allocate memory for it, and * then initialize some of the ports. */ struct IORequest *CreatExtIO() { register struct IORequest *ioReq ; ioReq = (struct IORequest *)AllocMem((long)sizeof(struct IOExtTD), MEMF_CLEAR | MEMF_PUBLIC) ; if (ioReq == NULL) return (NULL) ; ioReq->io_Message.mn_Node.ln_Type = NT_MESSAGE ; ioReq->io_Message.mn_Node.ln_Pri = 0 ; ioReq->io_Message.mn_ReplyPort = port ; return(ioReq) ; } /* * This routine frees an I/O request. */ DeleteIO(ioExt) struct IORequest *ioExt ; { FreeMem(ioExt, (long)sizeof(struct IOExtTD)) ; } /* * Now we try to allocate a disk. First, we attempt to allocate an * I/O request, and then we actually open the device. If either of * these fail, we return 0. This can occur if a drive is opened that * doesn't exist. After we have the disk, we inhibit AmigaDOS from * putzing with it. */ int OpenDisk(i) register int i ; { register struct IOExtTD **p = diskreq+i ; if (*p) return(1) ; if ((*p = (struct IOExtTD *)CreatExtIO()) && OpenDevice(TD_NAME, (long)i, *p, 0L) == 0) { inhibit(i, TRUE) ; return(1) ; } else { if (*p) { DeleteIO(*p) ; *p = NULL ; } return(0) ; } } /* * Here we release a disk. We check that we have it first! Then, we * close the device, uninhibit the drive, kill the I/O request, and * exit. */ FreeDisk(i) register int i ; { register struct IOExtTD **p = diskreq+i ; if (*p) { CloseDevice(*p) ; inhibit(i, FALSE) ; DeleteIO(*p) ; *p = NULL ; } } /* * This routine attempts to allocate all of the disks we need, based on the * current settings of source and dest. If a disk cannot be allocated, it * is removed from both source or dest. Source might be set to point to df0, * if this occurs. Then, if we can't allocate df0, we exit fatally. This * occurance can actually happen, if some other program has df0:. (I think.) */ allocdisks() { register int i, need ; top: need = dest ; if (source != -1) need |= 1 << source ; for (i=0; i<4; i++, need >>= 1) { if (need & 1) { if (! OpenDisk(i)) { dest &= ~(1 << i) ; if (source == i) { if (i == 0) error("! I couldn't allocate the internal drive") ; source = 0 ; goto top ; } } } else FreeDisk(i) ; } } /* * We call this routine if we are going to be accessing this * disk. It gets the changecount, so our read/write routines work. */ int InitDisk(d) register int d ; { register struct IOExtTD *p = diskreq[d] ; int result ; p->iotd_Req.io_Command = TD_CHANGENUM ; DoIO(p) ; result = (diskChangeCount[d] != p->iotd_Req.io_Actual) ; diskChangeCount[d] = p->iotd_Req.io_Actual ; return(result) ; } /* * The plural of the above routine takes a mask and initializes all of * the drives. It adds the source drive to the list automatically. */ initdisks(mask) int mask ; { int d ; if (source != -1) mask |= (1 << source) ; for (d=0; d<4; d++) if (mask & (1 << d)) InitDisk(d) ; } /* * Kill the motor of a drive. So someone can stick disks in and out. */ motoroff(d) register int d ; { register struct IOExtTD *p = diskreq[d] ; p->iotd_Req.io_Length = 0 ; p->iotd_Req.io_Command = TD_MOTOR ; DoIO(p) ; } /* * This routine is a fast machine-language block move, that moves * exactly one block of data. Do not change TRACKSIZE and expect * this still to work! */ fcpy(dest, src) long *dest, *src ; { #asm movem.l a0-a6/d0-d7,-(a7) move.l 12(a5),a0 move.l 8(a5),a1 move.l #43,d0 lsdf: movem.l (a0)+,a2-a6/d1-d7 movem.l a2-a6/d1-d7,(a1) add.w #48,a1 movem.l (a0)+,a2-a6/d1-d7 movem.l a2-a6/d1-d7,(a1) add.w #48,a1 movem.l (a0)+,a2-a6/d1-d7 movem.l a2-a6/d1-d7,(a1) add.w #48,a1 movem.l (a0)+,a2-a6/d1-d7 movem.l a2-a6/d1-d7,(a1) add.w #48,a1 movem.l (a0)+,a2-a6/d1-d7 movem.l a2-a6/d1-d7,(a1) add.w #48,a1 movem.l (a0)+,a2-a5 movem.l a2-a5,(a1) add.w #16,a1 dbra d0,lsdf movem.l (a7)+,a0-a6/d0-d7 #endasm } /* * Another fast assembly language routine for verifying a buffer. This * routine returns 0 if the two buffers are the same, and something else * otherwise. */ int fcmp(dest, src) long *dest, *src ; { register int foo = 0 ; #asm movem.l d0/a0/a1,-(a7) move.l 12(a5),a0 move.l 8(a5),a1 move.l #2815,d0 alsdf: cmp.l (a0)+,(a1)+ dbne d0,alsdf move.w d0,d4 addq.w #1,d4 movem.l (a7)+,a0/a1/d0 #endasm return(foo) ; } /* * This routine turns on a particular gadget. */ turnon(g) register struct Gadget *g ; { if (g->Flags & GADGDISABLED) { SetAPen(window->RPort, 0L) ; RectFill(window->RPort, (long)g->LeftEdge, (long)g->TopEdge, (long)g->LeftEdge+g->Width-1, (long)g->TopEdge+g->Height-1) ; SetAPen(window->RPort, 1L) ; OnGadget(g, window, NULL) ; } } /* * This routine turns off a particular gadget. */ turnoff(g) register struct Gadget *g ; { if (!(g->Flags & GADGDISABLED)) OffGadget(g, window, NULL) ; } /* * Our main copy routine. The variable j holds the current destinations * that are being written into; as disks drop like flies, j will drop * them; at the end, we print a message about all the disks that dropped * out. We start by initializing the disks. */ goforit(again) int again ; { register int i, j, k, t ; register int ohbuf ; turnoff(&gogadg) ; turnoff(&verifygadg) ; turnoff(&buffergadg) ; turnoff(&againgadg) ; turnon(&retrygadg) ; turnoff(&namegadg) ; RefreshGadgets(window->FirstGadget, window, NULL) ; j = dest ; initdisks(j) ; ohbuf = hibuf ; /* * If we are formatting, we only use one buffer. This avoids the * unsightly delay which happens if we build up the formatted disk in * memory first; the user wonders what the hell is going on. We first * check that the user isn't trying to read and write from the same disk; * if he is, he will have to swap about 160 times, so we tell him no go. */ if (source == -1) ohbuf = 1 ; if (ohbuf == 1 && source >= 0 && (dest & (1 << source))) { while (error("No buffering?") == 'R') ; goto finishup ; } for (t=0; t<80; t+=ohbuf) { if (! again) { for (k=0; k<ohbuf && t+k<80; k++) { if (disposemsgs()=='Q') goto aborted ; if (getdata(source, t+k, k)==0) goto finishup ; } if (source != -1) { if (dest & (1 << source)) { for (i=0; i<4; i++) if (j & (1 << i)) motoroff(i) ; do { if (error("Enter destination disks")=='Q') goto finishup ; } while (! InitDisk(source)) ; initdisks(j) ; } } if (ohbuf == 80) havedisk = 1 ; } for (k=0; k<ohbuf && t+k<80; k++) { if (disposemsgs()=='Q') goto aborted ; for (i=0; i<4; i++) { if (j & (1 << i)) { if (writedata(i, t+k, k)==0) { j &= ~(1 << i) ; motoroff(i) ; } } } } if (! again) { if (source != -1 && (dest & (1 << source)) && t+k < 80) { motoroff(source) ; do { if (error("Enter source disk")=='Q') goto finishup ; } while (! InitDisk(source)) ; } } } goto finishup ; /* * On exit, we turn off all the motors, clear out the middle line, and * return. */ aborted: j = 0 ; finishup: for (i=0; i<4; i++) if (diskreq[i]) motoroff(i) ; for (i=0; i<4; i++) if ((dest-j) & (1<<i)) { msg[7] = i + '0' ; error(msg) ; } draw(blank, COL3, LINE3+3) ; disposemsgs() ; } /* * This routine writes a given message to the screen; either reading, * writing, or ver'ing. It fills in the track and disk number. It * checks first that the source isn't `format', which is created behind * the scenes instead of being read from an actual disk. If we are * reading from track 40, we get the name of the disk and put it up on * the screen. */ writeop(s, t, d) register char *s ; register int t, d ; { if (d != -1) { s[14] = '0' + t / 10 ; s[15] = '0' + t % 10 ; s[19] = '0' + d ; draw(s, COL3, LINE3+3) ; } } /* * This routine gets data from disk d, track t, into buffer b. * If the disk is -1, it gets it from the format routine (later.) * We return 1 if success; 0 if failure. If there is an error, we allow * the user to retry as many times as he likes. */ int getdata(d,t,b) register int d, t, b ; { register struct IOExtTD *p = diskreq[d] ; register int i = 0 ; if (d==-1) { makeformatdata(t, b) ; return(1) ; } do { writeop(reading, t, d) ; p->iotd_Req.io_Length = TRACKSIZE ; p->iotd_Req.io_Data = (APTR)buffers[80] ; p->iotd_Req.io_Command = ETD_READ ; p->iotd_Count = diskChangeCount[d] ; p->iotd_Req.io_Offset = t * (long)TRACKSIZE ; DoIO(p) ; fcpy(buffers[b], buffers[80]) ; } while (p->iotd_Req.io_Error != 0 && ((i=error("Read Error; Quit/Retry?"))!='Q')) ; if (t==40 && i != 'Q') writename(b) ; return (i != 'Q') ; } /* * writedata is analagous to the above routine. However, if verify is * turned on, then we read the data back in from the disk and make sure * that it is correct. Note that whenever we write track 40, we first * update the root block creation date and last modified date. */ int writedata(d,t,b) register int d, t, b ; { register struct IOExtTD *p = diskreq[d] ; register int i = 0 ; top: if (t==40) updaterootblock(b) ; do { writeop(writing, t, d) ; fcpy(buffers[80], buffers[b]) ; p->iotd_Req.io_Length = TRACKSIZE ; p->iotd_Req.io_Data = (APTR)buffers[80] ; p->iotd_Req.io_Command = TD_FORMAT ; p->iotd_Count = diskChangeCount[d] ; p->iotd_Req.io_Offset = t * (long)TRACKSIZE ; DoIO(p) ; } while (p->iotd_Req.io_Error != 0 && ((i=error("Write Error; Quit/Retry?"))!='Q')) ; if (i=='Q') return(0) ; if (verify) { writeop(veriing, t, d) ; p->iotd_Req.io_Length = TRACKSIZE ; p->iotd_Req.io_Data = (APTR)buffers[80] ; p->iotd_Req.io_Command = ETD_READ ; p->iotd_Count = diskChangeCount[d] ; p->iotd_Req.io_Offset = t * (long)TRACKSIZE ; DoIO(p) ; if ((p->iotd_Req.io_Error != 0 || fcmp(buffers[80], buffers[b])) && ((i=error("Verify Error; Quit/Retry?"))!='Q')) { if (t==0) InitDisk(d) ; goto top ; } } return (i != 'Q') ; } /* * This routine creates data for the format option. Note the clever * way the data is built up; this routine should build a disk exactly * the same way the standard AmigaDOS format does. If we are on track * 40, some additional work must be done to create a root block and * bitmap, but it's not too bad. */ makeformatdata(t, b) int t, b ; { register long *p ; register long cs ; register long i ; unsigned char *q ; p = (long *)buffers[b] ; cs = 'DOS\0' + (((long)t & 48) << 16) ; for (i=0; i<TRACKSIZE/4; i++) *p++ = cs + (i & 3327) ; if (t != 40) return ; p = (long *)buffers[b] ; for (i=0; i<256; i++) *p++ = 0 ; p = (long *)buffers[b] ; p[0] = 2 ; p[3] = 0x48 ; p[78] = 1 ; p[79] = 0x371 ; q = (unsigned char *)(p + 108) ; *q++ = strlen(namebuf) ; strcpy(q, namebuf) ; p[127] = 1 ; p += 128 ; for (i=1; i<55; i++) p[i] = 0xffffffff ; p[0] = 0xc000c037 ; p[28] = 0xffff3fff ; p[55] = 0x3fffffff ; } /* * This routine recalculates the checksum for a block, and updates it in * word 5. The sum of all the words in a block must be 0. */ recheck(w) register long *w ; { register int i ; register long cs ; cs = 0 ; for (i=0; i<128; i++) cs += w[i] ; w[5] -= cs ; } /* * We simply DateStamp the creation date, modification date, and * rechecksum the block. */ updaterootblock(b) register int b ; { DateStamp(buffers[b] + 420) ; DateStamp(buffers[b] + 484) ; recheck(buffers[b]) ; } /* * If we read from track 40, we write the name on the screen in * the string gadget supplied for that purpose. */ writename(b) int b ; { int i = buffers[b][432] ; int j ; RemoveGadget(window, &namegadg) ; if (i > 31) i = 31 ; for (j=0; j<i; j++) namebuf[j] = buffers[b][j + 433] ; namebuf[j] = 0 ; AddGadget(window, &namegadg, -1L) ; RefreshGadgets(&namegadg, window, NULL) ; } /* * And finally, our main routine! This thing is awfully long; they do * get that way sometimes, don't they? */ main(argc, argv) int argc ; char *argv[] ; { int op ; register char *p, *q ; int seen = 0 ; output = (long)Output() ; /* * First, we parse the arguments. Arguments allowed are documented at * the top of this file. If there is a dash as the first character of * an argument, we ignore it, thus allowing Unix-style options. */ while (argc > 1) { argc-- ; argv++ ; p = *argv ; if (argc > 1) q = argv[1] ; else q = "" ; if (*p == '-') p++ ; switch (upcase(*p)) { /* * If the argument starts with a D, or a number, it specifies a drive. * Actually, it might be a `DRIVE' keyword, which would return a mask of * 0, so we check the return value for 0. */ case '0' : case '1' : case '2' : case '3' : case 'D' : dest = getmask(p) ; if (dest != 0) { if (! seen) source = dest ; seen = 1 ; } break ; /* * The from keyword sets the source . . . */ case 'F' : argc-- ; argv++ ; source = getmask(q) ; seen = 1 ; break ; /* * The to keyword sets the destination . . . */ case 'T' : argc-- ; argv++ ; dest = getmask(q) ; break ; /* * This could either be a `nobuffer', `noverify', or `name' keyword. * We check for any of these. */ case 'N' : if (p[1]=='o' || p[1]=='O') { if (p[2]=='v' || p[2]=='V') verify = 0 ; else if (p[2]=='b' || p[2]=='B') buffer = 0 ; else goto errorarg ; } else { if (strlen(q) > 30) q[30] = 0 ; strcpy(namebuf, q) ; source = 0 ; argc-- ; argv++ ; } break ; /* * Verify keyword is easy */ case 'V' : verify = 1 ; break ; /* * As is the buffer keyword. */ case 'B' : buffer = 1 ; break ; /* * We go ahead and print an error message if we didn't understand an * option. */ default: errorarg: if (output) { Write(output, "Unknown option ", 15L) ; Write(output, p, (long)strlen(p)) ; Write(output, "\n", 1L) ; } } } /* * Up to this point, the source has been a mask. Now we turn it into an * integer. */ if (source & 8) source = 3 ; else if (source & 4) source = 2 ; else if (source & 2) source = 1 ; else if (source & 1) source = 0 ; else source = -1 ; /* * We initialize a few gadgets based on the parameters the user selected. */ if (verify) { verifygadg.GadgetText = &verifyontext ; } else { verifygadg.GadgetText = &verifyofftext ; } if (buffer) { buffergadg.GadgetText = &bufferontext ; hibuf = 80 ; } else { buffergadg.GadgetText = &bufferofftext ; hibuf = 1 ; } /* * And now we try and open things up! First intuition, then graphics, * then our window and an I/O port. If any of these fail, we simply * exit. */ if ((IntuitionBase = (struct IntuitionBase *)OpenLibrary( "intuition.library",33L))!=NULL && (GfxBase = (struct GfxBase *)OpenLibrary( "graphics.library",0L))!=NULL && (window=OpenWindow(&newwindow))!=NULL && (port=CreatePort(0L, 0L)) && (gpacket=(struct StandardPacket *)AllocMem( (long)sizeof(struct StandardPacket),MEMF_PUBLIC|MEMF_CLEAR))) { /* * We draw the lines between what the user has selected, and enter into * our main command loop. Then, we wait for a message. After getting one, * we drop into a case statement keying off what message it was. */ redrawlines() ; getbuffers(hibuf) ; while (1) { if (havedisk) turnon(&againgadg) ; else turnoff(&againgadg) ; turnoff(&retrygadg) ; turnon(&gogadg) ; turnon(&verifygadg) ; turnon(&buffergadg) ; if (source == -1) turnon(&namegadg) ; else turnoff(&namegadg) ; RefreshGadgets(window->FirstGadget, window, NULL) ; while ((message = (struct IntuiMessage *) GetMsg(window->UserPort))==NULL) WaitPort(window->UserPort) ; op = getop() ; switch(op) { /* * A shift-0, shift-1, shift-2, or shift-3 toggles the appropriate bit * in the destination, and redraws the lines. A 0, 1, 2, or 3 sets the * source to that drive, and continues. */ case ')' : dest ^= 1 ; goto redrawem ; case '!' : dest ^= 2 ; goto redrawem ; case '@' : dest ^= 4 ; goto redrawem ; case '#' : dest ^= 8 ; goto redrawem ; case '0' : case '1' : case '2' : case '3' : source = op - '0' ; redrawem : redrawlines() ; break ; /* * If verify is selected, we toggle the state of the verify flag, and * update the gadget to reflect this state. */ case 'V' : verify = ! verify ; RemoveGadget(window, &verifygadg) ; if (verify) { verifygadg.GadgetText = &verifyontext ; } else { verifygadg.GadgetText = &verifyofftext ; } AddGadget(window, &verifygadg, -1L) ; RefreshGadgets(&verifygadg, window, NULL) ; break ; /* * The buffer option does essentially the same thing, only for buffering. */ case 'B' : buffer = ! buffer ; RemoveGadget(window, &buffergadg) ; if (buffer) { buffergadg.GadgetText = &bufferontext ; getbuffers(80) ; } else { buffergadg.GadgetText = &bufferofftext ; getbuffers(1) ; } havedisk = 0 ; AddGadget(window, &buffergadg, -1L) ; RefreshGadgets(&buffergadg, window, NULL) ; break ; /* * If the user selects `format', then we set the source appropriately and * redraw the lines. Of course, we no longer have a disk, as when the * format is executed, it will destroy buffer 0. */ case 'F' : source = -1 ; havedisk = 0 ; goto redrawem ; /* * The name gadget, if invoked from the keyboard with the `n' key, * Activates the string gadget. */ case 'N' : if (gadad == NULL && source == -1) { ActivateGadget(&namegadg, window, NULL) ; } break ; /* * The `G' gadget, or carriage return, starts us on our merry way. */ case 'G' : case 10 : case 32 : case 13 : goforit(0) ; break ; /* * The `A' gadget insures that we have a disk first, otherwise it * complains. */ case 'A' : if (! havedisk) { while (error("No disk in memory")=='R') ; } else { goforit(1) ; } break ; /* * Retry is ignored in the main command loop, as there is nothing to * retry! */ case 'R' : /* * Quit is handled at the bottom of the loop. */ case 'Q' : /* * As a default, we do nothing; not even an error message. */ default : break ; } /* * If the last gadget selected was 'Q', we exit. */ if (op == 'Q') break ; } } else { error("! couldn't open window") ; } /* * Release memory and exit. */ cleanup() ; } SHAR_EOF # End of shell archive exit 0