/****************************************************************************/ /* */ /* P e r i p h e r a l I n t e r c h a n g e P r o g r a m */ /* --------------------------------------------------------- */ /* */ /* Copyright (c) 1976, 1977, 1978, 1979, 1980, 1981 */ /* Digital Research */ /* Box 579 */ /* Pacific Grove, CA */ /* 93950 */ /* */ /****************************************************************************/ /****************************************************************************/ /* */ /* Revised: */ /* 17 Jan 80 by Thomas Rolander (MP/M 1.1) */ /* 05 Oct 81 by Ray Pedrizetti (MP/M-86 2.0) */ /* 18 Dec 81 by Ray Pedrizetti (CP/M-86 1.1) */ /* 29 Jun 82 by Ray Pedrizetti (CCP/M-86 3.0) */ /* 24 Aug 82 by Dave Sallume (Translate to C) */ /* 20 Sep 82 by Dominic Dunlop (PCP/M 2.2) */ /* */ /****************************************************************************/ /****************************************************************************/ /* */ /* Command lines used for command file generation */ /* */ /* === To be supplied later for Z8000-based systems === */ /* */ /****************************************************************************/ /****************************************************************************/ /* */ /* W H A T I T D O E S A N D H O W I T W O R K S */ /* */ /* 1 PIP'S FUNCTION */ /* */ /* PIP, the Peripheral Interchange Program, copies data from one */ /* or more peripheral or disk file to another peripheral or disk file. */ /* It is also able to copy all files matching an ambiguous file name */ /* (one containing a wild card such as * or ?) from one disk to */ /* another or from one user number to another. */ /* */ /* Finally, PIP can concatenate data from more than one source file */ /* and/or device, sending the combined data to a single destination */ /* file or device. */ /* */ /* While the copy is taking place, PIP can optionally perform checks */ /* - such as checking that an Intel Hex format file's checksums are */ /* correct, and translations - for example from upper to lower case. */ /* There are also facilites for adding line numbers to output and */ /* formatting it into pages for printing. It is also possible to */ /* specify that only part of a file, delimited by start and end */ /* strings, is copied. */ /* */ /* When the destination is a disk file, PIP first copies all the */ /* source data into a temporary file. Only when the transfer has */ /* completed without errors, and optional verification (if selected) */ /* has checked the data, does PIP delete any existing file with the */ /* destination file name and rename the temporary file to the */ /* destination name. This minimizes the possibility that a failing */ /* transfer will result in the loss of a backup or older version of */ /* the detination file. In the event that a transfer does fail, PIP's */ /* temporary file is not deleted in case it contains usable data. */ /* */ /* 2 PIP'S COMMAND MODES */ /* */ /* PIP has two command modes: single and multiple. In single command */ /* mode the user enters details of a single transfer on the command */ /* line following the name of the program, PIP. For example, */ /* */ /* A>PIP LST:=MYFILE.LST (where A> is the CCP prompt) */ /* A> (prompt from CCP on completion) */ /* */ /* copies A:MYFILE.LST to the LST: logical device (normally a printer) */ /* and returns control to the CCP (Console Command Processor, CP/M's */ /* command interpreter) when the operation is complete. See the next */ /* section for details of valid commands. */ /* */ /* To enter multiple command mode, the user gives PIP no command line */ /* arguments. In response, PIP prompts (with an asterisk) for */ /* commands, and continues to do this until a blank line is entered, */ /* showing that the user has finished with PIP and wishes to return to */ /* the CCP. Suppose the user wants to make a backup copy of a file, */ /* then print it: */ /* */ /* A>PIP (Run PIP In multiple command mode) */ /* *MYFILE.BAK=MYFILE.C (Make backup copy of a file) */ /* *LST:=MYFILE.C (Copy file to printer) */ /* * (User types return only) */ /* A> (Prompt from CCP) */ /* */ /* This sequence of commands is a quicker alternative to running PIP */ /* twice in single command mode to achieve the same effect because PIP */ /* is only loaded from disk once instead of twice. It also reduces the */ /* amount of typing required. */ /* */ /* 3 SOURCES AND DESTINATIONS */ /* */ /* PIP knows about three classes of data source or destination: */ /* logical devices, files and disks. */ /* */ /* 3.1 Logical Devices */ /* */ /* The following table lists the logical devices known to the system. */ /* These devices are mapped to actual physical devices in CP/M's BIOS */ /* (Basic Input/Output System) software. Many CP/M implementations */ /* allow the operator to change the mapping by manipulating a system */ /* variable, IOBYTE either with direct operating system calls or with */ /* the STAT utility. MP/M does not have this facility. */ /* */ /* Name | Function | Src? | Dst? | Comments */ /* --------+-----------------------+-------+-------+--------------- */ /* AXI: | Auxilliary input | Yes | No | Serial input, */ /* | | | | tape reader etc. */ /* AXO: | Auxilliary output | No | Yes | Serial ouput, */ /* | | | | tape punch etc. */ /* CON: | Console | Yes | Yes | Input is keyboard,*/ /* | | | | output is screen */ /* EOF: | End of file character | Yes | No | Dummy device */ /* INP: | (User defined input) | No | No | Obsolete: not */ /* | | | | supported */ /* LST: | List device | No | Yes | System printer */ /* NUL: | Null trailer for paper| Yes | No | Dummy device */ /* | tape runout | | | */ /* PRN: | Print on 66 line pages| No | Yes | Actually same */ /* | with line numbers | | | device as LST: */ /* OUT: | (User defined output) | No | No | Obsolete: not */ /* | | | | supported */ /* */ /* 3.2 Files */ /* */ /* File names come in two forms: unambiguous and ambiguous. Both have */ /* the form */ /* */ /* d:filename.typ;password */ /* */ /* where d: is an optional disk name. If missing, the */ /* current disk is assumed */ /* filename is a 1 to 8 character name consisting of */ /* alphabetic, numeric underscore or wildcard */ /* characters (* or ?) */ /* .ext is an optional file type (1 to 3 of the */ /* characters which may appear in filename.) */ /* If missing, presumed blank */ /* password is a 1 to 8 character password. If not */ /* given for a file which requires a password, */ /* PIP prompts the user for one. Passwords */ /* are implemented only in MP/M and CP/M */ /* version 3.0 or later */ /* */ /* The difference between unambiguous and ambiguous filenames is that */ /* the latter contain one or more wildcard while the former do not. */ /* An asterisk anywhere in a filename makes that name match all names. */ /* A question mark specifies a single "don't care" character. Similar */ /* rules apply to file types. */ /* */ /* Ambiguous filenames are not allowed as destinations, nor may they */ /* appear in or in place of lists of source files to be concatenated. */ /* They are used in copying groups of files from one disk or user to */ /* another. */ /* */ /* Files are held on disk and come in two flavors: ASCII and binary. */ /* A control-Z in an ASCII file marks the logical end of the file. */ /* Control-Z has no special significance in a binary file. In most */ /* cases this distinction is unimportant to PIP. The only time when */ /* it needs to know that it is not dealing with an ASCII file is when */ /* it is concatenating non-ASCII files (see 4.x below). The O option */ /* (section 5) should be used in this case. */ /* */ /* Files may be dense or sparse. Suppose the last record in a file */ /* has logical record number 7. If 8 physical records (sectors) */ /* (zero through seven) on the disk have been allocated to that file, */ /* then the file is dense: there is a physical sector allocated for */ /* each logical sector, and the physical size of the file is the same */ /* as the logical size. This is the normal case: files created by the */ /* editor, object files, command files, and most files created by */ /* programs have this structure. A sparse file, on the other hand, */ /* has fewer physical records than logical records. Thus a file with */ /* 8 logical sectors might have as few as one physical sector. Such */ /* files can only be created with the CP/M random write system call, */ /* and are typically used by specialized programs such as database */ /* managers and indexed access file handlers. */ /* */ /* PIP automatically accomodates both dense and sparse files and */ /* copies only those sectors which are actually allocated in the */ /* source when copying a sparse file. The resulting destination file */ /* is also sparse. PIP does not allow sparse files to be concatenated.*/ /* */ /* 3.3 Disks */ /* */ /* A disk name has the form d: where d is a letter from A through P. */ /* Disk names are used by PIP as a form of shorthand to cut down on */ /* the number of keystrokes needed to achieve a given effect. In */ /* effect, the disk name stands in for a filename or list of filenames */ /* which the user has specified elsewhere on the same command line. */ /* 4.4 and 4.6 below show the two cases where as disk name may be used.*/ /* */ /* 4 PIP COMMANDS */ /* */ /* The general format of PIP commands is */ /* */ /* dest[options]=source1[options],source2[options]... */ /* */ /* where dest is a disk, a logical device or an unambig- */ /* uous filename (no wildcards) */ /* source1 is a disk, a logical device, or an */ /* ambiguous or unambiguous file name */ /* source2 is a logical device or unabiguous file name */ /* (there may be more than one, separated by */ /* commas) */ /* options enclosed in square brackets may appear */ /* after any destination or source */ /* */ /* Not all possible combinations are valid. The remainder of this */ /* section details those which are. For details of valid options, see */ /* the next section. */ /* */ /* 4.1 Device to device copy */ /* */ /* dst:=src: */ /* */ /* copies data from the logical device src: to the logical device */ /* dst:. Both src: and dst: are three-character logical device names */ /* followed by a colon. Note that a single letter followed by a colon */ /* is NOT a logical device name, but a disk name (see below.) For */ /* example, */ /* */ /* LST:=AXI: */ /* */ /* copies data from the auxiliary input (typically a serial line or */ /* tape reader) to the list device, typically a printer. */ /* */ /* 4.2 File to device copy */ /* */ /* src:=ufn (ufn is an unambiguous file name) */ /* */ /* copies a single file with an unambiguous name to a destination */ /* device. For example to print the file PROGRAM.LST, */ /* */ /* PRT:=PROGRAM.LST */ /* */ /* 4.3 Device to file copy */ /* */ /* ufn=dst: */ /* */ /* copies data from a logical device to a file. This function is */ /* useful for reading disk files from paper tape and for creating */ /* short text files from the keyboard. These two functions would be */ /* performed by */ /* */ /* B:PROGRAM.ASM=AXI: */ /* COMMAND.SUB=CON: */ /* */ /* 4.4 File to file copy */ /* */ /* ufn=ufn ufn=d: d:=ufn */ /* */ /* copies one unambiguously named file to another. For example, to */ /* make a backup copy of a file VALUABLE.DAT in VALUABLE.BAK, use */ /* */ /* VALUABLE.BAK=VALUABLE.DAT */ /* */ /* File attributes and, in the case of CP/M 3.0 and MP/M, extended file*/ /* information are copied along with the file. Thus a copy of a read- */ /* only file is automatically made read only, and a copy of an MP/M */ /* file with password protection on write has similar protection. */ /* */ /* Special cases of this command can be used to copy an unambiguously */ /* named file from one disk to a file of the same name on another */ /* disk. To copy VALUABLE.DAT on drive A to VALUABLE.DAT on drive B, */ /* either */ /* */ /* B:VALUABLE.DAT=A: or B:=A:VALUABLE.DAT */ /* */ /* does the job. Both are shorter than the long form */ /* */ /* B:VALUABLE.DAT=A:VALUABLE.DAT */ /* */ /* 4.5 Concatenation */ /* */ /* dst=src1,src2,...srcn */ /* */ /* The commands of 4.1 to 4.4 (except the special cases of 4.4) can */ /* be generalized to concatenate data from a variety of sources into a */ /* single destination file or device. Each argument is a device */ /* name or unambiguous file name. PIP reads each source in turn, */ /* copying it to the destination. For example, to append data from */ /* paper tape to an existing file: */ /* */ /* BIGGER.SRC=SMALLER.SRC,AXI: */ /* */ /* 4.6 Multiple file copy */ /* */ /* d:=afn d:[Gn]=afn (afn is an ambiguous file name) */ /* */ /* When an ambiguous filename is used as a source, PIP recognizes that */ /* it must copy each file matched by the source name to a destination */ /* file of the same name, but on a different disk or belonging to a */ /* different user (the Gn option specifies user n.) It is an error to */ /* try copying files belonging to one user on one disk to the same */ /* user on the same disk! As an example, to copy all .TXT files from */ /* the current drive to drive B (which must not be the current drive): */ /* */ /* B:=*.TXT */ /* */ /* To copy all files belonging to the current user on drive B to user */ /* 7 (who must not be the current user) on drive B: */ /* */ /* B:[G7]=B:*.* */ /* */ /* 5 OPTIONS */ /* */ /* A list of options enclosed in square brackets may follow any file */ /* or device name. Each option consists of a letter, possibly followed*/ /* by a decimal number n or a control-Z terminated string s. Where an */ /* option requires neither string nor number, any following number or */ /* string is misinterpreted as more options. An error always results. */ /* A missing number is taken to be 1. There need be no separators */ /* between options, though spaces are allowed. Most combinations of */ /* options are accepted without objection, although some make little */ /* sense and may produce unexpected results. */ /* */ /* Any letter is accepted as an option, although some (for example B) */ /* have no effect. The following table lists implemented options */ /* and the context (following source or destination in which they make */ /* sense: */ /* */ /* Opt | Src | Dst | Description */ /* -----+-----+-----+---------------------------------------------- */ /* A | Yes | No | Copy only those disk files which DO NOT have the */ /* | | | archive bit set in at least one extent */ /* | | | (selective back-up of modified files) */ /* Dn | Yes | No | Delete characters after column n (truncate for */ /* | | | narrow paper or screen) */ /* E | Yes | No | Echo data to console as it is transfered */ /* F | Yes | No | Remove form feeds from data as it is copied */ /* Gn | Yes | Yes | Specify (Get) user number for source or */ /* | | | destination disk file */ /* H | Yes | No | Check that the data is a valid Intel Hex file */ /* I | Yes | No | Ignore (do not transfer) final :00 record in */ /* | | | Intel Hex file (used for concatenating files) */ /* L | Yes | No | Translate lower case alphabetic to upper */ /* Nn | Yes | No | Prepend line numbers to output data. Leading 0's*/ /* | | | not supressed if n=2 */ /* O | Yes | No | Object file. Ignore end-of-file marks */ /* Pn | Yes | No | Output page eject (form feed) every n lines */ /* | | | (default 60) */ /* Qs | Yes | No | Quit (stop) transfering data after the string s */ /* | | | has been copied */ /* R | Yes | No | Read system files (system attribute set). By */ /* | | | default such files are not read */ /* S | Yes | No | Start transfering data when the string s is */ /* | | | found in input data. s is first data copied */ /* Tn | Yes | No | Expand tabs at every n columns (default 8) */ /* U | Yes | No | Translate lower case alphabetic to upper */ /* V | Yes | No | Check destination disk file for readability (not */ /* | | | correctness) after transfer complete */ /* W | Yes | No | Overwrite read-only destination file (supress */ /* | | | PIP's normal query to user before overwriting) */ /* Z | Yes | No | Zero parity bit. Normally parity bit is copied */ /* */ /* 6 HOW IT WORKS */ /* */ /* The best way to understand this somewhat knotty program is to start */ /* at the _main function which is to be found at the end of this */ /* listing and work backwards to progressively lower level functions */ /* as and when it becomes necessary to know what they do and how they */ /* do it. */ /* */ /* For historical reasons, much of the communication between functions */ /* is accomplished with global variables rather than parameters, */ /* resulting in long-range interdependencies which may be hard to */ /* follow. A cross-reference listing is useful in tracking these */ /* down. All the global variables are global because they have to be */ /* that way: if you see one, at least two functions use it. */ /* */ /* Dominic Dunlop, Zilog Inc. 821022 */ /* */ /****************************************************************************/ /****************************************************************************/ /* */ /* E X T E R N A L A N D B D O S I N T E R F A C E */ /* ------------------------------------------------------ */ /* */ /****************************************************************************/ #include "portab.h" /* Portable program defs */ #include "bdos.h" /* BDOS calls & structures */ #include "basepage.h" /* CP/M base page structure */ #include "setjmp.h" /* Non-local goto */ extern BYTE *sbrk(); /* Memory allocator */ struct fcbtab *fcb; /* Default file control blk */ char *buff; /* Default DMA buffer */ char copyright[] = " (10/04/82) Portable CP/M PIP vers 1.0 "; /****************************************************************************/ /* Version dependencies */ /****************************************************************************/ #define CPM 0x0000 /* Vanilla flavor CP/M */ #define MPM 0x1000 /* MP/M */ #define PCPM 0x2000 /* Portable CP/M */ #define ONE_X 0x00 /* Version 1.x */ #define TWO_X 0x20 /* Version 2.x */ #define THREE_X 0x30 /* Version 3.x */ #define IS_MPM(x) (((x) & 0xff00) == MPM) #define HAS_GET_DFS(x) ((((x) & 0xff00) != CPM) || (((x) & 0xf0) >= THREE_X)) #define HAS_GSET_SCB(x) (((x) & 0xf0) >= THREE_X) #define HAS_RETERR(x) ((((x) & 0xff00) == MPM) || (((x) & 0xf0) >= THREE_X)) #define HAS_SETMSC(x) ((((x) & 0xff00) == MPM) || (((x) & 0xf0) >= THREE_X)) #define HAS_XFCBS(x) ((((x) & 0xff00) == MPM) || (((x) & 0xf0) >= THREE_X)) #define VOID_GET_DPB(x) (((x) & 0xff00) == PCPM) /****************************************************************************/ /* */ /* G E N E R A L D E F I N I T I O N S */ /* ------------------------------------- */ /* */ /* NOTE: definitions related to particular data structures appear */ /* adjacent to the definition of the structure */ /* */ /****************************************************************************/ /****************************************************************************/ /* Useful charcaters */ /****************************************************************************/ #define TAB 0x09 /* Horizontal TAB */ #define FF 0x0c /* Form feed */ #define CR 0x0d /* Carriage return */ #define LF 0x0a /* Line feed */ #define ENDFILE 0x1a /* CP/M end of file mark */ #define WILD '?' /* Filename wildcard */ /****************************************************************************/ /* Lengths of filename fields */ /****************************************************************************/ #define L_NAME 8 /* Length of filename */ #define L_PASS 8 /* Length of password */ #define L_TYPE 3 /* Length of file type */ /****************************************************************************/ /* Types for source and destination in copy */ /****************************************************************************/ #define OUTT 0 /* Output device */ #define PRNT 1 /* Printer */ #define LSTT 2 /* List device */ #define AXOT 3 /* Auxilary output device */ #define FILE 4 /* File type */ #define CONS 5 /* Console */ #define AXIT 6 /* Auxilary input device */ #define INPT 7 /* Input device */ #define NULT 8 /* Null characters */ #define EOFT 9 /* EOF character */ #define ERR 10 /* Error type */ #define DISKNAME 11 /* Diskname letter */ /****************************************************************************/ /* Constants associated with disk layout etc. */ /****************************************************************************/ #define C_MAXMBUF (C_MAXMCNT * SECSIZE) /* CPM/3 max transfer length*/ #define C_MAXMCNT ((UWORD) 128) /* CP/M-3 max multi sec cnt */ #define EXTMSK ~0x7f /* Ignore low-order bits */ #define EXTRECS 128 /* Records per extent */ #define M_MAXMBUF (M_MAXMCNT * SECSIZE) /* CPM/3 max transfer length*/ #define M_MAXMCNT ((UWORD) 16) /* MP/M max multi sector cnt*/ #define MP_SHF 8 /* Converts MP/M multi- */ /* sector return to bytes */ #define SECMSK ~0x7f /* Ignore low-order bits */ #define SECSIZE 128 /* No of bytes in record */ /****************************************************************************/ /* File attribute fields in FCB */ /****************************************************************************/ #define F1 fname[0] /* User flag #1 */ #define F2 fname[1] /* User flag #2 */ #define F3 fname[2] /* User flag #3 */ #define F4 fname[3] /* User flag #4 */ #define ASSIGN_PW fname[5] /* Assign password */ #define NO_WRITE fname[6] /* You can't write this file*/ #define USER_0 fname[7] /* Can be read by all users */ #define R_O ftype[0] /* Nobody can write file */ #define SYSTEM ftype[1] /* "Invisible" file */ #define ARC ftype[2] /* Archived file */ /****************************************************************************/ /* Any other business */ /****************************************************************************/ #define DEF_TAB 8 /* Default tab stops */ #define FORCE_READ 0xffff /* Index causes source read */ #define LPP 60 /* Lines per printer page */ #define MAXDRIVE ('P' - 'A' + 1) /* Max valid drive number */ #define MAXUSER 0xf /* Max valid user number */ #define NIBBLE 4 /* Bits in a nibble */ #define NONE 0 /* No extended error code */ #define NULLS 40 /* Length of null trailer */ #define SAFETY 1024 /* Stack margin in sbrk call*/ #define SEARFCB fcb /* Search fcb in multi copy */ /****************************************************************************/ /* */ /* G L O B A L D A T A */ /* --------------------- */ /* */ /****************************************************************************/ /****************************************************************************/ /* Simple types */ /****************************************************************************/ BYTE f1; /* F1 user attribute flag */ BYTE f2; /* F2 user attribute flag */ BYTE f3; /* F3 user attribute flag */ BYTE f4; /* F4 user attribute flag */ BYTE ro; /* Read only attribute flag */ BYTE sys; /* System attribute flag */ BOOLEAN ambig; /* File is ambig type */ BOOLEAN concat; /* Concatination command */ BOOLEAN dblbuf; /* Double buffering needed */ BOOLEAN dfile; /* Dest is file type */ BOOLEAN endofsrc; /* End of source file */ BOOLEAN eretry; /* Error return flag */ BOOLEAN fastcopy; /* Copy directly to dbuf */ BOOLEAN getpw; /* No dst passwd given */ BOOLEAN insparc; /* In middle of sparse file */ BOOLEAN made; /* Dest file already made */ BOOLEAN multcom; /* Handling multiple command*/ BOOLEAN nendcmd; /* Not end of command tail */ BOOLEAN putnum; /* Ready for next line num */ BOOLEAN sfile; /* Source is file type */ BOOLEAN sparfil; /* Sparce file being copied */ char ch; /* Last character scanned */ char *dbase; /* Destination buffer base */ char *sbase; /* Source buffer base */ /****************************/ /* Note: size of buffers */ /* depends on available */ /* memory */ /****************************/ UWORD bufsize; /* Multi sector buffer size */ UWORD cdisk; /* Current disk */ UWORD column; /* Column count for tabs */ UWORD concnt; /* Counter for abort check */ UWORD c_user; /* Current user number */ UWORD dblen; /* Dest buffer length */ UWORD dcnt; /* Error/directory code */ UWORD exten; /* Extended error code */ UWORD feedbase; /* String search base */ UWORD feedlen; /* Length of search string */ UWORD matchlen; /* Lenght of matched string */ UWORD ndest; /* Index of next dest char */ UWORD nsbuf; /* Next source sector index */ UWORD nsource; /* Index of next src char */ UWORD odcnt; /* Return from open of dest */ UWORD page_line; /* Line within page */ UWORD quitlen; /* Length of quit string */ UWORD sblen; /* Source buffer length */ UWORD ver; /* CP/M version number */ long filsize; /* File size (24 bits only) */ long line_no; /* Line count on printer */ /****************************************************************************/ /* Character arrays */ /****************************************************************************/ /* Option toggles, one for */ /* each letter (although */ char cont[26]; /* some letters unused) */ /****************************/ /* Functions of elements in */ /* cont[]. Most act as */ /* boolean, though some */ /* hold numeric values */ /****************************/ #define ARCHIV cont[0] /* File archive */ #define DELET cont[3] /* Delete characters */ #define ECHO cont[4] /* Echo console characters */ #define FORMF cont[5] /* Form filter */ #define GETU cont[6] /* Get file, user number */ #define HEXT cont[7] /* Hex file transfer */ #define IGNOR cont[8] /* Ignore :00 record on file*/ #define KILDS cont[10] /* Kill filename display */ #define LOWER cont[11] /* Translate to lower case */ #define NUMB cont[13] /* Number output lines */ #define OBJ cont[14] /* Object file transfer */ #define PAGCNT cont[15] /* Page length */ #define QUITS cont[16] /* Quit copy */ #define RSYS cont[17] /* Read system files */ #define STARTS cont[18] /* Start copy */ #define TABS cont[19] /* TAB set */ #define UPPER cont[20] /* Upper case translate */ #define VERIF cont[21] /* Verify equal files only */ #define WRROF cont[22] /* Write to r/o file */ #define ZEROP cont[25] /* Zero parity on input */ /****************************************************************************/ /* Standard and extended error messages */ /****************************************************************************/ char *errmsg[] = /* Standard messages */ { "DISK READ", /* 0 */ "DISK WRITE", /* 1 */ "VERIFY", /* 2 */ "INVALID DESTINATION", /* 3 */ "INVALID SOURCE", /* 4 */ "USER ABORTED", /* 5 */ "BAD PARAMETER", /* 6 */ "INVALID USER NUMBER", /* 7 */ "INVALID FORMAT", /* 8 */ "HEX RECORD CHECKSUM", /* 9 */ "FILE NOT FOUND", /* 10 */ "START NOT FOUND", /* 11 */ "QUIT NOT FOUND", /* 12 */ "INVALID HEX DIGIT", /* 13 */ "CLOSE FILE", /* 14 */ "UNEXPECTED END OF HEX FILE", /* 15 */ "INVALID SEPARATOR", /* 16 */ "NO DIRECTORY SPACE", /* 17 */ "INVALID FORMAT WITH SPARCE FILE",/*18 */ "MAKE FILE", /* 19 */ "OPEN FILE", /* 20 */ "PRINTER BUSY", /* 21 */ "CAN'T DELETE TEMP FILE", /* 22 */ "OBSOLETE FEATURE" /* 23 */ }; char *extmsg[] = /* Extended error messages */ { "", /* 0 */ "NON-RECOVERABLE", /* 1 */ "R/O DISK", /* 2 */ "R/O FILE", /* 3 */ "INVALID DISK SELECT", /* 4 */ "INCOMPATIBLE MODE", /* 5 */ "FCB CHECKSUM", /* 6 */ "INVALID PASSWORD", /* 7 */ "ALREADY EXISTS", /* 8 */ "INVALID FILENAME", /* 9 */ "LIMIT EXCEEDED", /* 10 */ "INTERNAL LOCK LIMIT EXCEEDED" /* 11 */ }; #define NUMMSGS (sizeof extmsg / sizeof (char *)) /****************************************************************************/ /* Option - transfer type mapping table */ /****************************************************************************/ #define FAST 0 /* Fast file to file copy */ #define CHRT 1 /* Character transfer option*/ #define DUBL 2 /* Double buffer required */ char optype[26] = { FAST, /* A option */ FAST, /* B option */ FAST, /* C option */ DUBL, /* D option */ CHRT, /* E option */ DUBL, /* F option */ FAST, /* G option */ CHRT, /* H option */ DUBL, /* I option */ FAST, /* J option */ FAST, /* K option */ CHRT, /* L option */ FAST, /* M option */ DUBL, /* N option */ FAST, /* O option */ DUBL, /* P option */ DUBL, /* Q option */ FAST, /* R option */ DUBL, /* S option */ DUBL, /* T option */ CHRT, /* U option */ FAST, /* V option */ FAST, /* W option */ FAST, /* X option */ FAST, /* Y option */ CHRT /* Z option */ }; /****************************************************************************/ /* Logical device name table */ /****************************************************************************/ #define DEVLEN 3 /* Length of device name */ #define TYPES (sizeof io / DEVLEN) /* Number of devices */ char io[] [DEVLEN] = /* Logical device names */ { 'O', 'U', 'T', 'P', 'R', 'N', 'L', 'S', 'T', 'A', 'X', 'O', 0, 0, 0, /* Dummy for file type */ 'C', 'O', 'N', 'A', 'X', 'I', 'I', 'N', 'P', 'N', 'U', 'L', 'E', 'O', 'F', }; /****************************************************************************/ /* Structures */ /****************************************************************************/ jmp_buf main_stack; /* Used in error recovery */ /****************************************************************************/ /* Console command buffer */ /****************************************************************************/ struct { BYTE maxlen; /* Max buffer length */ BYTE comlen; /* Current length */ char comline[128]; /* Command buffer contents */ UWORD cbp; /* Command buffer pointer */ } combuf; /****************************************************************************/ /* File control blocks, expanded to carry password and device type data */ /****************************************************************************/ struct flctlb { struct fcbtab flfcb; char pwnam[L_PASS]; BYTE pwmode; BYTE user; BYTE type; } source; /* Source file description */ struct flctlb dest; /* Temporary destination */ /* file (scratch file) */ struct flctlb odest; /* True destination file */ struct fcbtab dxfcb; /* Extended FCB (XFCB) for */ /* destination file */ struct flctlb empty_fcb = /* Empty for initialization */ { 0, /* Drive, name, */ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', /* type, extent, s1, s2, */ 0, 0, 0, 0, /* record, reserved, */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* current & random record*/ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 1, /* password, passwd mode, */ 0, /* user number, */ ERR /* file type */ }; /****************************************************************************/ /* */ /* L O W L E V E L F U N C T I O N S */ /* ------------------------------------- */ /* */ /****************************************************************************/ /********************************/ /* */ /* G E T C H */ /* */ /********************************/ #define getch(a) (a = gnc()) /* get a char in a with gnc*/ /********************************/ /* */ /* C R L F */ /* */ /********************************/ VOID /* Send carriage-return, line*/ crlf() /* feed to console */ { _conout(LF); _conout(CR); } /********************************/ /* */ /* P R I N T X */ /* */ /********************************/ VOID /* Print a null-terminated */ printx(a) /* string on the console */ register char *a; { while (*a) _conout(*a++); } /********************************/ /* */ /* P R I N T */ /* */ /********************************/ VOID /* Print a null-terminated */ print(a) /* string on a new line */ char *a; /* on the console */ { crlf(); printx(a); } /********************************/ /* */ /* R D C O M */ /* */ /********************************/ VOID /* Read a command line from */ rdcom() /* the console into combuf*/ { combuf.maxlen = sizeof combuf.comline; /* 128 characters maximum */ _conbuf(&combuf); } /********************************/ /* */ /* P R N A M E */ /* */ /********************************/ VOID /* Print a file name */ prname(fcba) struct fcbtab *fcba; { register int i, c; for (i = 0; i < L_NAME + L_TYPE; i++) /* Do not print spaces */ if ((c = fcba->fname[i] & 0x7f) != ' ') { if (i == L_NAME) /* '.' separates name & type*/ _conout('.'); _conout(((c<0x20)||(c>0x7e))?'?':c); } } /********************************/ /* */ /* O P E N */ /* */ /********************************/ UWORD /* Open a file, using passwd*/ open(fcba) /* on MP/M, CP/M 3 */ struct flctlb *fcba; /* Returns error code */ { if (HAS_XFCBS(ver)) /* If password required, */ _setdma(fcba->pwnam); /* show where it is */ dcnt = _open(fcba); /* Try the open */ if ((dcnt != 255) /* If it worked, but found */ && (fcba->flfcb.USER_0 & 0x80)) /* a user zero file, */ { /* behave as if it failed */ _close(fcba); dcnt = 255; } exten = dcnt >> 8; /* Separate low & high bytes*/ return (dcnt &= 0xff); /* of error code */ } /********************************/ /* */ /* C L O S E */ /* */ /********************************/ UWORD /* Close a file. Returns */ close(flcb) /* error code */ struct flctlb *flcb; { dcnt = _close(flcb); exten = dcnt >> 8; return (dcnt &= 0xff); } /********************************/ /* */ /* S E A R C H */ /* */ /********************************/ UWORD /* Search for the first file*/ search(flcb) /* with a name matching */ struct flctlb *flcb; /* that in flcb */ { /* Delivers directory record*/ dcnt = _srch_1st(flcb); /* containing match in */ exten = dcnt >> 8; /* current DMA buffer */ return (dcnt &= 0xff); /* Returns index/error code */ } /********************************/ /* */ /* S E A R C H N */ /* */ /********************************/ UWORD /* Search for the next file*/ searchn() /* with a name matching */ { /* that set up by search()*/ /* Delivers directory record*/ dcnt = _srch_next(); /* containing match in */ exten = dcnt >> 8; /* current DMA buffer */ return (dcnt &= 0xff); /* Returns index/error code */ } /********************************/ /* */ /* D E L E T E */ /* */ /********************************/ UWORD /* Delete a file, giving */ delete(flcb) /* password if it might */ struct flctlb *flcb; /* be necessary */ { /* Returns error code */ if (HAS_XFCBS(ver)) _setdma(flcb->pwnam); dcnt = _delete(flcb); exten = dcnt >> 8; return (dcnt &= 0xff); } /********************************/ /* */ /* D I S K R D */ /* */ /********************************/ UWORD /* Read file sequentially */ diskrd(flcb) /* Reads no of records set */ struct flctlb *flcb; /* by last call to */ { /* setmsc(), or 1 if */ dcnt = _s_read(flcb); /* function not available*/ exten = dcnt >> 8; /* Returns error code */ return (dcnt &= 0xff); } /********************************/ /* */ /* D I S K W R I T E */ /* */ /********************************/ UWORD /* Write file sequentially */ diskwrite(flcb) /* Writes no of records set*/ struct flctlb *flcb; /* by last call to */ { /* mulsect(), or 1 if */ dcnt = _s_write(flcb); /* function not available*/ exten = dcnt >> 8; /* Returns error code */ return (dcnt &= 0xff); } /********************************/ /* */ /* M A K E */ /* */ /********************************/ UWORD /* Create a new file, */ make(fcba) /* assigning password if */ struct flctlb *fcba; /* necessary */ { /* Returns error code */ if (HAS_XFCBS(ver)) /* Password allowed? */ if (fcba->pwnam[0] == 0) /* Yes: wanted? */ fcba->flfcb.ASSIGN_PW &= 0x7f; /* No */ else /* Password is wanted */ { fcba->flfcb.ASSIGN_PW |= 0x80; _setdma(fcba->pwnam); /* Show where it is */ } dcnt = _create(fcba); exten = dcnt >> 8; return (dcnt &= 0xff); } /********************************/ /* */ /* R E N A M E */ /* */ /********************************/ UWORD /* Rename file, giving pass-*/ rename(flcb) /* word if it might be */ struct flctlb *flcb; /* necessary */ { /* Returns error code */ if (HAS_XFCBS(ver)) _setdma(flcb->pwnam); /* Say where password is */ dcnt = _rename(flcb); exten = dcnt >> 8; return (dcnt &= 0xff); } /********************************/ /* */ /* S E T A T T */ /* */ /********************************/ UWORD /* Set the attributes of a */ setatt(flcb) /* file (=== assumes pass-*/ struct flctlb *flcb; /* word already set up if */ { /* required===) */ dcnt = _set_att(flcb); /* Returns error code */ exten = dcnt >> 8; return (dcnt &= 0xff); } /********************************/ /* */ /* S E T U S E R */ /* */ /********************************/ VOID /* Set the user code */ setuser(user) BYTE user; { static BYTE last_user = 0; /* Remember last user code */ if (last_user != user) /* Only call BDOS if user */ _gset_ucode(last_user = user); /* has changed */ } /********************************/ /* */ /* M U L T S E C T */ /* */ /********************************/ VOID /* Set multisector count */ multsect(cnt) UWORD cnt; { static last_count = 1; /* Remember last count */ if (! HAS_SETMSC(ver)) /* Do nothing if call not */ return; /* supported */ if (last_count != cnt) /* Only call BDOS if new */ _setmsc(last_count = cnt); /* count differs from last*/ } /********************************/ /* */ /* M O V E */ /* */ /********************************/ VOID /* Move n bytes from source */ move(s, d, n) /* to destination */ register char *s, *d; register UWORD n; { if (!n) return; do *d++ = *s++; while (--n); } /****************************************************************************/ /* */ /* E R R O R R E P O R T I N G */ /* ----------------------------- */ /* */ /****************************************************************************/ /********************************/ /* */ /* E R R O R */ /* */ /********************************/ error(errtype, exended, retflag, fileadr) /* Called following errors */ UWORD errtype, exended; /* which terminate current*/ BOOLEAN retflag; /* command, prints message*/ struct flctlb *fileadr; /* cleans up and returns */ { /* control to main() */ eretry = retflag & ambig; /* Error retry = retry flag */ multsect(1); /* Forget current msc */ if (sfile) /* If source is a file, */ { /* close it */ setuser(source.user); close(&source); } if (made) /* If destination scratch */ { /* file already created, */ setuser(odest.user); /* delete it */ close(&dest); delete(&dest); /* Delete dest scratch file */ } print("ERROR: "); /* Print requested message */ printx(errmsg[errtype]); if ((exended &= 0x0f) < NUMMSGS) /* and extended message if*/ { /* there is one */ _conout(' '); printx(extmsg[exended]); } if (fileadr != 0) /* If error involves a file,*/ { /* incriminate it */ printx(" - "); _conout(fileadr->flfcb.drive + 'A' - 1); _conout(':'); prname(&fileadr->flfcb); } combuf.comlen = 0; /* Trash current cmd line */ crlf(); longjmp(main_stack, TRUE); /* Restart from top of main()*/ } /********************************/ /* */ /* N O N F I L E _ E R R O R */ /* */ /********************************/ VOID /* Same as above, but with */ nonfile_error(errtype) /* defaults for all but */ UWORD errtype; /* first parameter */ { error(errtype, NONE, FALSE, (struct flctlb *) 0); } /****************************************************************************/ /* */ /* F I L E I N P U T / O U T P U T */ /* ----------------------------------- */ /* */ /****************************************************************************/ /********************************/ /* */ /* S E T U P D E S T */ /* */ /********************************/ VOID /* Set up the destination */ setupdest() /* file */ { setuser(odest.user); /* Set up destination user */ dest = odest; /* Temporary destination */ move("$$$", dest.flfcb.ftype, L_TYPE); /* has original name but */ /* "$$$" type */ if (HAS_XFCBS(ver)) /* Are we worrying about */ { /* passwords? */ odest.flfcb.ASSIGN_PW |= 0x80; /* Yes: tell BDOS */ odcnt = open(&odest.flfcb); /* Try to open dest file */ /* and save error code */ if (odcnt != 255) /* If it exists, close it */ close(&odest); else /* If file exists, but we */ if ((exten & 0x0f) != 0)/* can't open it, error */ error(20, exten, TRUE, &odest); if ((delete(&dest) == 255) /* Try to delete old temp */ && (exten != 0)) /* file. If it exists but*/ /* can't be deleted, error*/ error(22, exten, TRUE, &dest); if (make(&dest) == 255) /* Create new temp file */ error(19, exten, FALSE, &dest);/* Make file error */ } else /* No passwords */ { delete(&dest); /* Remove old temp file */ if (make(&dest) == 255) /* Create a new one */ error(17, NONE, FALSE, &dest);/* No directory space */ } dest.flfcb.record &= 0x00ffffff; /* Current record is 0 */ made = TRUE; /* Show temp file now made */ } /********************************/ /* */ /* S E T U P S O U R C E */ /* */ /********************************/ VOID /* Prepare to use a source */ setupsource() /* file */ { register int i; setuser(source.user); /* Set up source user */ if (HAS_XFCBS(ver)) /* Do we have to worry about*/ source.flfcb.ASSIGN_PW |= 0x80; /* passwords? */ open(&source.flfcb); /* Open source file */ if ((! RSYS) /* Do we read system files? */ && (source.flfcb.SYSTEM & 0x80)) /* Is this one? */ dcnt = 255; /* Yes: pretend open failed */ if (dcnt == 255) /* Was there an error? */ /* Report error, retry if */ /* extended */ error(20, exten, ((exten & 0x0f) != 0), &source); if (HAS_XFCBS(ver) && getpw) /* Did user give dest passwd*/ { /* No: get it from source */ dxfcb = source.flfcb; if (_get_xfcb(&dxfcb) == 255) /* Get source file's XFCB */ dest.pwnam[0] = 0; /* Not found: no password */ else /* Has a password */ { /* Get "decrypted" copy */ for (i = 0; i < sizeof dest.pwnam; i++ ) dest.pwnam[i] = dxfcb.resvd[sizeof dest.pwnam - i - 1] ^ dxfcb.s1; dest.pwmode = dxfcb.extent;/* Copy password mode */ } } f1 = source.flfcb.fname[0] & 0x80; /* Save source attributes */ f2 = source.flfcb.fname[1] & 0x80; f3 = source.flfcb.fname[2] & 0x80; f4 = source.flfcb.fname[3] & 0x80; ro = source.flfcb.ftype[0] & 0x80; sys = source.flfcb.ftype[1] & 0x80; _filsiz(&source); /* Find size of source file */ filsize = source.flfcb.record & 0xffffff; source.flfcb.record = 0; /* Clear random record no. */ nsource = FORCE_READ; /* Force read from source */ } /* before write to dest */ /********************************/ /* */ /* W R I T E D E S T */ /* */ /********************************/ VOID /* Write data from output */ writedest() /* buffer to destination */ { /* file. On entry, ndest */ BOOLEAN dataok; /* indexes the byte beyond*/ register UWORD j, tdest, n; /* the last to be written.*/ UWORD chunk; /* Writes whole sectors only*/ if (! made) /* Create output file if not*/ setupdest(); /* already done */ if ((n = ndest & SECMSK) == 0) /* Return if nothing to do */ return; tdest = 0; /* Bytes written so far */ setuser(odest.user); /* Set destination user */ if (sparfil |= insparc) /* Is this a sparse file */ { /* (fewer physical than */ /* virtual records)? */ multsect(1); /* Yes: write one sector at */ _setdma(&dbase[tdest]); /* a time (random record */ if (_b_write(&dest) == 255) /* addr already set up) */ error(1, exten, FALSE, &dest);/* Disk write error */ } else /* Not sparse: */ _set_rand(&dest.flfcb); /* Set base rec for verify */ if (HAS_SETMSC(ver) && fastcopy) /* Can we write more than */ { /* on sector at a time? */ bufsize = (IS_MPM(ver)) ? /* Yes. */ M_MAXMBUF : C_MAXMBUF; multsect((IS_MPM(ver)) ? M_MAXMCNT : C_MAXMCNT); } else /* No. Too bad. */ { bufsize = SECSIZE; multsect(1); } /* Strange code overcomes */ /* unsigned compare bug */ while ((0xffff & (long) (chunk = n - tdest)) >= (long) SECSIZE) /* Write loop */ { if (HAS_SETMSC(ver) /* Is this the last chunk in*/ && fastcopy /* a copy using multiple */ && ((0xffff & (long) chunk) < (long) bufsize)) { bufsize = chunk; /* Reduce multi sector count*/ multsect(bufsize / SECSIZE);/* to correct value */ } _setdma(&dbase[tdest]); /* Adjust DMA start address */ if (diskwrite(&dest) != 0) /* Write the data */ error(1, exten, FALSE, &dest); /* Disk write error */ tdest += bufsize; } if (VERIF) /* Read-after write verify */ { /* required? */ tdest = 0; /* Yes: start from the */ multsect(1); /* beginning of data just */ _setdma(buff); /* written. Read one sec */ /* at a time into default */ /* DMA buffer */ while ((0xffff & (long) tdest) < (long) n) /* Verification loop */ { /* Read random record */ dataok = (_b_read(&dest.flfcb) == 0); #ifdef HILO dest.flfcb.record++; /* Move to next random rec */ #else /* - just how depends on */ dest.flfcb.record += 256;/* processor byte ordering*/ #endif /* Perform comparison */ for (j = 0; j < SECSIZE; j++) if (dataok |= (buff[j] == dbase[tdest + j])) break; tdest += SECSIZE; if (! dataok) error(2, NONE, FALSE, &dest);/* Verify error*/ } diskrd(&dest); /* Done: move to next */ } /* sequential record */ /* Move unwritten tail down */ /* to buffer start */ move(&dbase[tdest], dbase, (UWORD)(BYTE)(ndest -= tdest)); } /********************************/ /* */ /* F I L L S O U R C E */ /* */ /********************************/ VOID /* Fill the source buffer */ fillsource() /* from the current source*/ { /* file */ BYTE extsave; if (HAS_SETMSC(ver) && fastcopy) /* Are we able to read more */ { /* than one sector? */ bufsize = (IS_MPM(ver)) ? /* Yes: try to get bufferful*/ M_MAXMBUF : C_MAXMBUF; /* (size depends whether */ multsect((IS_MPM(ver)) ? /* MP/M or CP/M-3) */ M_MAXMCNT : C_MAXMCNT); } else /* No: just get one */ { bufsize = SECSIZE; multsect(1); } setuser(source.user); /* Source user number set */ nsource = nsbuf; while ((0xffff & (long) (sblen - nsbuf)) >= (long) SECSIZE) /* Read loop: exit when */ { /* buff full or file ends */ if (fastcopy /* Are we filling whole buff*/ && ((0xffff & (long) (sblen - nsbuf)) < (long) bufsize)) /*Is buffer bigger than */ { /* remainder of file? */ /* If so, decrease count */ bufsize = (sblen - nsbuf) & SECMSK; multsect(bufsize / SECSIZE); } _setdma(&sbase[nsbuf]); /* Say where data goes */ extsave = source.flfcb.extent; /* Save current extent field*/ if (diskrd(&source) == 0) /* Read. Was there an error*/ nsbuf += bufsize; /* No: tally bytes read */ else { /* Error: */ if (dcnt != 1) /* End of file error? */ error(0, exten, FALSE, &source);/* Something else: abort*/ /* End of file: clean up */ if (HAS_SETMSC(ver) && fastcopy)/* Add no. sectors copied */ nsbuf += (IS_MPM(ver)) ? (exten >> NIBBLE) * SECSIZE : exten; /****************************/ /* */ /* Following corrects for */ /* bug in BDOS by zeroing */ /* current record if it has */ /* a new value of 0x80 */ /* */ /****************************/ if ((source.flfcb.extent != extsave) && ((source.flfcb.record & 0xff000000) == 0x80000000)) source.flfcb.record &= 0x00ffffff; _set_rand(&source); /* Get next record number */ /* Is this sparse file (less*/ /* data than virtual size */ /* suggests is in file)? */ if (insparc = #ifdef HILO ((source.flfcb.record & 0xffffff) != filsize)) #else ((source.flfcb.record >> 8) != filsize)) #endif /* Yes. May not be allowed */ {if (concat || (! fastcopy)) error(18, 0, FALSE, &source); } else /* End of non-sparse file */ close(&source); /* Flag the fact */ endofsrc = TRUE; sbase[nsbuf] = ENDFILE; /* Plug in end of file mark */ return; /* That's all, folks! */ } ; /* End of error handling */ } /* End of read loop */ } /****************************************************************************/ /* */ /* N O N - F I L E D A T A H A N D L I N G */ /* ------------------------------------------- */ /* */ /****************************************************************************/ /********************************/ /* */ /* P U T D C H */ /* */ /********************************/ VOID /* Write the character b to */ putdch(b) /* device given by */ register int b; /* odest.type */ { if ((b >= ' ') /* Tally non-control chars */ && (++column > DELET) && (DELET > 0))/* in column count. Take */ return; /* no further action if */ /* beyond right margin */ if (ECHO) /* Need to echo? */ _conout(b); /* Yes */ switch (odest.type) /* Send char to right place */ { case OUTT: /* OUT: device not supported*/ nonfile_error(23); break; case PRNT: /* PRN: & LST: same action */ case LSTT: /* (tabs expanded for */ _lstout(b); /* PRN: by putdstc()) */ break; case AXOT: /* Auxilliary (punch) output*/ if (! IS_MPM(ver)) { _punch(b); } else /* MP/M does not support */ nonfile_error(3); break; case FILE: /* Output is a file */ if (ndest >= dblen) /* If buffer full, write out*/ writedest(); dbase[ndest++] = b; /* Add char to buffer */ break; case CONS: /* Console output */ _conout(b); break; } } /********************************/ /* */ /* P U T D S T C */ /* */ /********************************/ VOID /* Write out destination */ putdstc(b) /* character, expanding */ char b; /* tabs */ { register int i; if ((b != TAB) || (TABS == 0)) /* Not a tab or not expan- */ putdch(b); /* ding: just output */ else /* Expand a tab */ { i = TABS - (column % TABS); /* How many spaces needed? */ while (i--) putdch(' '); } if (b == CR) /* Zero column on CR */ column = 0; } /********************************/ /* */ /* N E W L I N E */ /* */ /********************************/ VOID newline() /* Output a new line number */ { /* on a new line (six */ register LONG factor, number; /* digits are printed, */ register int digit; /* leading 0's suppressed)*/ BOOLEAN zeroprint; factor = 1000000; number = ++line_no % factor; /* Truncate to 6 digits */ zeroprint = (NUMB != 1); /* NUMB always nonzero here */ while (factor /= 10) /* Print 6 digit positions */ { digit = number / factor; /* Get digit for this place */ zeroprint |= ((digit != 0) /* Suppresed leading zero? */ || (factor == 1)); _conout((zeroprint) ? /* Print digit or space */ digit + '0' : ' '); number %= factor; /* Discard current place */ } printx((NUMB == 1) ? ": " : "\t"); /* NUMB value selects */ } /* separator */ /********************************/ /* */ /* P U T D E S T */ /* */ /********************************/ VOID /* Write a destination */ putdest(b) /* character, checking */ int b; /* tabs and newlines */ { if (FORMF && (b == FF)) /* Ignore form feeds if cmd */ return; /* option tells us to */ if (putnum) /* New/first line of file */ { if (b == FF) /* Form feed? */ page_line = 0; /* Start a new page */ else /* Not a form feed */ { if (PAGCNT != 0) /* Page eject selected? */ { /* Yes: end of current page?*/ if (++page_line >= ((PAGCNT == 1) ? LPP : PAGCNT)) { /* Yes: behave as if form */ page_line = 0; /* feed received */ putdstc(FF); } } } if (NUMB) /* Printing line numbers? */ newline(); /* Yes */ putnum = FALSE; /* New line processing done */ } putdstc(b); /* Now we can put the char */ putnum = (b == LF); /* Next to go on new line? */ } /********************************/ /* */ /* U T R A N */ /* L T R A N */ /* */ /********************************/ int /* Translate character to */ utran(b) /* upper case if it is */ int b; /* lower case */ { return ((('a' <= b) && (b <= 'z')) ? b & 0x5f : b); } int /* Translate character to */ ltran(b) /* lower case if it is */ int b; /* upper case */ { return ((('A' <= b) && (b <= 'Z')) ? b | 0x20 : b); } /********************************/ /* */ /* G E T S R C C */ /* */ /********************************/ int /* Gets a character from the*/ getsrcc() /* current source device */ { int b; BOOLEAN conchk; conchk = TRUE; /* Console status chk below */ switch (source.type) /* Get data from current src*/ { case OUTT: /* These devices are not */ case PRNT: /* valid sources */ case LSTT: case AXOT: nonfile_error(4); break; case FILE: /* Source is a file */ if (nsource >= sblen) /* Is source buffer empty? */ { if (dblbuf || (! dfile))/* If source & dest buffers */ /* separate, or dest not a*/ nsbuf = 0; /* file, just clear index */ else /* Source buff is dest buff:*/ { /* Empty it unless read from*/ /* first read from source */ if (nsource != FORCE_READ) writedest(); nsbuf = ndest; } fillsource(); /* Fill source buffer */ } b = sbase[nsource++]; /* Get a character */ break; case CONS: /* Source is console */ conchk = FALSE; /* Don't chk console status */ b = _conin(); break; case AXIT: /* Auxiliary input */ if (IS_MPM(ver)) /* MP/M doesn't have one */ nonfile_error(4); b = _reader() & 0x7f; /* Not MP/M: get character */ break; case INPT: /* User-defined input: not */ nonfile_error(23); /* supported */ } if (conchk) /* Check if user wants to */ { /* end transfer */ /* On object files, check */ /* every 256 bytes, on */ /* ASCII every line */ conchk = (OBJ) ? ((++concnt & 0xff) == 0) : (b == LF); if (conchk && _constat()) /* Did user enter character */ { /* If end of file, pretend */ if (_conin() == ENDFILE)/* it came from source */ return (ENDFILE); nonfile_error(5); /* Not ENDFILE: user abort */ } } if (ZEROP) /* Clear parity bit if */ b &= 0x7f; /* necessary */ if (UPPER) /* Return character, trans- */ return (utran(b)); /* lated to upper or lower*/ return ((LOWER) ? ltran(b) : b); /* case if requested */ } /********************************/ /* */ /* M A T C H */ /* */ /********************************/ BOOLEAN /* Match start, quit strings*/ match(b, ch) /* (used in transferring */ int b; /* that part of a file */ int ch; /* between delimiter pair)*/ { /* b indexes start of match */ int c; /* string in command buff */ /* ch is current char in */ /* file, not cmd line. */ /* Have we reached end of */ /* match string? */ if ((c = combuf.comline[b += matchlen]) == ENDFILE || c == ']' || c == CR ) /* Allow ] or CR to end str */ { /* Yes: success! Save char */ combuf.comline[b] = ch; /* for output following */ return (TRUE); /* match string, return */ } if (c == ch) /* OK so far? */ matchlen++; else matchlen = 0; return (FALSE); /* No cigar (so far) */ } /********************************/ /* */ /* G E T S R C */ /* */ /********************************/ char /* Get next source character*/ getsrc() /* May be from source device*/ { /* or from start match */ int c; /* string. Skips chars */ /* before start match or */ /* after end match */ if (quitlen > 0) /* This has the effect of */ /* generating CR, LF, */ /* ENDFILE after end match*/ return (--quitlen == 1) ? LF : ENDFILE;/* (see below) */ FOREVER /* Until start string found */ { /* (if user specified one)*/ if (feedlen > 0) { /* Start string just found: */ /* return character from */ feedlen--; /* matching sequence */ return (combuf.comline[feedbase++]); } if ((c = getsrcc()) == ENDFILE) /* If source char is ENDFILE*/ return (ENDFILE); /* look no further */ if (STARTS > 0) /* Looking for start string?*/ { if (match(STARTS,c)) /* Complete match? */ { /* Yes: we're in business! */ feedbase = STARTS; /* Arrange for output of */ feedlen = matchlen + 1; /* matching sequence */ STARTS = matchlen = 0; /* Show start found and not */ /* searching for start */ } continue; /* Back to FOREVER */ } /* No interest in start */ if ((QUITS > 0) /* Looking for quit string? */ && match(QUITS,c)) /* Complete match? */ { /* Yes: arrange for CR, LF */ QUITS = 0; /* ENDFILE to be returned */ quitlen = 2; /* by this and next two */ return (CR); /* calls */ } else /* Character of no special */ return (c); /* interest: deliver it */ } /* End of FOREVER */ } /********************************/ /* */ /* R D _ E O F */ /* */ /********************************/ BOOLEAN /* Get a character, return */ rd_eof() /* TRUE if end of file */ { ch = getsrc(); /* On an object file, last */ if (OBJ) /* byte in last bufferful?*/ return (endofsrc && (nsource > nsbuf)); return (ch == ENDFILE); /* On ASCII, is char ENDFILE*/ } /****************************************************************************/ /* */ /* F U N C T I O N S H A N D L I N G I N T E L H E X D A T A */ /* ----------------------------------------------------------------- */ /* */ /****************************************************************************/ /********************************/ /* */ /* C K H E X */ /* */ /********************************/ int /* Check that a is a valid */ ckhex(a) /* hex digit. If it is */ int a; /* return corresponding */ { /* 4-bit value */ if (('0' <= a) && (a <= '9')) return (a - '0'); if (('A' <= a) && (a <= 'F')) return (a - 'A' + 0xa); error(13, 0, FALSE, &source); /* Invalid hex digit: abort */ } /********************************/ /* */ /* H E X R E C O R D */ /* */ /********************************/ VOID /* Transfer a whole file in */ hexrecord() /* Intel hex format, */ { /* validating checksums */ BOOLEAN zerorec, inrec; UWORD length, count; BYTE byte; int chr, lastchr; UWORD checksum; zerorec = inrec = FALSE; /* Not last record in file, */ /* not inside record */ while ((chr = getsrc()) != ENDFILE) /* Transfer data until end */ { /* of file reached */ if (! inrec) /* Perform no checks on data*/ { /* outside records */ if (inrec = (chr == ':')) /*Record start? */ checksum = count = 0; else /* Not record start: copy to*/ putdest(chr); /* dest */ continue; /* Back to while... */ } /* Inside a record */ if (++count & 1) /* Odd numbered chars are */ { /* high nibble of byte. */ lastchr = chr; /* Save for next time around*/ continue; /* Go get next character */ } /* Even numbered char: */ /* assemble a byte */ byte = (ckhex(lastchr) << 4) + ckhex(chr); checksum += byte; /* Tally in checksum */ if (count == 2) /* Record length */ { length = 10 + (byte * 2); /* Calculate character count*/ if (! ((zerorec = (byte == 0)) /* If this is not an ignored*/ && IGNOR)) /* final record, transfer */ { /* byte count to dest */ putdest(':'); putdest(lastchr); } } else /* Checksum at record end? */ { if (count == length) /* Yes: low 8 bits of our */ { /* checksum should be zero*/ if ((checksum & 0xff) != 0) /* Error if not */ error(9, NONE, FALSE, &source); else inrec = FALSE; /* No longer in record */ } } if (! (zerorec && IGNOR)) /* Unless an ignored final */ putdest(chr); /* record has been found, */ } /* transfer character */ if (inrec || (! zerorec)) /* End of file. Expected? */ error(15, NONE, FALSE, &source); /* Error if not */ } /****************************************************************************/ /* */ /* S T A R T , E N D O F F I L E F U N C T I O N S */ /* ------------------------------------------------------- */ /* */ /****************************************************************************/ /********************************/ /* */ /* C K _ S T R I N G S */ /* */ /********************************/ VOID /* Complain if start or end */ ck_strings() /* string not found in */ { /* source data */ if (STARTS > 0) /* Looking for start, but */ nonfile_error(11); /* didn't find it? */ if (QUITS > 0) /* Similarly for end... */ nonfile_error(12); } /********************************/ /* */ /* C L O S E D E S T */ /* */ /********************************/ VOID /* Close the destination */ closedest() /* file, flushing buffer */ { /* & renaming scratch file*/ ck_strings(); /* Started, ended OK? */ while ((ndest % SECSIZE) != 0) /* Fill to end of final */ putdest(ENDFILE); /* sector with ENDFILE */ writedest(); /* Flush buffer */ setuser(odest.user); /* Set up destination user */ close(&dest); /* Close scratch file */ if (dcnt == 255) error(14, exten, TRUE, &dest); /* Can't close file! */ if (! HAS_XFCBS(ver) /* If we have not done so */ && ((odcnt = open(&odest)) != 255)) /* already, find out if */ close(&odest); /* true dest file exists */ if (odcnt != 255) /* File exists */ { if ((odest.flfcb.R_O & 0x80) /* Is it read only and if so*/ && (! WRROF)) /* do we want to know? */ { /* Yes: pass buck to user */ while ((dcnt != 'Y') && (dcnt != 'N')) { print ("DESTINATION IS R/O, DELETE (Y/N)?"); dcnt = utran((int) _conin()); } if (dcnt == 'N') /* User doesn't want file */ { /* deleted */ print("**NOT DELETED**"); /* Confirm wise decision */ crlf(); delete(&dest); /* Delete temp file and exit*/ return; } crlf(); } odest.flfcb.R_O &= 0x7f; /* OK to delete old dest */ odest.flfcb.SYSTEM &= 0x7f; /* file: clear attributes */ setatt(&odest); /* so it can be deleted */ delete(&odest); } /* Rename the temporary dest*/ /* file (replace $$$ type)*/ move((char *) &odest.flfcb, dest.flfcb.resvd, 16); rename(&dest); /* Set dest attributes same */ /* as source */ odest.flfcb.F1 = (odest.flfcb.F1 & 0x7f) | f1; odest.flfcb.F2 = (odest.flfcb.F2 & 0x7f) | f2; odest.flfcb.F3 = (odest.flfcb.F3 & 0x7f) | f3; odest.flfcb.F4 = (odest.flfcb.F4 & 0x7f) | f4; odest.flfcb.USER_0 &= 0x7f; odest.flfcb.R_O = (odest.flfcb.R_O & 0x7f) | ro; odest.flfcb.SYSTEM = (odest.flfcb.SYSTEM & 0x7f) | sys; odest.flfcb.ARC &= 0x7f; setatt(&odest); if (ARCHIV) /* Should we archive source */ { /* Yes: */ setuser(source.user); source.flfcb.ftype[2] |= 0x80; source.flfcb.extent = 0; setatt(&source); } } /********************************/ /* */ /* S I Z E _ M E M O R Y */ /* */ /********************************/ VOID /* Allocate space for the */ size_memory() /* source and destination*/ { /* buffers */ UWORD freespace; static UWORD allocated = 0; static BYTE *base; if (! allocated) /* First time through, grab*/ { /* as much memory as we */ base = sbrk(0); /* can (even multiple of */ /* SECSIZE) */ allocated = (((BYTE *) &freespace - base) - SAFETY) & (SECMSK << 1); if (sbrk(allocated) == (BYTE *) -1) { print("Can't allocate memory"); _exit(1); } } if (dblbuf) /* Are two buffers needed? */ { /* Yes: divide the available*/ sblen = dblen = allocated / 2; /* space equally */ dbase = (char *) base; sbase = (char *) base + dblen; if (ndest >= dblen) writedest(); nsbuf = 0; } else /* Just one buffer */ { sblen = dblen = allocated; sbase = dbase = (char *) base; } } /********************************/ /* */ /* S E T U P E O B */ /* */ /********************************/ VOID /* Finds the first ENDFILE */ setupeob() /* in the last sector of */ { /* the source file just */ register int i; /* read and adjusts nsbuf */ /* to index the character */ if (OBJ) /* No-op for object files */ return; nsbuf -= SECSIZE + 1; /* Index char before last */ for (i = 0; i < SECSIZE; i++) /* sector and examine a */ if (sbase[++nsbuf] == ENDFILE) /* sector's worth of data */ return; nsbuf++; /* Point beyond last sector */ } /********************************/ /* */ /* C H K R A N D O M */ /* */ /********************************/ VOID /* Find and read the next */ chkrandom() /* record containing data */ { /* in a sparse file (file */ BYTE current; /* where not all records */ /* contain data) */ setuser(source.user); /* Set up source user */ _set_rand(&source); /* Set up record number */ multsect(1); /* Only read one record */ _setdma(buff); /* into default buffer */ FOREVER /* Until record containing */ { /* data found */ switch ((int) _b_read(&source)) /* Data in this record? */ { /* Yes: copy record no to */ case 0: /* destination FCB (do not*/ /* disturb current record)*/ current = ((BYTE *) &dest.flfcb.record)[0]; dest.flfcb.record = source.flfcb.record; ((BYTE *) &dest.flfcb.record)[0] = current; endofsrc = FALSE; return; /* Mission accomplished */ case 1: /* No data in this record: */ #ifdef HILO /* go to next (just how */ source.flfcb.record++; /* depens on byte order) */ #else source.flfcb.record += 0x100; #endif break; case 4: /* No data in this extent! */ #ifdef HILO /* Move to the next (again, */ /* method byte order */ /* dependent) */ source.flfcb.record = (source.flfcb.record + EXTRECS) & EXTMSK; #else source.flfcb.record = (source.flfcb.record + (EXTRECS << 8)) & ((EXTMSK << 8) & 0xff); #endif break; default: /* Something went wrong: say*/ error(0, NONE, FALSE, &source); /* its a disk read error */ } } } /****************************************************************************/ /* */ /* D A T A C O P Y I N G F U N C T I O N S */ /* ------------------------------------------- */ /* */ /****************************************************************************/ /********************************/ /* */ /* S I M P L E C O P Y */ /* */ /********************************/ VOID /* Copy a single input to */ simplecopy() /* an output (concaten- */ { /* ation handled in */ register int i; /* _main function!) */ fastcopy = (sfile && dfile); /* Is this file-file copy? */ endofsrc = dblbuf = sparfil = FALSE; /* Set initial conditions */ for (i = 0; i < sizeof cont; i++) /* Check options which */ { /* affect copy method */ if (cont[i]) { switch(optype[i]) { case CHRT: /* Character by character */ fastcopy = FALSE; break; case DUBL: /* May need double buffer */ dblbuf = (sfile && dfile); fastcopy = FALSE; break; } } } size_memory(); /* Set up buffers */ if (sfile) /* Open source if it is a */ setupsource(); /* file */ /* Now ready to do copy */ if (fastcopy) /* Quick file-file copy? */ { while (! endofsrc) /* Read the whole source */ { fillsource(); /* Fill source buffer */ if (endofsrc && concat) /* End of source file? */ { setupeob(); /* Find true end of file */ ndest = nsbuf; if (nendcmd) /* More to concatenate? */ return; /* Yes: don't flush buffer */ } ndest = nsbuf; writedest(); /* Write buffer out to */ nsbuf = ndest; /* scratch file */ if (endofsrc && insparc) /* If this has turned out to*/ chkrandom(); /* be sparse file, move to*/ } /* next record containing */ } /* data */ else /* Character by character */ { if (HEXT || IGNOR) /* Transfer Intel hex data */ hexrecord(); else while (! rd_eof()) /* Copy characters between */ putdest(ch); /* start & end strings */ if (concat && nendcmd) /* End of file: more to come*/ { /* Yes: mark buffer, but */ nsbuf = ndest; /* don't wrap up yet */ return; } } /* All done: */ if (dfile) /* If destination is a file,*/ closedest(); /* close it */ } /********************************/ /* */ /* A R C H K */ /* */ /********************************/ BOOLEAN /* Check if archive bit is */ archck() /* clear in any extent of */ { /* source file (used by */ /* [A] option in deciding */ /* which files to copy) */ /* Returns TRUE if file NOT */ /* archived */ if (! ARCHIV) /* Only check if option */ return (TRUE); /* selected */ setuser(source.user); /* Set up source user */ source.flfcb.extent = WILD; /* Look for all extents of */ search(&source); /* the source file */ while (dcnt != 255) /* While more extents */ { /* Copy name from directory */ move(&buff[((dcnt & 3) * 32) + 1], source.flfcb.fname, 15); if (! (source.flfcb.ARC & 0x80))/* Is archive bit clear? */ return (TRUE); searchn(); /* No, it's set: try again */ } return (FALSE); /* All extents archived */ } /********************************/ /* */ /* N E X T _ F I L E */ /* */ /********************************/ /****************************************************************************/ /* NOTE: This function could be made much more efficient if it were */ /* rewritten to use _gset_scb() to save and restore search context */ /* between calls if BDOS has CP/M 3.0 features */ /****************************************************************************/ int /* Find the first extent of */ next_file(beyond) /* next file after direct-*/ int beyond; /* ory entry indexed by */ { /* beyond which matches */ register int ndcnt; /* the name in SEARFCB. */ /* Returns new beyond value */ setuser(source.user); /* Set up source user */ _setdma(buff); /* Get directory record into*/ SEARFCB->extent = 0; /* default DMA buffer */ search(SEARFCB); ndcnt = 0; while ((dcnt != 255) /* Find the match requested */ && (ndcnt++ < beyond)) searchn(); if (dcnt != 255) /* Copy name from directory */ { /* to source & dest FCB's */ move(&buff[((dcnt & 3) * 32) + 1], odest.flfcb.fname, 15); move(odest.flfcb.fname, source.flfcb.fname, 15); } return(ndcnt); /* Return index to match */ } /********************************/ /* */ /* M U L T C O P Y */ /* */ /********************************/ VOID /* Copy mutiple files to a */ multcopy() /* a named drive or user */ { int nextdir, ncopied; if (! eretry) /* Unless retrying following*/ nextdir = ncopied = 0; /* error, clear counters */ FOREVER /* Until no more matches */ { nextdir = next_file(nextdir); /* Find a file to copy */ if (dcnt == 255) /* No more files? */ { if (ncopied == 0) /* No matches at all? */ error(10, 0, FALSE, SEARFCB);/* File not found */ if (! KILDS) /* Terminate list of files */ crlf(); return; /* All done */ } /* We have a filename match */ if (! archck()) /* Ignore it if archived and*/ continue; /* A option in force */ if ((odest.flfcb.SYSTEM & 0x80) /* Ignore if a system file */ && ! RSYS) /* and not allowed to copy*/ continue; made = FALSE; /* Destination file not made*/ /* Start from first extent */ odest.flfcb.extent = source.flfcb.extent = 0; if (! KILDS) /* Need to print its name? */ { if (ncopied == 0) /* Print heading first time */ print("COPYING -"); crlf(); prname(&odest.flfcb); } ncopied++; /* We have a file to copy */ simplecopy(); /* Do the copy */ } } /****************************************************************************/ /* */ /* C O M M A N D P A R S I N G F U N C T I O N S */ /* ------------------------------------------------- */ /* */ /****************************************************************************/ /********************************/ /* */ /* C K _ D I S K */ /* */ /********************************/ ck_disk() /* Abort if source and dest */ { /* disks, source and dest */ if ((odest.user == source.user) /* users the same */ && (odest.flfcb.drive == source.flfcb.drive)) nonfile_error(8); } /********************************/ /* */ /* G N C */ /* */ /********************************/ int /* Get next character from */ gnc() /* command line or CR at */ { /* end of line */ /* Also translates returned */ /* value to upper case. */ return ((++combuf.cbp >= (combuf.comlen & 0xff)) ? CR : (utran(combuf.comline[combuf.cbp]))); } /********************************/ /* */ /* D E B L A N K */ /* */ /********************************/ VOID /* Skip until non-blank */ deblank() /* command line character */ { if (ch == CR) return; while (getch(ch) == ' ') ; } /********************************/ /* */ /* C K _ E O L */ /* */ /********************************/ VOID /* Complain if further input*/ ck_eol() /* follows a complete cmd */ { deblank(); if (ch != CR) nonfile_error(8); } /********************************/ /* */ /* D E L I M I T E R */ /* */ /********************************/ BOOLEAN /* Return TRUE if the curent*/ delimiter(c) /* character is delimiter */ int c; { static char del[] = "* =.:;,!=\r<[]"; register int i; for (i = 0; i < sizeof del - 1; i++) if (c == del[i]) return (TRUE); return (FALSE); } /********************************/ /* */ /* A _ T O _ I */ /* */ /********************************/ UWORD /* Convert ASCII to decimal */ a_to_i() /* Returns result */ { register UWORD decimal; decimal = 0; while (('0' <= getch(ch)) /* Until non-digit found */ && ((ch) <= '9')) decimal = 10 * decimal + (ch - '0'); combuf.cbp--; /* "Push back" terminator */ return (decimal); } /********************************/ /* */ /* S C A N _ P A R */ /* */ /********************************/ VOID /* Scan option parameters */ scanpar(fcba) /* enclosed in square */ struct flctlb *fcba; /* brackets */ { register int usr; register int n; /* Int returned by a_to_i() */ char chsave; /* Because of C side-effect */ /* crock - see a_to_i call. */ while ((getch(ch) != CR) && (ch != ']')) { if ((ch = utran(ch)) == ' ') /* Skip spaces between */ continue; /* options */ if (!(('A' <= ch) && (ch <= 'Z'))) /* Non-alpha option? */ nonfile_error(6); /* Yes: error */ cont[ch - 'A'] = 1; /* Show option selected */ switch (chsave = ch) /* Check for modifiers after*/ { /* option */ default: /* D, N, P, T take an intgr */ if( (n = a_to_i()) != 0) /* arg (optional w/ N.) */ cont[chsave - 'A'] = n; /* Plug in value (note lack */ break; /* of range check) */ /* NOTE: a_to_i() steps on */ /* the global variable "ch",*/ /* and the subscript is eval*/ /* uated after the call. */ case 'G': /* Get user number */ if ((usr = a_to_i()) > MAXUSER) /* Complain if invalid user */ nonfile_error(6); fcba->user = usr; /* Plug user no. into FCB */ break; case 'Q': /* Start or quit string: */ case 'S': /* Put string index in */ cont[ch - 'A'] = combuf.cbp + 1;/* option buffer */ while ((n = gnc()) != ENDFILE && n != ']' && n != CR);/* Skip string */ break; } } } /********************************/ /* */ /* T O K E N */ /* */ /********************************/ int /* Finds the length of the */ token() /* next command token, */ { /* that is the number of */ register int len; /* chars, including the */ /* one indexed by cbp on */ for (len = 0; ! delimiter(ch); len++) /* entry, up to but not */ getch(ch); /* including the next */ /* delimiter. ch contains*/ return (len); /* and cbp indexes, the */ } /* delimiter on exit */ /********************************/ /* */ /* G E T _ D E V */ /* */ /********************************/ int /* If the current token */ get_dev() /* matches a valid device,*/ { /* return index in io to */ register int i, j; /* device name. If no */ /* match, return invalid */ /* index */ for (i = 0; i < TYPES; i++) /* For each valid name */ { for ( j = 0; j < 3; j++) /* Try to match 3 characters*/ { if (io[i] [j] != utran(combuf.comline[combuf.cbp - 3 + j])) break; /* Not this device. Next? */ if (j == 2) /* Complete match? */ return (i); /* Yes */ } } return (TYPES); /* No match */ } /********************************/ /* */ /* S C A N */ /* */ /********************************/ VOID /* Scan a file/device name */ scan(fcba) /* If file name, put in fcba*/ struct flctlb *fcba; /* Set up any options */ { register UWORD i, length; *fcba = empty_fcb; /* Clear out FCB */ fcba->flfcb.drive = cdisk + 1; /* Initialize to current */ fcba->user = c_user; /* disk and current user */ fcba->type = FILE; /* Clear out options */ for (i = 0; i < sizeof cont; cont[i++] = 0); feedlen = matchlen = quitlen = 0; /* Initialize string match */ /* control variables */ ambig = FALSE; /* Filename not ambiguous */ deblank(); /* Skip to nonblank char */ /* Initialization done: */ /* parse */ length = token(); /* Find a token */ if (ch == ':') /* Did it end with ':'? */ { /* Yes. How long was it? */ switch (length) { case 1: /* Looks like a drive name */ /* Put in FCB, go on if OK */ if (! ('A' <= (ch = utran(combuf.comline[combuf.cbp - 1])) && ((fcba->flfcb.drive = ch - 'A' + 1) <= MAXDRIVE))) goto parse_error; /* Not valid disk name */ deblank(); /* Blanks may follow drive */ if((length = token()) == 0) /* Get another token. If */ /* it's null, and not */ if (ch != '*') { /* an asterisk, device is */ fcba->type = DISKNAME; /* disk name only */ /* Maybe options follow?? */ if (ch == '[') { /* Yes: collect options */ scanpar(fcba); if (ch != ']' && ch != CR) /* Make sure the options */ /* End? If Not format error*/ nonfile_error(6); /* skip error? */ return; /* Return w/ ch == ']' */ } combuf.cbp--; /* Step back to last char */ return; /* All done */ } break; /* Non null token: filename */ case 3: /* Looks like device name */ /* Matches known name? */ if ((fcba->type = get_dev()) >= TYPES) goto parse_error; /* No: invalid device */ if (gnc() == '[') { /* Yes: collect options */ scanpar(fcba); if (ch != ']' && ch != CR) /* Make sure the options */ nonfile_error(6); /* End if Not format error*/ else deblank(); } combuf.cbp--; /* Step back to last char */ return; /* We're done */ default: goto parse_error; /* Unidentified token */ } } /* Past the colon (if any): */ /* should be positioned at*/ /* filename */ if (length > L_NAME) /* Too long for */ goto parse_error; /* filename? */ if ((ch == '*') && (length == 0)) /* Wildcard in filename? */ { /* Yes: fill with ?'s */ move("????????", fcba->flfcb.fname, L_NAME); getch(ch); token(); /* Skip to next delimiter */ ambig = TRUE; /* Name is ambiguous */ } else /* Not wildcard: */ { /* copy to FCB */ for( i = combuf.cbp - length; i < combuf.cbp; i++) combuf.comline[i] = /* Translate filename to */ utran(combuf.comline[i]); /* upper case */ move(&combuf.comline[combuf.cbp - length], fcba->flfcb.fname, length); } if (ch == '.') /* Does a type follow name? */ { /* Yes: read it */ getch(ch); if ((length = token()) > L_TYPE) /* Error if too long */ goto parse_error; if ((ch == '*') && (length == 0)) /* Wildcard in type? */ { /* Yes: fill with ?'s */ move("???", fcba->flfcb.ftype, L_TYPE); getch(ch); token(); /* Skip to next delimiter */ ambig = TRUE; /* Name is ambiguous */ } else { /* O K: copy to FCB */ for( i = combuf.cbp - length; i < combuf.cbp; i++) combuf.comline[i] = /* Translate filename to */ utran(combuf.comline[i]);/* upper case */ move(&combuf.comline[combuf.cbp - length], fcba->flfcb.ftype, length); } } if (HAS_XFCBS(ver) /* Past the filename. There*/ && (ch == ';')) /* could be a password */ { getch(ch); if ((length = token()) > L_PASS) /* Error if too long */ goto parse_error; if (length != 0) /* O K: copy to FCB if not */ { /* zero length */ move(&combuf.comline[combuf.cbp - length], fcba->pwnam, length); if (fcba == &odest) /* If parsing dest name, */ getpw = FALSE; /* show non-default passwd*/ } /* it to be used */ } /* Maybe options follow?? */ if (ch == '[') { /* Yes: collect options */ scanpar(fcba); if (ch != ']' && ch != CR) /* Make sure the options */ nonfile_error(6); /* End? If Not format error*/ else deblank(); } combuf.cbp--; /* Align the input */ return; /* That's all, folks */ parse_error: combuf.cbp -= length; /* Step back to first char */ return; /* of bad token */ } /****************************************************************************/ /* */ /* U T I L I T Y F U N C T I O N S F O R M A I N ( ) */ /* ------------------------------------------------------- */ /* */ /****************************************************************************/ /********************************/ /* */ /* G E T _ C M D */ /* */ /********************************/ BOOLEAN /* Get the next command line*/ get_cmd() /* Return TRUE if not empty */ { /* Also set up variables */ line_no = 0; /* Line no for printer */ concnt = column = 0; /* Abort check, printer col */ ndest = nsbuf = 0; /* Src, dest buffer indices */ made = FALSE; /* Destination file not made*/ concat = FALSE; /* Single source file */ putnum = TRUE; /* Need line no before print*/ dfile = sfile = TRUE; /* Src, dest are files */ nendcmd = TRUE; /* Command end not reached */ page_line = (UWORD) -2; /* Form feed before print */ combuf.cbp = (UWORD) -1; /* Command buff index is */ /* incremented to zero */ getpw = (HAS_XFCBS(ver)); /* Files may need passwords */ if (multcom) /* If in interactive mode, */ { /* get a command line */ _conout('*'); /* Prompt for it */ rdcom(); crlf(); dcnt = 0; /* Clear error flag */ } ch = ' '; /* To solve the CR at eol */ /* bug */ return ((UWORD) (combuf.comlen & 0xff) > 1); /* Tell caller if cmd empty */ } /********************************/ /* */ /* D S T _ D I S K */ /* */ /********************************/ VOID /* Destination is a disk: */ dst_disk() /* copy file(s) to it */ { BYTE dd, du; ck_eol(); /* Nothing can follow name */ ck_disk(); /* Dest user or disk must */ /* differ from source */ odest.type = FILE; /* Dest is really a file */ if (ambig) /* Form is X:=Y:ambiguous */ { *SEARFCB = source.flfcb; /* Prepare for search */ multcopy(); /* Copy matching files */ } else /* Form is X:=Y:unique */ { /* Copy source FCB to dest, */ dd = odest.flfcb.drive; /* preserving dest drive */ du = odest.user; /* and user number */ odest = source; odest.flfcb.drive = dd; odest.user = du; simplecopy(); /* Copy the file */ } } /********************************/ /* */ /* D I S K _ T O _ F I L E */ /* */ /********************************/ VOID /* Action command with form */ disk_to_file() /* unique=X: */ { BYTE sd, su; ck_eol(); /* Nothing can follow name */ ck_disk(); /* Dest user or disk must */ /* differ from source */ source.type = FILE; /* Source is really a file */ sd = source.flfcb.drive; /* Copy dest FCB to source, */ su = source.user; /* preserving source drive*/ source = odest; /* and user number */ source.flfcb.drive = sd; source.user = su; simplecopy(); /* Copy the file */ } /********************************/ /* */ /* C O P Y _ S O U R C E */ /* */ /********************************/ VOID /* Transfer the current */ copy_source() /* source file to the */ { /* destination */ register int i; if (odest.type != FILE) dfile = FALSE; /* So we won't fastcopy */ if (odest.type == PRNT) /* If the destination is */ { /* formatted printer set */ NUMB = TRUE; /* up default tabs and */ if (TABS == 0) /* page length if options */ TABS = 8; /* have not already set */ if (PAGCNT == 0) /* them up */ PAGCNT = 1; } switch (source.type) /* How we behave depends on */ { /* source type */ case FILE: /* It's a file */ if (ambig) /* Name cannot be abiguous */ nonfile_error(4); break; /* Name is unique: go to it!*/ case CONS: /* It's valid, but not a */ case AXIT: /* file */ case INPT: sfile = FALSE; break; case NULT: /* Null trailer: generate it*/ for (i = NULLS; i--; putdest(0)); return; /* Transfer complete */ case EOFT: /* End of file mark: send it*/ putdest(ENDFILE); return; /* Transfer complete */ default: /* None of the above: error */ nonfile_error(4); } simplecopy(); /* Transfer the data */ ck_strings(); /* Abort if start, end */ /* string not found */ } /****************************************************************************/ /* */ /* M A I N F U N C T I O N */ /* ------------------------- */ /* */ /* NOTE: this function is named _main, not main, so as to avoid the */ /* standard C prolog function (also called _main) being pulled in */ /* from the library at link-edit time. The library function would */ /* add a considerable amount of dead wood - notably several parts */ /* of the standard I/O package - which are not required by PIP */ /* */ /****************************************************************************/ VOID _main() { BOOLEAN copydone; /* Needed for case */ /* source.type == DISKNAME */ /* Set up destination addr */ if (setjmp(main_stack) == 0) /* for non-local goto */ { /* Following code not reex- */ /* ecuted after error */ buff = _base->buff; /* Set up pointers into */ fcb = &_base->fcb1; /* basepage */ /* Get command line tail */ move(buff, (char *) &combuf.comlen, sizeof (combuf.comline) + 1); if (HAS_RETERR(ver = _version())) /* Get CP/M version, set */ _ret_errors(0xff); /* error return mode */ if (multcom = (combuf.comlen == 0)) /* If cmd line tail empty, */ /* interactive mode: */ { /* announce ourselves */ print("Portable CP/M PIP version 1.0"); crlf(); } c_user = _gset_ucode(0xff); /* Get current user */ cdisk = _ret_cdisk(); /* and current disk */ eretry = FALSE; /* First time around can't */ } /* be error retry! */ /****************************************************************************/ /* Restart here following longjmp at end of error(). Eretry shows whether */ /* current transfer should be retried or not (MP/M only) */ /****************************************************************************/ if (eretry) multcopy(); /* Retry failed transfer */ while (get_cmd()) /* Until empty command */ { copydone = FALSE; /* Nothing done yet */ scan(&odest); /* Parse destination name */ if (ambig) /* Cannot be ambiguous */ error(3, NONE, FALSE, &odest); deblank(); /* Should be assignment next*/ switch(ch) /* Parse failures or bad */ { /* command line will force*/ case '=': /* error at this point */ case '<': break; /* Valid assignment operator*/ default: /* Invalid: error */ nonfile_error(8); } scan(&source); /* Parse first source name */ switch (odest.type) /* We are now in a position */ { /* to handle special cases*/ case CONS: case OUTT: case AXOT: break; /* These cases not special */ case PRNT: /* Formatted list device: */ case LSTT: /* List device: if MP/M, */ if (IS_MPM(ver) /* check device is avail- */ && (_cond_lst() == 0xff)) /* able and abort if not */ nonfile_error(8); break; case FILE: /* Form may be unique=X: */ if (source.type == DISKNAME) { /* Yes, it is */ disk_to_file(); combuf.comlen = multcom; /* Zero command length if */ /* single command */ continue; /* Done with current command*/ } break; /* Not a special case */ case DISKNAME: /* Form is X: = something */ if (source.type != FILE) /* Error if "something" not */ nonfile_error(8); /* a filename */ dst_disk(); /* O K: go do the work */ copydone = TRUE; /* So we won't do the */ break; /* copy_source() below */ default: /* Should have been one of */ nonfile_error(3); /* the above. Error */ } while (nendcmd) /* While still more to do */ { deblank(); /* Skip blanks in cmd line */ switch(ch) /* What have we found? */ { case CR: /* The end of the line */ nendcmd = FALSE; break; case ',': /* An indication that */ concat = nendcmd = TRUE; /* another file follows */ break; default: /* An illegal separator... */ nonfile_error(16); } if( ! copydone ) /* If not done already... */ copy_source(); /* ...perform transfer */ if (nendcmd) /* If more files named, */ scan(&source); /* go parse another */ } combuf.comlen = multcom; /* Zero command length if */ /* single command */ } /* End of while (get_cmd()) */ setuser(c_user); /* Restore current user */ _exit(0); /* Normal exit from PIP */ }