home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The World of Computer Software
/
World_Of_Computer_Software-02-387-Vol-3of3.iso
/
d
/
dlcpmv23.zoo
/
cpmv.c
< prev
next >
Wrap
C/C++ Source or Header
|
1992-03-29
|
20KB
|
721 lines
/*************************************************************************
* DL_CPMV v2.2 92/02/15 by Dennis Lo
* This program and its source code are in the public domain.
*
* DESCRIPTION:
* DL_CPMV is a BSD Unix-like cp and mv for MS-DOS. Unlike most
* other MS-DOS cp and mv programs, this one:
* - has the -p and -v options from BSD/SunOS.
* - can move files across drives.
* - can rename directories as well as files
* - supports environment variable references in file names
* - allows forward slashes to be used in path names
*
* TO COMPILE:
* The same source file contains both MV and CP. To compile, #define
* either CP or MV below, rename this file to cp.c or mv.c, and compile
* with Turbo C++ v1.01, small memory model:
* eg. tcc cp.c or
* tcc mv.c
* Other versions of Turbo C should also work but have not been tested.
* User documentation is embedded in HelpExit().
* Notes: a "filename" means something like "c:/etc/zansi.sys". A
* "filespec" means something like "c:/etc/*.sys".
*
* TEST SUITE:
* - mv/cp within current dir: mv mv.obj mv.2
* - mv/cp from current dir to other dir: mv mv.obj ..
* - mv/cp from other dir to another dir: mv c:/d/work/mv.obj /tmp/mv.o
* - mv/cp across drives: mv mv.obj e:
* - mv/cp across drives with paths: mv c:/d/work/mv.obj e:/tmp
* - test -f/-i flags cp -p cp.obj mv.obj
* - test -p/-n flags: mv -p mv.obj a:
* - test -v/-q flags: mv -q mv.obj mv.2
* - test several flags together: mv -ipn mv.obj mv.2
* - test flags in env variable: set MV=piv
* - mv/cp -r across drives mv -r c:/tmp/1 e:/
* - mv/cp -r on same drive mv -r /tmp/1/*.* /tmp/2
* - mv/cp no -r: subdirs not moved mv *.* ..
*
* CHANGE LOG:
* Version 2.3 92/03/30:
* - return error code of 0 when successful
* Version 2.2 92/01/08:
* - fixed cp -r bug that tries to remove subdirectories
* Version 2.1 92/01/01:
* - added -r option
* - added explicit wildcard expansion to allow cases like $junk/*.*
* Version 2.0 91/12/28:
* - switched from Datalight C to Turbo C++ (and the .exe grew by 5K !)
* - mv defaults to -p instead of -n
* - mv can rename directories now
* Version 1.6 91/01/20:
* - allow moving across drives
* TODO:
* - Switch disks if src<>dst drive, and dest drive is a floppy, and
* dest free space < current file size
* - package for release
*************************************************************************/
/*
* Define one of the following to indicate whether this is 'mv' or 'cp'
*/
#define CP
/*
#define MV
*/
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <dos.h>
#include <dir.h>
#include <conio.h>
#include <stdlib.h>
/*=====================================================================
* Macros
*=====================================================================
*/
/* Macro to write a string to stdout (avoids the need to use printf) */
#define PUT(str) fputs (str, stdout)
/*=====================================================================
* Global Variables
*=====================================================================
*/
int Force_flag = 1;
int Verbose_flag = 0;
int Recursive_flag = 0;
#ifdef CP
int Preserve_flag = 0; /* CP: use new timestamp by default */
#else
int Preserve_flag = 1; /* MV: preserve timestamp by default */
#endif
/*=====================================================================
* Print a help message and exit
*=====================================================================
*/
void
HelpExit ()
{
#ifdef CP
PUT ("DL_CP - a BSD Unix style file copy utility v2.3 92/03/30 Dennis Lo\n");
PUT ("USAGE: cp [-fipnvq] file dest_file\n");
PUT (" or: cp [-fipnvq] file1 [file2...fileN] dest_directory\n");
PUT (" -f = Force overwrites without prompting (default).\n");
PUT (" -i = Interactively prompt before overwriting (opposite of -f).\n");
PUT (" -p = Preserve original timestamp when copying.\n");
PUT (" -n = use New timestamp when copying (opposite of -p). (default)\n");
PUT (" -v = Verbose - show each file copied.\n");
PUT (" -q = Quiet - opposite of -v (default).\n");
PUT (" -r = Recursively descend & copy sub-directories.\n");
PUT ("Default arguments can be put in the CP environment variable. (eg. 'set CP=ipv')\n");
PUT ("File names can contain environment variable references (eg. 'cp $work/*.c a:')\n");
#else
PUT ("DL_MV - a BSD Unix style file move utility v2.3 92/03/30 Dennis Lo\n");
PUT ("USAGE: mv [-fipnvq] old_file new_file\n");
PUT (" or: mv [-fipnvq] file1 [file2...fileN] dest_directory\n");
PUT (" -f = Force overwrites without prompting (default).\n");
PUT (" -i = Interactively prompt before overwriting (opposite of -f).\n");
PUT (" -p = Preserve original timestamp when copying. (default)\n");
PUT (" -n = use New timestamp when copying (opposite of -p).\n");
PUT (" -v = Verbose - show each file copied.\n");
PUT (" -q = Quiet - opposite of -v (default).\n");
PUT (" -r = Recursively descend & move sub-directories.\n");
PUT ("Can move directories. Can move files from one drive to another.\n");
PUT ("Default arguments can be put in the MV environment variable. (eg. 'set MV=ipv')\n");
PUT ("File names can contain environment variable references (eg. 'mv $work/*.c a:')\n");
#endif
exit (1);
}
/*=====================================================================
* Return TRUE if a filespec is a file
*=====================================================================
*/
IsFile (filespec)
char *filespec;
{
struct ffblk findblk;
return (findfirst (filespec, &findblk, 0) == 0);
/* maybe should change the 0 to FA_HIDDEN|FA_SYSTEM */
}
/*=====================================================================
* Return TRUE if a filespec is a directory
*=====================================================================
*/
IsDir (filespec)
char *filespec;
{
struct ffblk findblk;
int lastchar = filespec [strlen (filespec) - 1];
return ((findfirst (filespec, &findblk, FA_DIREC) == 0
&& !IsFile (filespec))
|| lastchar == '.' || lastchar == '/' || lastchar == '\\');
}
/*=====================================================================
* Interpret an arguments string of the form "fiv" (not "-f -iv")
*=====================================================================
*/
void
ParseArgs (str)
char *str;
{
char *param;
static char errmsg[] = "-?\n";
for (param = str; *param; param++)
{
switch (*param)
{
case 'f': /* Force overwrites */
Force_flag = 1;
break;
case 'i': /* Interactive */
Force_flag = 0;
break;
case 'n': /* cp: create New file */
Preserve_flag = 0;
break;
case 'p': /* cp: Preserve timestamp & modes */
Preserve_flag = 1;
break;
case 'q': /* Quiet */
Verbose_flag = 0;
break;
case 'r': /* Recursive */
Recursive_flag = FA_DIREC;
break;
case 'v': /* Verbose */
Verbose_flag = 1;
break;
default:
errmsg[1] = *param;
PUT ("Invalid parameter "); PUT (errmsg);
HelpExit ();
}
}
}
/*=====================================================================
* Return the drive letter of a filename
*=====================================================================
*/
char
DriveName (filename)
char *filename;
{
char drive;
/*
* If the drive name is embedded in the given file name then
* extract it
*/
if (filename[1] == ':')
{
drive = filename[0];
}
/* else return current drive */
else
{
char path [80];
getcwd (path, 79);
drive = *path;
}
return (toupper (drive));
}
/*=====================================================================
* Return a pointer to the filename portion of a filespec.
* (Skips the drive specifier and the path)
*=====================================================================
*/
char *
FileName (filename)
char *filename;
{
char *p;
/* Start at end of filename */
p = filename + strlen (filename) - 1;
/* Scan for first '/' or '\' or ':' */
while (p >= filename)
{
if (*p == '/' || *p == '\\' || *p == ':')
break;
p--;
}
return (p + 1);
}
/*=====================================================================
* Copy the path portion of a filename (including the last '/').
*=====================================================================
*/
void
PathName (filename, pathname)
char *filename; /* src */
char *pathname; /* dest: caller provides storage */
{
char *path_end = FileName (filename);
int path_len = path_end - filename;
if (path_len < 0)
path_len = 0;
strncpy (pathname, filename, path_len);
pathname [path_len] = '\0';
}
/*=====================================================================
* Expand the environment varible references in a filespec.
* The caller must provide storage for the expanded filespec.
* Only one environment variable is allowed per filespec.
*=====================================================================
*/
void
ExpandEnvVar (oldname, newname)
char *oldname;
char *newname;
{
int len, i;
char *envvar_start;
char *expanded_name;
char envvar [80];
while (*oldname)
{
/*
* Scan for the start of an environment variable reference,
* copying to newname at the same time.
*/
while (*oldname && *oldname != '$')
*newname++ = *oldname++;
if (!*oldname) /* if at end of old name */
{
*newname = '\0';
break;
}
envvar_start = ++oldname;
/*
* Scan for the end of the environment variable name
*/
len = 0;
while (*oldname &&
*oldname != '/' && *oldname != '\\' &&
*oldname != ':' && *oldname != '$' && *oldname != ' ')
{
oldname++;
len++;
}
/*
* Convert the env variable name to upper case, and
* Expand the environment variable and add it to newname
*/
for (i=0; i<len; i++)
envvar [i] = envvar_start [i] & (255-32);
envvar [len] = '\0';
expanded_name = getenv (envvar);
if (expanded_name)
{
strcpy (newname, expanded_name);
newname += strlen (expanded_name);
}
else
*newname = '\0';
/*
* Copy the rest of oldname to the newname
*/
while (*oldname && *oldname != ' ')
*newname++ = *oldname++;
*newname = '\0';
}
}
/*=====================================================================
* Copy a file
*=====================================================================
*/
int
Copy (src, dest, preserve_date)
char *src;
char *dest;
int preserve_date; /* non-zero = preserve src date on dest file */
{
int src_fd, dst_fd;
unsigned bytes_read;
union REGS regs;
/* File copying buffer */
# define BUFSIZE 49152
static char *Filebuf = NULL;
/* Allocate the file buffer if not already done */
if (Filebuf == NULL)
{
Filebuf = malloc (BUFSIZE);
if (Filebuf == NULL)
{
PUT ("Not enough memory!\n");
exit (1);
}
}
/* Open the source file */
if ((src_fd = open (src, O_BINARY)) == -1)
{
PUT ("Can't open source file "); PUT(src);
return (1);
}
/* Open the destination file */
if ((dst_fd = open (dest, O_CREAT|O_TRUNC|O_BINARY, S_IREAD|S_IWRITE))
== -1)
{
PUT ("Can't create destination file!");
return (1);
}
/* Copy the contents of the source file into the destination file */
while ((bytes_read = read (src_fd, Filebuf, BUFSIZE)) > 0)
{
if (write (dst_fd, Filebuf, bytes_read) < bytes_read)
{
PUT ("Error writing file "); PUT (dest);
return (1);
}
}
/* Set the dest file's date to the source file's date */
if (preserve_date)
{
/* Fetch the timestamp of the src file */
regs.x.ax = 0x5700;
regs.x.bx = src_fd;
int86 (0x21, ®s, ®s);
/* Set the dest file's timestamp to the timestamp just fetched */
regs.x.ax = 0x5701;
regs.x.bx = dst_fd;
int86 (0x21, ®s, ®s);
}
/* Close the src and dest files */
if (close (src_fd) == -1)
{
PUT ("Error closing "); PUT (src);
return (1);
}
if (close (dst_fd) == -1)
{
PUT ("Error closing "); PUT (dest);
return (1);
}
return (0);
}
/*=====================================================================
* Copy a file and print informative msgs. Returns 0 if failed.
*=====================================================================
*/
int
DoCopy (src_name, dst_name)
char *src_name;
char *dst_name;
{
if (IsDir (src_name))
return 0;
/*
* Print status message if verbose flag is on
*/
if (Verbose_flag)
{
PUT("Copying ") ;PUT(src_name); PUT(" to "); PUT(dst_name); PUT("\n");
}
/*
* Do the copy; if it fails then print a msg and delete the incomplete
* dest file.
*/
if (strcmp (src_name, dst_name) == 0
|| Copy (src_name, dst_name, Preserve_flag) != 0)
{
PUT(" ...Copy to "); PUT(dst_name); PUT(" failed\n");
unlink (dst_name);
return 0;
}
return 1;
}
#ifndef CP
/*=====================================================================
* Move a file and print informative msgs.
*=====================================================================
*/
void
DoMove (src_name, dst_name)
char *src_name;
char *dst_name;
{
/*
* If the destination file is on a different drive, then copy the
* src to the dest and then delete the src.
*/
if (DriveName (dst_name) != DriveName (src_name))
{
if (DoCopy (src_name, dst_name))
{
if (Verbose_flag)
{
PUT("Removing "); PUT(src_name); PUT("\n");
}
unlink (src_name);
rmdir (src_name);
}
}
/*
* Else (on the same drive) rename the file
*/
else
{
if (Verbose_flag)
{
PUT("Moving "); PUT(src_name);
PUT(" to "); PUT(dst_name); PUT("\n");
}
if (rename (src_name, dst_name) != 0)
{
/*
* If the move failed because the destination is on a different
* drive, copy the file and delete the source file.
*/
PUT(" ...Move to "); PUT(dst_name); PUT(" failed\n");
}
}
}
#endif
/*=====================================================================
* Do the copy/move for 1 source filename
*=====================================================================
*/
void
Do1File (src_name, dst_base)
char *src_name;
char *dst_base;
{
char dst_name[80]; /* current destination file name */
int ch;
/*
* Build the destination file's name.
* If the destination is a directory then append the src filename.
* (preceded by a '/' if necessary)
*/
strcpy (dst_name, dst_base);
if (IsDir (dst_base))
{
if (dst_name [strlen (dst_name)-1] != '/' &&
dst_name [strlen (dst_name)-1] != '\\')
strcat (dst_name, "/");
strcat (dst_name, FileName (src_name));
}
/*
* if the destination is a file that already exists
*/
if (IsFile (dst_name))
{
/* if force flag is off then prompt before deleting */
if (!Force_flag)
{
PUT ("Overwrite "); PUT (dst_name); PUT (" ? ");
fflush (stdout);
ch = getche();
/* If the answer is not YES then skip this file */
if (ch != 'y' && ch != 'Y')
{
PUT ("\n");
return;
}
if (Verbose_flag)
PUT (" ");
else
PUT ("\n");
}
/* delete the existing file */
unlink (dst_name);
}
/*
* if the source is a directory and the -r option is set
* Create the dest directory if it doesn't already exist.
* Recursively copy/move the source directory's contents.
* Delete the src directory if in 'mv'
* else
* Copy or move the file
*/
if (IsDir (src_name))
{
if (Recursive_flag)
{
mkdir (dst_name);
strcat (src_name, "/*.*");
Do1Filespec (src_name, dst_name);
#ifndef CP
/* remove the "/*.*" from src_name and rmdir it */
src_name [strlen(src_name) - 4] = '\0';
if (Verbose_flag)
{
PUT("Removing directory "); PUT(src_name); PUT("\n");
}
rmdir (src_name);
#endif
}
#ifndef CP
/* else ignore src directories unless renaming a directory */
else if (!IsDir (dst_base))
DoMove (src_name, dst_name);
#endif
}
else
#ifdef CP
DoCopy (src_name, dst_name);
#else
DoMove (src_name, dst_name);
#endif
}
/*=====================================================================
* Do the copy/move for 1 source filespec (eg. c:/etc/*.*)
*=====================================================================
*/
int
Do1Filespec (src_base, dst_base)
char *src_base;
char *dst_base;
{
char src_name[80]; /* current source file name */
struct ffblk findblk;
/* Start the wildcard expansion process */
if (findfirst (src_base, &findblk, FA_DIREC) != 0)
return 0;
/* Loop once for each expanded wildcard file name */
do
{
/* Skip the '.' and '..' files */
if (findblk.ff_name[0] == '.')
continue;
/* Prepend the src path to the found file name */
PathName (src_base, src_name);
strcat (src_name, findblk.ff_name);
Do1File (src_name, dst_base);
} while (findnext (&findblk) == 0);
return 1;
}
/*=====================================================================
* Main
*=====================================================================
*/
void
main (argc, argv)
int argc;
char **argv;
{
int srci; /* argv index of current source name */
int desti = argc - 1; /* argv index of destination name */
char *env_opt; /* environment variable options string */
char src_base[80]; /* source file name base */
char dst_base[80]; /* destination file name base */
int len;
int par;
/*
* Get the options from the environment variables, then get the options
* from the command line switches. Command line options can be either
* in the form '-f -i -v' or the form '-fiv'.
*/
#ifdef CP
env_opt = getenv ("CP");
#else
env_opt = getenv ("MV");
#endif
if (env_opt)
ParseArgs (env_opt);
for (par=1; par<argc && *argv[par] == '-'; par++)
ParseArgs (argv[par] + 1);
/*
* If no filename arguments given then print help.
* If not enough filename arguments given then print error message.
*/
if (desti - par < 0)
HelpExit();
if (desti - par < 1)
{
PUT (argv[0]); PUT (": Not enough files specified.\n");
exit (1);
}
/*
* Expand the destination name for env vars.
* For filenames ending in ':', add a '.' (eg. "a:" becomes "a:.")
*/
ExpandEnvVar (argv [desti], dst_base);
len = strlen (dst_base);
if (dst_base [len-1] == ':')
{
dst_base [len] = '.';
dst_base [len+1] = '\0';
}
/*
* Error if the destination is not a dir and there are > 1 source files
*/
if (desti - par > 1 && !IsDir (dst_base))
{
#ifdef CP
PUT ("Can't copy multiple files to the same destination file\n");
#else
PUT ("Can't move multiple files to the same destination file\n");
#endif
exit (1);
}
/*
* Loop once for each source filespec on the command line
*/
for (srci = par; srci < desti; srci++)
{
/* Expand the env variables in the source filename */
ExpandEnvVar (argv [srci], src_base);
if (Do1Filespec (src_base, dst_base) == 0)
{
PUT (src_base); PUT(": file(s) not found.\n");
}
}
exit (0);
}