549 lines
18 KiB
C
549 lines
18 KiB
C
/* tc.c - general purpose tree copy program
|
|
*
|
|
* tc.c recursively walks the source tree and copies the entire structure
|
|
* to the destination tree, creating directories as it goes along.
|
|
*
|
|
* 2/18/86 dan lipkie correct error msg v[0] -> v[1]
|
|
* 2/18/86 dan lipkie allow for out of space on destination
|
|
* 4/11/86 dan lipkie add /h switch
|
|
* 4/13/86 dan lipkie allow all switches to use same switch char
|
|
* 17-Jun-1986 dan lipkie add /n, allow ^C to cancel
|
|
* 11-Jul-1986 dan lipkie add /s
|
|
* 21-Jul-1986 dan lipkie add MAXDIRLEN
|
|
* 06-Nov-1986 mz add /L
|
|
* 13-May-1987 mz add /F
|
|
* 15-May-1987 mz Make /F display dirs too
|
|
* 11-Oct-1989 reubenb fix /L parsing (?)
|
|
* add some void declarations
|
|
* 19-Oct-1989 mz
|
|
*
|
|
*/
|
|
#include <direct.h>
|
|
#include <sys\types.h>
|
|
#include <sys\stat.h>
|
|
#include <io.h>
|
|
#include <conio.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <process.h>
|
|
#include <ctype.h>
|
|
#include <windows.h>
|
|
#include <tools.h>
|
|
|
|
|
|
// Forward Function Declartions...
|
|
void CopyNode( char *, struct findType *, void * );
|
|
void MakeDir( char * );
|
|
void __cdecl Usage( char *, ... );
|
|
void errorexit( char *, unsigned, unsigned, unsigned );
|
|
void ChkSpace( int, LONGLONG );
|
|
int FormDest( char * );
|
|
|
|
char const rgstrUsage[] = {
|
|
"Usage: TC [/adhijnqrstAFLS] src-tree dst-tree\n"
|
|
" /a only those files with the archive bit on are copied\n"
|
|
" /b copies to inuse files are delayed to reboot\n"
|
|
" /d deletes source files/directories as it copies them\n"
|
|
" /h copy hidden directories, implied by /d\n"
|
|
" /i ignore hidden, has nothing to do with hidden dir\n"
|
|
" /j ignore system files, has nothing to do with hidden dir\n"
|
|
" /n no subdirectories\n"
|
|
" /q silent operation. Normal mode displays activity\n"
|
|
" /r read-only files are overwritten\n"
|
|
" /s structure only\n"
|
|
" /t only those files with source time > dest time are copied\n"
|
|
" /A allow errors from copy (won't delete if /d present)\n"
|
|
" /F list files that would be copied\n"
|
|
" /L large disk copy (no full disk checking)\n"
|
|
" /S produce batch script to do copy"
|
|
};
|
|
|
|
flagType fReboot = FALSE; // TRUE => delay reboot
|
|
flagType fDelete = FALSE; // TRUE => delete/rmdir source after
|
|
flagType fQuiet = FALSE; // TRUE => no msg except for error
|
|
flagType fArchive = FALSE; // TRUE => copy only ARCHIVEed files
|
|
flagType fTime = FALSE; // TRUE => copy later dated files
|
|
flagType fHidden = FALSE; // TRUE => copy hidden directories
|
|
flagType fNoSub = FALSE; // TRUE => do not copy subdirect
|
|
flagType fStructure = FALSE; // TRUE => copy only directory
|
|
flagType fInCopyNode = FALSE; // TRUE => prevent recursion
|
|
flagType fIgnoreHidden = FALSE; // TRUE => don't consider hidden
|
|
flagType fIgnoreSystem; // TRUE => don't consider system
|
|
flagType fOverwriteRO; // TRUE => ignore R/O bit
|
|
flagType fLarge = FALSE; // TRUE => disables ChkSpace
|
|
flagType fFiles = FALSE; // TRUE => output files
|
|
flagType fScript = FALSE; // TRUE => output files as script
|
|
flagType fAllowError = FALSE; // TRUE => fcopy errors ignored
|
|
flagType fRebootNecessary = FALSE; // TRUE => reboot ultimately necessary
|
|
|
|
|
|
|
|
char source[MAX_PATH];
|
|
char dest[MAX_PATH];
|
|
char tempdir[MAX_PATH];
|
|
char tempfile[MAX_PATH];
|
|
int drv;
|
|
|
|
int srclen, dstlen;
|
|
|
|
|
|
/* Usage takes a variable number of strings, terminated by zero,
|
|
* e.g. Usage ("first ", "second ", 0);
|
|
*/
|
|
void __cdecl Usage( char *p, ... )
|
|
{
|
|
char **rgstr;
|
|
|
|
rgstr = &p;
|
|
if (*rgstr) {
|
|
fprintf (stderr, "TC: ");
|
|
while (*rgstr)
|
|
fprintf(stderr, "%s", *rgstr++);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
fputs(rgstrUsage, stderr);
|
|
exit (1);
|
|
}
|
|
|
|
void errorexit (fmt, a1, a2, a3)
|
|
char *fmt;
|
|
unsigned a1, a2, a3;
|
|
{
|
|
fprintf (stderr, fmt, a1, a2, a3);
|
|
fprintf (stderr, "\n");
|
|
exit (1);
|
|
}
|
|
|
|
|
|
/* chkspace checks to see if there is enough space on drive d to hold a file
|
|
* of size l. If not, requests a disk swap
|
|
*/
|
|
void ChkSpace (d, l)
|
|
int d;
|
|
LONGLONG l;
|
|
{
|
|
char *pend;
|
|
char pathStr[MAX_PATH];
|
|
int i;
|
|
|
|
if (!fLarge)
|
|
while (freespac (d) < sizeround (l, d)) {
|
|
_cprintf ("Please insert a new disk in drive %c: and strike any key",
|
|
d + 'A'-1);
|
|
if (_getch () == '\003') /* ^C */
|
|
exit (1);
|
|
_cprintf ("\n\r");
|
|
pend = pathStr;
|
|
drive(dest, pend);
|
|
pend += strlen(pend);
|
|
path(dest, pend);
|
|
if (fPathChr(pathStr[(i = (strlen(pathStr) - 1))]) && i > 2)
|
|
pathStr[i] = '\0';
|
|
MakeDir(pathStr);
|
|
}
|
|
}
|
|
|
|
|
|
__cdecl main (c, v)
|
|
int c;
|
|
char *v[];
|
|
{
|
|
struct findType fbuf;
|
|
char *p;
|
|
|
|
ConvertAppToOem( c, v );
|
|
SHIFT(c,v);
|
|
while (c && fSwitChr (*v[ 0 ])) {
|
|
p = v[ 0 ];
|
|
SHIFT(c,v);
|
|
while (*++p)
|
|
switch (*p) {
|
|
case 'b':
|
|
fReboot = TRUE;
|
|
break;
|
|
case 'd':
|
|
fDelete = TRUE;
|
|
/* fall through; d => h */
|
|
case 'h':
|
|
fHidden = TRUE;
|
|
break;
|
|
case 'S':
|
|
fScript = TRUE;
|
|
/* Fall through implies FILES and QUIET */
|
|
case 'F':
|
|
fFiles = TRUE;
|
|
/* Fall through implies QUIET */
|
|
case 'q':
|
|
fQuiet = TRUE;
|
|
break;
|
|
case 'a':
|
|
fArchive = TRUE;
|
|
break;
|
|
case 't':
|
|
fTime = TRUE;
|
|
break;
|
|
case 'n':
|
|
fNoSub = TRUE;
|
|
break;
|
|
case 's':
|
|
fStructure = TRUE;
|
|
break;
|
|
case 'i':
|
|
fIgnoreHidden = TRUE;
|
|
break;
|
|
case 'j':
|
|
fIgnoreSystem = TRUE;
|
|
break;
|
|
case 'r':
|
|
fOverwriteRO = TRUE;
|
|
break;
|
|
case 'L':
|
|
fLarge = TRUE;
|
|
break;
|
|
case 'A':
|
|
fAllowError = TRUE;
|
|
break;
|
|
default:
|
|
Usage ( "Invalid switch - ", p, 0 );
|
|
}
|
|
}
|
|
|
|
if (fStructure && fDelete)
|
|
Usage ("Only one of /d and /s may be specified at a time", 0);
|
|
if (c != 2)
|
|
Usage (0);
|
|
if (rootpath (v[0], source))
|
|
Usage ("Invalid source", v[0], 0);
|
|
if (rootpath (v[1], dest))
|
|
Usage ("Invalid dest", v[1], 0); /* M000 */
|
|
srclen = strlen (source);
|
|
dstlen = strlen (dest);
|
|
if (!strcmp(source, dest))
|
|
Usage ("Source == dest == ", source, 0);
|
|
fbuf.fbuf.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
|
|
drv = toupper(*dest) - 'A' + 1;
|
|
CopyNode (source, &fbuf, NULL);
|
|
|
|
return( fRebootNecessary ? 2 : 0 );
|
|
}
|
|
|
|
/* copy node walks the source node and its children (recursively)
|
|
* and creats the appropriate parts on the dst node
|
|
*/
|
|
void
|
|
CopyNode (
|
|
char *p,
|
|
struct findType *pfb,
|
|
void *dummy
|
|
)
|
|
{
|
|
char *pend;
|
|
int attr;
|
|
flagType fCopy;
|
|
flagType fDestRO;
|
|
|
|
DWORD Status;
|
|
char *pszError;
|
|
|
|
FormDest (p);
|
|
if (TESTFLAG (pfb->fbuf.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY)) {
|
|
/* If we're to exclude subdirectories, and we're in one then
|
|
* skip it altogether
|
|
*/
|
|
if (fNoSub && fInCopyNode)
|
|
return;
|
|
fInCopyNode = TRUE;
|
|
|
|
/* Skip the . and .. entries; they're useless
|
|
*/
|
|
if (!strcmp (pfb->fbuf.cFileName, ".") || !strcmp (pfb->fbuf.cFileName, ".."))
|
|
return;
|
|
|
|
/* if we're excluding hidden and this one is then
|
|
* skip it altogether
|
|
*/
|
|
if (!fHidden && TESTFLAG (pfb->fbuf.dwFileAttributes, FILE_ATTRIBUTE_HIDDEN))
|
|
return;
|
|
|
|
/* if we're not just outputting the list of files then
|
|
* Make sure that the destination dir exists
|
|
*/
|
|
if ( !fFiles ) {
|
|
ChkSpace(drv, 256);
|
|
}
|
|
MakeDir (dest);
|
|
|
|
pend = strend (p);
|
|
if (!fPathChr (pend[-1]))
|
|
strcat (p, "\\");
|
|
strcat (p, "*.*");
|
|
forfile (p, FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM, CopyNode, NULL);
|
|
*pend = '\0';
|
|
|
|
/* if we're not just outputting files then
|
|
* if we're to delete this node then
|
|
* ...
|
|
*/
|
|
if (!fFiles)
|
|
if (fDelete)
|
|
if (_rmdir (p) == -1)
|
|
Usage ("Unable to rmdir ", p, " - ", error (), 0);
|
|
}
|
|
else
|
|
if (!fStructure) {
|
|
if (_access(p, 04) == -1) /* If we can read the source */
|
|
Usage ("Unable to peek status of ", p, " - ", error (), 0);
|
|
|
|
/* do not copy the file if:
|
|
* fIgnoreHidden && hidden
|
|
* fIgnoreSystem && system
|
|
* fArchive and archive bit not set
|
|
* dest exists &&
|
|
* fTime && src <= dest time ||
|
|
* dest is readonly && !fOverwriteRO
|
|
*/
|
|
|
|
fCopy = (flagType)TRUE;
|
|
fDestRO = (flagType)FALSE;
|
|
/* If destination exists, check the time of the destination to
|
|
* see if we should copy the file
|
|
*/
|
|
if (_access (dest, 00) != -1 ) {
|
|
struct _stat srcbuf;
|
|
struct _stat dstbuf;
|
|
/* We have now determined that both the source and destination
|
|
* exist, we now want to check to see if the destination is
|
|
* read only, and if the /T switch was specified if the
|
|
* destination is newer than the source.
|
|
*/
|
|
if (_stat (p, &srcbuf) != -1) {/* if source is stat'able */
|
|
if (_stat (dest, &dstbuf) != -1 ) { /* and destination too, */
|
|
attr = GetFileAttributes( dest ); /* get dest's flag */
|
|
fDestRO = (flagType)TESTFLAG ( attr, FILE_ATTRIBUTE_READONLY ); /* Flag dest R.O. */
|
|
if ( fTime && srcbuf.st_mtime <= dstbuf.st_mtime)
|
|
fCopy = FALSE;
|
|
else
|
|
if ( fDestRO && !fOverwriteRO ) {
|
|
if (!fQuiet)
|
|
printf ("%s => not copied, destination is read only\n", p);
|
|
fCopy = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (fCopy && fIgnoreHidden && TESTFLAG (pfb->fbuf.dwFileAttributes, FILE_ATTRIBUTE_HIDDEN))
|
|
fCopy = FALSE;
|
|
if (fCopy && fIgnoreSystem && TESTFLAG (pfb->fbuf.dwFileAttributes, FILE_ATTRIBUTE_SYSTEM))
|
|
fCopy = FALSE;
|
|
if (fCopy && fArchive && !TESTFLAG (pfb->fbuf.dwFileAttributes, FILE_ATTRIBUTE_ARCHIVE))
|
|
fCopy = FALSE;
|
|
if (fCopy) {
|
|
if (!fFiles) {
|
|
if (fDestRO) {
|
|
RSETFLAG (attr, FILE_ATTRIBUTE_READONLY);
|
|
SetFileAttributes( dest, attr );
|
|
}
|
|
_unlink(dest);
|
|
ChkSpace(drv, FILESIZE(pfb->fbuf));
|
|
}
|
|
if (!fQuiet)
|
|
printf ("%s => %s\t", p, dest);
|
|
|
|
Status = NO_ERROR;
|
|
pszError = "[OK]";
|
|
|
|
if (fFiles) {
|
|
if (fScript)
|
|
printf ("copy %s %s\n", p, dest);
|
|
else
|
|
printf ("file %s\n", p, dest);
|
|
}
|
|
else
|
|
if (!CopyFile (p, dest, FALSE)) {
|
|
pszError = error ();
|
|
Status = GetLastError ();
|
|
|
|
// If we received a sharing violation, we try to perform
|
|
// a boot-delayed copy.
|
|
|
|
do {
|
|
if (Status != ERROR_SHARING_VIOLATION)
|
|
continue;
|
|
|
|
if (!fReboot)
|
|
continue;
|
|
|
|
Status = NO_ERROR;
|
|
pszError = "[reboot necessary]";
|
|
|
|
// We attempt to delay this operation until reboot.
|
|
// Since there is at least one DLL that we cannot
|
|
// rename in this fashion, we perform delayed DELETE
|
|
// of unused files.
|
|
|
|
// get a temp name in the same directory
|
|
upd (dest, ".", tempdir);
|
|
if (GetTempFileName (tempdir, "tc", 0, tempfile) == 0) {
|
|
pszError = error ();
|
|
Status = GetLastError ();
|
|
continue;
|
|
}
|
|
|
|
// rename dest file to temp name
|
|
if (!MoveFileEx (dest, tempfile, MOVEFILE_REPLACE_EXISTING)) {
|
|
pszError = error ();
|
|
Status = GetLastError ();
|
|
DeleteFile (tempfile);
|
|
continue;
|
|
}
|
|
|
|
// copy again
|
|
if (!CopyFile (p, dest, TRUE)) {
|
|
pszError = error ();
|
|
Status = GetLastError ();
|
|
DeleteFile (dest);
|
|
MoveFile (tempfile, dest);
|
|
continue;
|
|
}
|
|
|
|
// mark temp for delete
|
|
if (!MoveFileEx (tempfile, NULL, MOVEFILE_DELAY_UNTIL_REBOOT)) {
|
|
pszError = error ();
|
|
Status = GetLastError ();
|
|
DeleteFile (dest);
|
|
MoveFile (tempfile, dest);
|
|
continue;
|
|
}
|
|
|
|
fRebootNecessary = TRUE;
|
|
} while (FALSE);
|
|
|
|
}
|
|
|
|
/* Display noise if we're not quiet
|
|
*/
|
|
if (!fQuiet)
|
|
printf ("%s\n", pszError);
|
|
|
|
/* If we got an error and we're not supposed to ignore them
|
|
* quit and report error
|
|
*/
|
|
if (Status != NO_ERROR)
|
|
if (!fAllowError)
|
|
Usage ("Unable to copy ", p, " to ", dest, " - ", pszError, 0);
|
|
else
|
|
printf ("Unable to copy %s to %s - %s\n", p, dest, pszError);
|
|
|
|
/* If we're not just producing a file list and no error on copy
|
|
*/
|
|
if (!fFiles && Status == NO_ERROR) {
|
|
|
|
/* If we're supposed to copy archived files and archive was
|
|
* set, go reset the source
|
|
*/
|
|
if (fArchive && TESTFLAG (pfb->fbuf.dwFileAttributes, FILE_ATTRIBUTE_ARCHIVE)) {
|
|
RSETFLAG (pfb->fbuf.dwFileAttributes, FILE_ATTRIBUTE_ARCHIVE);
|
|
if( SetFileAttributes( p, pfb->fbuf.dwFileAttributes ) == -1 )
|
|
Usage ("Unable to set ", p, " attributes - ", error (), 0);
|
|
}
|
|
|
|
/* Copy attributes from source to destination
|
|
*/
|
|
SetFileAttributes( dest, pfb->fbuf.dwFileAttributes );
|
|
|
|
/* If we're supposed to delete the entry
|
|
*/
|
|
if (fDelete) {
|
|
|
|
/* If the source was read-only then
|
|
* reset the source RO bit
|
|
*/
|
|
if (TESTFLAG (pfb->fbuf.dwFileAttributes, FILE_ATTRIBUTE_READONLY))
|
|
if( SetFileAttributes( p, 0 ) == -1 )
|
|
Usage ("Unable to set attributes of ", " - ", error (), 0);
|
|
|
|
/* Delete source and report error
|
|
*/
|
|
if (_unlink (p) == -1)
|
|
Usage ("Unable to del ", p, " - ", error (), 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dummy;
|
|
}
|
|
|
|
/* given a source pointer, form the correct destination from it
|
|
*
|
|
* cases to consider:
|
|
*
|
|
* source dest p realdest
|
|
* D:\path1 D:\path2 D:\path1\path3 D:\path2\path3
|
|
* D:\ D:\path1 D:\path2\path3 D:\path1\path2\path3
|
|
* D:\path1 D:\ D:\path1\path2 D:\path2
|
|
* D:\ D:\ D:\ D:\
|
|
*/
|
|
FormDest (p)
|
|
char *p;
|
|
{
|
|
char *subsrc, *dstend;
|
|
|
|
subsrc = p + srclen;
|
|
if (fPathChr (*subsrc))
|
|
subsrc++;
|
|
dstend = dest + dstlen;
|
|
if (fPathChr (dstend[-1]))
|
|
dstend--;
|
|
*dstend = '\0';
|
|
if (*subsrc != '\0') {
|
|
_strlwr(subsrc);
|
|
strcat (dest, "\\");
|
|
strcat (dest, subsrc);
|
|
}
|
|
return( 0 );
|
|
}
|
|
|
|
/* attempt to make the directory in pieces */
|
|
void MakeDir (p)
|
|
char *p;
|
|
{
|
|
struct _stat dbuf;
|
|
char *pshort;
|
|
int i;
|
|
|
|
if (strlen (p) > 3) {
|
|
|
|
if (_stat (p, &dbuf) != -1)
|
|
if (!TESTFLAG (dbuf.st_mode, S_IFDIR))
|
|
Usage (p, " is a file", 0);
|
|
else
|
|
return;
|
|
|
|
pshort = strend (p);
|
|
while (pshort > p)
|
|
if (fPathChr (*pshort))
|
|
break;
|
|
else
|
|
pshort--;
|
|
/* pshort points to last path separator */
|
|
*pshort = 0;
|
|
MakeDir (p);
|
|
*pshort = '\\';
|
|
if (!fQuiet)
|
|
printf ("Making %s\t", p);
|
|
if (fFiles)
|
|
if (fScript)
|
|
printf ("mkdir %s\n", p);
|
|
else
|
|
printf ("dir %s\n", p);
|
|
else {
|
|
i = _mkdir (p);
|
|
if (!fQuiet)
|
|
printf ("%s\n", i != -1 ? "[OK]" : "");
|
|
if (i == -1)
|
|
Usage ("Unable to mkdir ", p, " - ", error (), 0);
|
|
}
|
|
}
|
|
}
|