windows-nt/Source/XPSP1/NT/sdktools/tc/tc.c
2020-09-26 16:20:57 +08:00

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);
}
}
}