/* 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 #include #include #include #include #include #include #include #include #include #include #include // 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); } } }