Files
Sepp J Morris 31738079c4 Upload
Digital Research
2020-11-06 18:50:37 +01:00

3041 lines
100 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/****************************************************************************/
/* */
/* 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 */
}