/* $Id$
* COPY.C -- Internal Copy Command
*
* 1999/05/10 ska
* rewritten, based upon previous COPY.C of FreeCom v0.76b
*
* Known bugs:
* + Multiple '+' plus signs are scanned as a single one.
*
* 1999/07/08 ska
* bugfix: destination is a drive letter only
*
* 2000/07/17 Ron Cemer
* bugfix: destination ending in "\\" must be a directory, but fails
* within dfnstat()
*
* 2000/07/24 Ron Cemer
* bugfix: Suppress "Overwrite..." prompt if destination is device
*
* 2001/02/17 ska
* add: interactive command flag
* bugfix: copy 1 + 2 + 3 <-> only first and last file is stored
*/
#include "../config.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*#define DEBUG*/
#include "dfn.h"
#include "suppl.h"
#include "supplio.h"
#include "../include/lfnfuncs.h"
#include "../include/command.h"
#include "../include/cmdline.h"
#include "../err_fcts.h"
#include "../include/misc.h"
#include "../strings.h"
#include "../include/openf.h"
#define ASCII 1
#define BINARY 2
#define IS_DIRECTORY 5
static struct CopySource {
struct CopySource *nxt; /* next source */
struct CopySource *app; /* list of files to append */
int flags; /* ASCII / Binary */
char *fnam; /* filename */
} *head, *last, *lastApp;
static int appendToFile; /* Append the next file rather than new source */
static char *destFile; /* destination file/directory/pattern */
static int destFlags;
static int optY, optV, optA, optB;
optScanFct(opt_copy)
{
(void)arg;
switch(ch) {
case 'Y': return optScanBool(optY);
case 'V': return optScanBool(optV);
case 'A': case 'B': return E_Ignore;
}
optErr();
return E_Useage;
}
optScanFct(opt_copy1)
{
int ec, *opt = NULL, *optReset = NULL;
(void)arg;
switch(ch) {
#ifndef NDEBUG
default:
fprintf(stderr, "Invalid option: file '%s' in line %d\n"
, __FILE__, __LINE__);
abort();
#endif
case 'A': opt = &optA; optReset = &optB; break;
case 'B': opt = &optB; optReset = &optA; break;
}
if((ec = optScanBool(*opt)) == E_None
&& *opt)
*optReset = 0;
return ec;
}
static void initContext(void)
{
appendToFile = 0;
last = lastApp = 0;
}
static void killContext(void)
{
if(last) {
assert(head);
do {
if((lastApp = head->app) != 0) do {
lastApp = (last = lastApp)->app;
free(last);
} while(lastApp);
head = (last = head)->nxt;
free(last);
} while(head);
}
}
/*
faster copy, using large (far) buffers
*/
/*
a) this copies data, using a 60K buffer
b) if transfer is slow (or on a huge file),
indicate some progress
*/
static int BIGcopy(int fdout, int fdin, int asc)
{
char far *buffer;
unsigned size;
unsigned rd;
int retval = 0;
/* stat stuff */
unsigned startTime, lastTime=0, now, doStat = 0, deviceIn;
unsigned long copied = 0, toCopy = filelength(fdin);
char *statString;
char far *ctrlz;
/* Fetch the largest available buffer */
for(size = 60*1024u; size != 0; size -= 4*1024) {
#ifdef FARDATA
/* use last-fit allocation to work well with large model */
buffer = MK_SEG_PTR(void, DOSalloc(size/16,2));
#else
buffer = MK_SEG_PTR(void, DOSalloc(size/16,0));
#endif
if(buffer != NULL)
goto ok;
}
return 3; /* out of memory error */
ok:
dprintf( ("[MEM: BIGcopy() allocate %u bytes @ 0x%04x]\n"
, size, FP_SEG(buffer)) );
deviceIn = isadev(fdin);
statString = getString(deviceIn
? TEXT_COPY_COPIED_NO_END
: TEXT_COPY_COPIED);
startTime = *(unsigned far *)MK_FP(0x40,0x6c);
ctrlz = 0;
while((rd = farread(fdin, buffer, size)) != 0) {
if(rd == 0xffff) {
retval = 1;
goto _exit;
}
if(asc) {
ctrlz = _fmemchr(buffer, 0x1a, rd);
if(ctrlz != 0)
rd = (unsigned)(ctrlz - buffer);
}
if(farwrite(fdout, buffer, rd) != rd) {
if(!isadev(fdout)) retval = 2;
goto _exit;
}
/* statistics */
copied += rd;
now = *(unsigned far *)MK_FP(0x40,0x6c);
if(!doStat
&& now - startTime > 15 * 18
&& isatty(fileno(stdout)))
doStat = TRUE;
if(now - lastTime > 18) {
if(doStat)
printf(statString, copied/1024, toCopy/1024);
if(cbreak) {
retval = 3;
goto _exit;
}
lastTime = now;
}
if(ctrlz || (rd < size && !(deviceIn && asc))) break;
}
_exit:
if(doStat)
printf("%30s\r","");
dprintf( ("[MEM: BIGcopy() release memory @ 0x%04x]\n"
, FP_SEG(buffer)) );
DOSfree(FP_SEG(buffer));
free(statString);
return retval;
}
static int is_valid_disk(int tstdsk)
{
int savdsk = getdisk();
int newdsk;
/* Change to new disk */
setdisk(tstdsk);
newdsk = getdisk();
/* Restore */
setdisk(savdsk);
return (newdsk == tstdsk);
}
static int copy(char *dst, char *pattern, struct CopySource *src
, int openMode)
{ struct dos_ffblk ff;
struct CopySource *h;
char rDest[MAXPATH], rSrc[MAXPATH];
int fdin, fdout;
int rc;
FLAG keepFTime;
#if defined(__WATCOMC__) && __WATCOMC__ < 1280
unsigned short date, time;
#elif defined(__TURBOC__)
struct ftime fileTime;
#else
unsigned date, time;
#endif
char *srcFile;
FLAG wildcarded;
/*FLAG isfirst = 1;*/
FLAG singleFileCopy = src->app == NULL;
assert(dst);
assert(pattern);
assert(src);
if(strpbrk(pattern, "*?") == 0) {
srcFile = dfnfilename(pattern);
wildcarded = 0;
} else if(dos_findfirst(pattern, &ff, FA_RDONLY | FA_ARCH) != 0) {
error_sfile_not_found(pattern);
return 0;
} else {
srcFile = ff.ff_name;
wildcarded = 1;
}
do {
/* if( wildcarded && !strpbrk( dst, "*?" ) && !isfirst ) openMode = O_APPEND; */
fillFnam(rDest, dst, srcFile);
if(rDest[0] == 0)
return 0;
h = src;
do { /* to prevent to open a source file for writing, e.g.
for COPY *.c *.? */
fillFnam(rSrc, h->fnam, srcFile);
if(rSrc[0] == 0) {
return 0;
}
rc = samefile(rDest, rSrc);
if(rc < 0) {
error_out_of_memory();
return 0;
} else if(rc) {
error_selfcopy(rDest);
return 0;
}
} while((h = h->app) != 0);
/* Concenation of files uses ASCII by default */
if(src->app) {
for(h = src; h && !h->flags; h = h->app)
h->flags = ASCII;
if(!destFlags) destFlags = ASCII;
}
if(interactive_command /* Suppress prompt if in batch file */
&& openMode != O_APPEND && !optY
&& (fdout = dos_open(rDest, O_RDONLY)) >= 0) {
int destIsDevice = isadev(fdout);
dos_close(fdout);
if(!destIsDevice) { /* Devices do always exist */
if((fdin = devopen(rSrc, O_RDONLY)) < 0) { /* Source doesn't exist */
error_open_file( rSrc );
return 0;
} else {
dos_close(fdin);
switch(userprompt(PROMPT_OVERWRITE_FILE, rDest)) {
default: /* Error */
case 4: /* Quit */
return 0;
case 3: /* All */
optY = 1;
case 1: /* Yes */
break;
case 2: /* No */
continue;
}
}
}
}
if(cbreak) {
return 0;
}
if((fdout = devopen(rDest, openMode)) < 0) {
error_open_file(rDest);
return 0;
}
keepFTime = 1;
if(isadev(fdout)) {
if(destFlags & BINARY) {
/* in forced binary mode character devices are set to raw */
fdsetattr(fdout, (fdattr(fdout) & 0xff) | 0x20);
}
keepFTime = 0;
}
h = src;
keepFTime = (keepFTime && h->app == 0);
do {
fillFnam(rSrc, h->fnam, srcFile);
if(rSrc[0] == 0) {
dos_close(fdout);
unlink(rDest); /* if device -> no removal, ignore error */
return 0;
}
if((fdin = devopen(rSrc, O_RDONLY)) < 0) {
error_open_file(rSrc);
dos_close(fdout);
unlink(rDest); /* if device -> no removal, ignore error */
return 0;
}
if(isadev(fdin)) {
keepFTime = 0; /* Cannot keep file time of devices */
if(h->flags & BINARY)
/* in forced binary mode character devices are set to raw */
fdsetattr(fdin, (fdattr(fdin) & 0xff) | 0x20);
else
/* make sure to stop at Ctrl-Z */
h->flags |= ASCII;
}
if(keepFTime)
#ifdef __TURBOC__
if(getftime(fdin , &fileTime))
#else
if(_dos_getftime(fdin , &date , &time))
#endif
keepFTime = 0; /* if error: turn it off */
displayString(TEXT_MSG_COPYING, rSrc
, (openMode == 'a' || h != src)? "=>>": "=>", rDest);
if(cbreak) {
dos_close(fdin);
dos_close(fdout);
unlink(rDest); /* if device -> no removal, ignore error */
return 0;
}
/* Now copy the file */
rc = 1;
{
FLAG sizeChanged = !(h->flags & ASCII) && singleFileCopy &&
!isadev(fdin) && !isadev(fdout);
if(sizeChanged) { /* faster copy, *MUCH* faster on floppies
change destination filesize to wanted size.
this a) writes all required entries to the
FAT (faster) determines, if there is enough
space on the destination device
no need to copy file, if it won't fit */
/* No test if chsize() fails for MS DOS 5/6 bug
see RBIL DOS-40 */
/* Don't use chsize() as Turbo RTL fills with
'\0' bytes, which is not useful here */
lseek(fdout, filelength(fdin), SEEK_SET);
if(truncate(fdout) != 0
|| lseek(fdout, 0, SEEK_SET) == -1) {
error_write_file_disc_full(rDest, filelength(fdin));
rc = 0;
} else {
dprintf( ("[COPY chsize(%s, %lu)]\n", rDest,
filelength(fdin)) );
}
}
if(rc != 0)
switch(BIGcopy(fdout, fdin, h->flags & ASCII)) {
case 0:
if(sizeChanged)
/* probably the source file got truncated */
/* we silently ignore any failure here, because it is
assumed that we never extend, but truncate the file
only (or do not change the length at all) */
truncate(fdout);
break;
case 1: error_read_file(rSrc); rc = 0; break;
case 2: error_write_file(rDest); rc = 0; break;
default: error_copy(); rc = 0; break;
}
}
if(cbreak)
rc = 0;
dos_close(fdin);
if(!rc) {
dos_close(fdout);
unlink(rDest); /* if device -> no removal, ignore error */
return 0;
}
} while((h = h->app) != 0);
rc = 0;
if((destFlags & ASCII) && !isadev(fdout)) { /* append the ^Z as we copied in ASCII mode */
if (dos_write(fdout, "\x1a", 1) != 1)
rc = 1;
}
if(keepFTime)
#ifdef __TURBOC__
setftime(fdout, &fileTime);
#else
_dos_setftime(fdout, date, time);
#endif
if(dos_close(fdout) != 0)
rc = 1;
if(rc) {
error_write_file(rDest);
unlink(rDest); /* if device -> no removal, ignore error */
return 0;
}
} while (wildcarded && dos_findnext(&ff) == 0);
/*} while(wildcarded && FINDNEXT(&ff) == 0 && !(isfirst = 0)); */
dos_findclose(&ff);
return 1;
}
static int copyFiles(struct CopySource *h)
{ int differ, rc;
rc = 0;
#define dst destFile
if((differ = samefile(h->fnam, dst)) < 0)
error_out_of_memory();
else if(!differ)
rc = copy(dst, h->fnam, h, O_WRONLY|O_TRUNC|O_CREAT);
else if(h->app)
rc = copy(dst, h->fnam, h->app, O_WRONLY|O_APPEND);
else
error_selfcopy(dst);
#undef dst
return rc;
}
static int cpyFlags(void)
{
return (optA? ASCII: 0) | (optB? BINARY: 0);
}
static struct CopySource *srcItem(char *fnam)
{ struct CopySource *h;
if((h = malloc(sizeof(struct CopySource))) == 0) {
error_out_of_memory();
return 0;
}
h->fnam = fnam;
h->nxt = h->app = 0;
h->flags = cpyFlags();
return h;
}
static int addSource(char *p)
{ struct CopySource *h;
char *q;
assert(p);
q = strtok(p, "+");
assert(q && *q);
if(appendToFile) {
appendToFile = 0;
if(!lastApp) {
error_leading_plus();
return 0;
}
} else { /* New entry */
if(0 == (h = srcItem(q)))
return 0;
if(!last)
last = lastApp = head = h;
else
last = lastApp = last->nxt = h;
if((q = strtok(0, "+")) == 0) /* no to-append file */
return 1;
}
/* all the next files are to be appended to the source in "last" */
assert(q);
assert(lastApp);
do {
if(0 == (h = srcItem(q)))
return 0;
lastApp = lastApp->app = h;
} while((q = strtok(0, "+")) != 0);
return 1;
}
int cmd_copy(char *rest)
{ char **argv, *p;
int argc, opts, argi;
struct CopySource *h;
char **argBuffer = 0;
/* Initialize options */
optA = optB = optV = optY = 0;
/* read the parameters from env */
if ((argv = scanCmdline(p = getEnv("COPYCMD"), opt_copy, 0, &argc, &opts))
== 0) {
free(p);
return 1;
}
free(p);
freep(argv); /* ignore any parameter from env var */
if((argv = scanCmdline(rest, opt_copy, 0, &argc, &opts)) == 0)
return 1;
initContext();
/* Now parse the remaining arguments into the copy file
structure */
for(argi = 0; argi < argc; ++argi)
if(isoption(p = argv[argi])) { /* infix /a or /b */
if(leadOptions(&p, opt_copy1, 0) != E_None) {
killContext();
freep(argv);
return 1;
}
/* Change the flags of the previous argument */
if(lastApp)
lastApp->flags = cpyFlags();
} else { /* real argument */
if(*p == '+') { /* to previous argument */
appendToFile = 1;
while(*++p == '+');
if(!*p)
continue;
}
if(!addSource(p)) {
killContext();
freep(argv);
return 1;
}
}
if(appendToFile) {
error_trailing_plus();
killContext();
freep(argv);
return 1;
}
if(!last) { /* Nothing to do */
error_nothing_to_do();
killContext();
freep(argv);
return 1;
}
assert(head);
/* Check whether the source items are files or directories */
h = head;
argc = 0; /* argBuffer entries */
do {
struct CopySource *p = h;
do {
char *s = strchr(p->fnam, '\0') - 1;
if(*s == '/' || *s == '\\' /* forcedly be directory */
|| 0 != (dfnstat(p->fnam) & DFN_DIRECTORY)) {
char **buf;
char *q;
if(*s == ':')
q = dfnmerge(0, p->fnam, 0, "*", "*");
else
q = dfnmerge(0, 0, p->fnam, "*", "*");
if(0 == (buf = realloc(argBuffer, (argc + 2) * sizeof(char*)))
|| !q) {
free(q);
error_out_of_memory();
goto errRet;
}
argBuffer = buf;
buf[argc] = p->fnam = q;
buf[++argc] = 0;
} else if(*s == ':' && (s - p->fnam) > 1) { /* Device name LPT1:, but not X: */
if(!isDeviceName(p->fnam)) {
error_invalid_parameter(p->fnam);
goto errRet;
}
}
} while(0 != (p = p->app));
} while(0 != (h = h->nxt));
destFlags = last->flags;
if(last != head) {
/* The last argument is to be the destination */
if(last->app) { /* last argument is a + b syntax -> no dst! */
error_copy_plus_destination();
goto errRet;
}
destFile = last->fnam;
h = head; /* remove it from argument list */
while(h->nxt != last) {
assert(h->nxt);
h = h->nxt;
}
free(last);
(last = h)->nxt = 0;
} else { /* Nay */
destFile = ".\\*.*";
}
#define dst destFile
/* If the destination specifies a drive, check that it is valid */
if (dst[0] && dst[1] == ':' && !is_valid_disk(dst[0] - 'A')) {
error_invalid_drive(dst[0] - 'A');
return 0;
}
#undef dst
/* Now copy the files */
h = head;
while(copyFiles(h) && (h = h->nxt) != 0);
errRet:
killContext();
freep(argv);
freep(argBuffer);
return 0;
}