cpmish/cpmtools/copy.c

348 lines
7.2 KiB
C

/* copy © 2019 David Given
* This program is distributable under the terms of the 2-clause BSD license.
* See COPYING.cpmish in the distribution root directory for more information.
*
* This is a simple and rather stupid copy utility for CP/M. It does buffered
* reads and writes, so it's reasonably quick, and it understands wildcards,
* but it only supports disk-to- disk copies unlike PIP. It also doesn't know
* about user areas, so.
*
* Syntax: copy <inputfilespec> <outputfilespec>
*
* The right hand side can be a drive name ('c:'). It has to be a drive name if
* the left hand side is a wildcard.
*/
#include <cpm.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
const char** gargv;
uint8_t first_arg;
uint8_t last_arg;
uint8_t* buffer_start;
bool erase_destination = false;
bool only_one_record = false;
uint8_t olduser;
uint8_t srcuser;
FCB src_fcb;
uint8_t destuser;
FCB dest_fcb;
static void printn(const char* s, unsigned len)
{
while (len--)
{
uint8_t b = *s++;
if (!b)
return;
cpm_conout(b);
}
}
static void print(const char* s)
{
for (;;)
{
uint8_t b = *s++;
if (!b)
return;
cpm_conout(b);
}
}
static void crlf(void)
{
print("\r\n");
}
static void printx(const char* s)
{
print(s);
crlf();
}
void fatal(const char* s)
{
print("Error: ");
printx(s);
cpm_set_user(olduser);
cpm_exit();
}
void syntax_error(void)
{
fatal("syntax error");
}
void cant_use_wildcards(void)
{
fatal("you can't use wildcards in this mode");
}
void abort(void)
{
fatal("user cancel");
}
void help(void)
{
printx("Syntax: copy [/uf] [<inputspec...>] <outputspec>");
printx("Options:");
printx(" F: overwrite output");
printx(" U: unbuffered");
printx("<inputspec> may contain wildcards. <outputspec> may be just a drive.");
cpm_exit();
}
void parse_options(void)
{
const char* opt;
uint8_t b;
opt = gargv[first_arg];
b = *opt++;
if ((b != '-') && (b != '/')) /* includes \0 */
return;
first_arg++;
for (;;)
{
b = *opt++;
if (!b)
return;
switch (b)
{
case 'F': erase_destination = true; break;
case 'U': only_one_record = true; break;
case '?': case 'H': help();
default:
fatal("invalid option");
}
}
}
void print_fcb(const FCB* fcb)
{
uint8_t i;
const uint8_t* p;
cpm_conout('@' + fcb->dr);
cpm_conout(':');
p = fcb->f;
for (i=0; i<8; i++)
{
uint8_t b = *p++;
if (b == ' ')
break;
cpm_conout(b);
}
if (fcb->f[8] == ' ')
return;
cpm_conout('.');
p = fcb->f+8;
for (i=0; i<3; i++)
{
uint8_t b = *p++;
if (b == ' ')
break;
cpm_conout(b);
}
}
bool does_fcb_have_wildcards(const FCB* fcb)
{
uint8_t i = 11;
while (--i)
{
if (fcb->f[i] == '?')
return true;
}
return false;
}
void copy_one_file(FCB* src, FCB* dest)
{
bool destexists;
uint8_t* maxp;
uint8_t* readp;
uint8_t* writep;
bool eof = false;
uint8_t i;
maxp = buffer_start + (only_one_record ? 128 : ((cpm_ramtop - buffer_start + 1) & ~127));
print_fcb(src);
print(" -> ");
print_fcb(dest);
print(": ");
cpm_set_user(destuser);
cpm_set_dma(buffer_start); /* findfirst writes to this */
destexists = cpm_findfirst(dest) != 0xff;
if (destexists)
{
if (erase_destination)
{
if (cpm_findfirst(dest) != 0xff)
{
if (cpm_delete_file(dest) == 0xff)
fatal("cannot erase destination file");
}
}
else
fatal("destination file exists");
}
cpm_set_user(srcuser);
if (cpm_open_file(src) == 0xff)
fatal("cannot open source file");
src->cr = 0;
cpm_set_user(destuser);
if (cpm_make_file(dest) == 0xff)
fatal("cannot open destination file");
while (!eof)
{
uint8_t count;
/* Read phase. */
readp = buffer_start;
count = 0;
while (readp != maxp)
{
cpm_set_dma(readp);
cpm_set_user(srcuser);
i = cpm_read_sequential(src);
if (i == 1) /* EOF */
{
eof = true;
break;
}
if (i != 0)
fatal("error on read");
readp += 128;
if ((count & 3) == 0)
cpm_conout('r');
count++;
if (cpm_const())
abort();
}
/* Write phase */
writep = buffer_start;
count = 0;
while (writep != readp)
{
cpm_set_dma(writep);
cpm_set_user(destuser);
i = cpm_write_sequential(dest);
if (i != 0)
fatal("error on write");
writep += 128;
if ((count & 3) == 0)
cpm_conout('w');
count++;
if (cpm_const())
abort();
}
}
crlf();
if (cpm_close_file(dest) == 0xff)
fatal("failed to close output file");
}
void multicopy(void)
{
DIRE* dmabuf = (DIRE*) buffer_start;
FCB* const fcbtab_start = (FCB*) (buffer_start + 128);
FCB* fcbtab_max = fcbtab_start;
FCB* fcbtab_ptr = fcbtab_start;
uint8_t i;
while (first_arg != last_arg)
{
srcuser = cpm_parse_filename(&cpm_fcb, gargv[first_arg++]);
cpm_set_dma(dmabuf);
i = cpm_findfirst(&cpm_fcb);
while (i != 0xff)
{
DIRE* de = &dmabuf[i];
memset(fcbtab_max, 0, sizeof(FCB));
fcbtab_max->dr = cpm_fcb.dr;
for (i=0; i<11; i++)
fcbtab_max->f[i] = de->f[i] & 0x7f;
fcbtab_max++;
i = cpm_findnext(&cpm_fcb);
}
}
if (fcbtab_max == fcbtab_start)
fatal("nothing to copy");
buffer_start = (uint8_t*) fcbtab_max;
while (fcbtab_ptr != fcbtab_max)
{
memcpy(&cpm_fcb, &dest_fcb, sizeof(FCB));
memcpy(cpm_fcb.f, fcbtab_ptr->f, sizeof(cpm_fcb.f));
copy_one_file(fcbtab_ptr, &cpm_fcb);
fcbtab_ptr++;
}
}
void singlecopy(void)
{
if (last_arg != (first_arg + 1))
fatal("only one source allowed if a filename is specified as destination");
srcuser = cpm_parse_filename(&src_fcb, gargv[first_arg]);
if (does_fcb_have_wildcards(&src_fcb) || does_fcb_have_wildcards(&dest_fcb))
cant_use_wildcards();
copy_one_file(&src_fcb, &dest_fcb);
}
void main(int argc, const char* argv[])
{
if (argc == 1)
help();
gargv = argv;
first_arg = 1;
last_arg = argc - 1;
buffer_start = cpm_ram;
olduser = cpm_get_user();
parse_options();
if (last_arg <= first_arg)
syntax_error();
destuser = cpm_parse_filename(&dest_fcb, gargv[last_arg]);
if (dest_fcb.f[0] == ' ')
{
/* Destination is a drive spec. */
multicopy();
}
else
{
/* Copy a single file. */
singlecopy();
}
cpm_set_user(olduser);
}