mirror of
https://github.com/SEPPDROID/Digital-Research-Source-Code.git
synced 2025-10-23 16:34:07 +00:00
3041 lines
100 KiB
C
3041 lines
100 KiB
C
/****************************************************************************/
|
||
/* */
|
||
/* 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 */
|
||
}
|