mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
553 lines
17 KiB
553 lines
17 KiB
/*++
|
|
|
|
Copyright (c) 1992-1993 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
CopyFile.c
|
|
|
|
Abstract:
|
|
|
|
ReplCopyFile() copies all of the following for a file:
|
|
|
|
- the data
|
|
- the attributes (normal, readonly, etc)
|
|
- the EAs
|
|
- the security (including ACLs, owner, group)
|
|
- the alternate data stream (for NTFS only)
|
|
- the timestamps (creation, last _access, last _write)
|
|
|
|
Here's how those things are copied, depending on conditional compilation:
|
|
|
|
USE_BACKUP_APIS and
|
|
what USE_UNC_GETFILESEC USE_BACKUP_APIS neither
|
|
---------- -------------------- -------------------- --------------------
|
|
data Backup APIs Backup APIs CopyFile API
|
|
attributes SetFileAttributes SetFileAttributes SetFileAttributes
|
|
EAs Backup APIs Backup APIs BUGBUG: NOT COPIED!
|
|
security CreateFile CreateFile CreateFile
|
|
alt.data Backup APIs Backup APIs BUGBUG: NOT COPIED!
|
|
timestamps ReplCopyJustDateTime ReplCopyJustDateTime ReplCopyJustDateTime
|
|
|
|
The access required for the import tree and export tree are given below.
|
|
CHANGE=add+read+write+traverse.
|
|
READ=read+traverse.
|
|
ALL=add+read+write+traverse+setperm+takeowner.
|
|
|
|
USE_BACKUP_APIS and
|
|
what USE_UNC_GETFILESEC USE_BACKUP_APIS neither
|
|
---------- -------------------- -------------------- --------------------
|
|
dest needs CHANGE access CHANGE access ALL access
|
|
src needs CHANGE access CHANGE access CHANGE access
|
|
|
|
NOTE: We need CHANGE access on src files, as we copy those ACLs to dest
|
|
files, which we need to be able to update next time!
|
|
|
|
Author:
|
|
|
|
John Rogers (JohnRo) 11-Aug-1992
|
|
|
|
Environment:
|
|
|
|
User mode only. Uses Win32 APIs.
|
|
Requires ANSI C extensions: slash-slash comments, long external names.
|
|
Tab size is set to 4.
|
|
|
|
Revision History:
|
|
|
|
11-Aug-1992 JohnRo
|
|
Created for RAID 3288: repl svc should preserve ACLs on copy.
|
|
30-Dec-1992 JohnRo
|
|
RAID 1513: repl does not maintain ACLs. (Really enable Backup APIs.)
|
|
Make sure timestamp gets rounded in right direction.
|
|
Use NetpKdPrint where possible.
|
|
08-Jan-1993 JohnRo
|
|
RAID 6961: ReplCopyDirectoryItself does not respect bFailIfExists.
|
|
18-Jan-1993 JohnRo
|
|
RAID 7983: Repl svc needs to change process token to really copy ACLs.
|
|
11-Feb-1993 JohnRo
|
|
RAID 10716: msg timing and checksum problems (also downlevel msg bug).
|
|
26-Feb-1993 JohnRo
|
|
RAID 12986: Workaround bug where HPFS leaves archive bit on.
|
|
06-Apr-1993 JohnRo
|
|
RAID 1938: Replicator un-ACLs files when not given enough permission.
|
|
Do some debug output when setting attributes.
|
|
Prepare for >32 bits someday.
|
|
23-Apr-1993 JohnRo
|
|
RAID 7313: repl needs change permission to work on NTFS,
|
|
or we need to delete files differently.
|
|
04-Jun-1993 JohnRo
|
|
RAID 12473: Changed to handle ReplMakeFileSecurity not supported on
|
|
downlevel exporter.
|
|
|
|
--*/
|
|
|
|
|
|
// These must be included first:
|
|
|
|
#include <windows.h> // IN, LPTSTR, CopyFile(), WRITE_DAC, etc.
|
|
#include <lmcons.h> // NET_API_STATUS, PATHLEN, etc.
|
|
|
|
// These may be included in any order:
|
|
|
|
#include <lmerr.h> // NERR_InternalError, NO_ERROR.
|
|
#include <lmerrlog.h> // NELOG_ equates.
|
|
#include <netdebug.h> // NetpKdPrint(), FORMAT_ equates, etc.
|
|
#include <prefix.h> // PREFIX_ equates.
|
|
#include <repldefs.h> // IF_DEBUG(), my prototype, USE_ equates, etc.
|
|
#include <tstr.h> // TCHAR_EOS, STRLEN().
|
|
|
|
|
|
|
|
// Arbitrary backup API buffer size.
|
|
#define REPL_BACKUP_BUFFER_SIZE (16*1024)
|
|
|
|
// No, we don't want to put security info in the stream.
|
|
// (We're using CreateFile's security attributes parameter for this.)
|
|
#define REPL_PROCESS_SECURITY FALSE
|
|
|
|
|
|
NET_API_STATUS
|
|
ReplCopyFile(
|
|
IN LPCTSTR SourcePath,
|
|
IN LPCTSTR DestPath,
|
|
IN BOOL bFailIfExists
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
ReplCopyFile copies everthing contained in a file. ("Everything" includes
|
|
data, alternate data, extended attributes, attributes, timestamps, and
|
|
security info.)
|
|
|
|
Arguments:
|
|
|
|
SourcePath - Points to source file path, which might be a UNC name.
|
|
|
|
DestPath - Points to destination file path name.
|
|
|
|
bFailIfExists - a boolean which is TRUE iff this routine should fail
|
|
if the destination already exists.
|
|
|
|
Return Value:
|
|
|
|
NET_API_STATUS
|
|
|
|
Threads:
|
|
|
|
Used by client and syncer threads.
|
|
|
|
--*/
|
|
|
|
{
|
|
NET_API_STATUS ApiStatus;
|
|
BOOL DestExists;
|
|
HANDLE DestHandle = INVALID_HANDLE_VALUE;
|
|
PSECURITY_ATTRIBUTES DestSecurityAttr = NULL;
|
|
DWORD SourceAttributes = (DWORD) (-1);
|
|
|
|
#ifdef USE_BACKUP_APIS
|
|
DWORD ActualBufferSizeRead = 0;
|
|
DWORD ActualBufferSizeWritten;
|
|
LPVOID BackupBuffer = NULL;
|
|
LPVOID DestContext = NULL;
|
|
LPVOID SourceContext = NULL;
|
|
HANDLE SourceHandle = INVALID_HANDLE_VALUE;
|
|
#endif
|
|
|
|
|
|
//
|
|
// Check for caller errors.
|
|
//
|
|
|
|
NetpAssert( SourcePath != NULL );
|
|
NetpAssert( (*SourcePath) != TCHAR_EOS );
|
|
NetpAssert( STRLEN(SourcePath) <= PATHLEN );
|
|
NetpAssert( DestPath != NULL );
|
|
NetpAssert( (*DestPath) != TCHAR_EOS );
|
|
NetpAssert( STRLEN(DestPath) <= PATHLEN );
|
|
|
|
DestExists = ReplFileOrDirExists( DestPath );
|
|
if (bFailIfExists && DestExists) {
|
|
// DON'T GOTO CLEANUP FROM HERE, AS IT WOULD DELETE FILE!!!
|
|
return (ERROR_ALREADY_EXISTS);
|
|
}
|
|
|
|
//
|
|
// What kind of source (file/directory) is it?
|
|
//
|
|
|
|
SourceAttributes = GetFileAttributes( (LPTSTR) SourcePath );
|
|
|
|
if ( SourceAttributes == (DWORD) -1 ) {
|
|
|
|
//
|
|
// File doesn't exist, bad syntax, or something along those lines.
|
|
//
|
|
ApiStatus = (NET_API_STATUS) GetLastError();
|
|
NetpAssert( ApiStatus != NO_ERROR );
|
|
// DON'T GOTO CLEANUP FROM HERE, AS IT WOULD DELETE FILE!!!
|
|
return (ApiStatus);
|
|
|
|
} else if ((SourceAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
|
|
|
|
//
|
|
// Source is a directory tree. We don't handle this!
|
|
//
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplCopyFile: source is directory! path: '"
|
|
FORMAT_LPTSTR "', src attr " FORMAT_HEX_DWORD ".\n",
|
|
SourcePath, SourceAttributes ));
|
|
ApiStatus = NERR_InternalError;
|
|
// DON'T GOTO CLEANUP FROM HERE, AS IT WOULD DELETE FILE!!!
|
|
return (ApiStatus);
|
|
|
|
} else {
|
|
|
|
//
|
|
// Simple case: source exists and is just a single file.
|
|
// Start off by setting desired destination security attributes.
|
|
//
|
|
|
|
ApiStatus = ReplMakeSecurityAttributes(
|
|
SourcePath,
|
|
&DestSecurityAttr ); // alloc and set ptr
|
|
if (ApiStatus==ERROR_NOT_SUPPORTED) {
|
|
|
|
// Just downlevel master, so set default security and continue.
|
|
DestSecurityAttr = NULL;
|
|
|
|
} else if (ApiStatus != NO_ERROR) {
|
|
// DON'T GOTO CLEANUP FROM HERE, AS IT WOULD DELETE FILE!!!
|
|
return (ApiStatus);
|
|
} else {
|
|
NetpAssert( ApiStatus == NO_ERROR );
|
|
NetpAssert( DestSecurityAttr != NULL );
|
|
}
|
|
|
|
#ifdef USE_BACKUP_APIS
|
|
//
|
|
// Open source file.
|
|
//
|
|
SourceHandle = CreateFile(
|
|
SourcePath,
|
|
GENERIC_READ, // desired access
|
|
FILE_SHARE_READ, // share mode
|
|
NULL, // no security attributes
|
|
OPEN_EXISTING, // open if exists; fail if not.
|
|
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN, // flags
|
|
(HANDLE) NULL // no template
|
|
);
|
|
|
|
if (SourceHandle == INVALID_HANDLE_VALUE) {
|
|
ApiStatus = (NET_API_STATUS) GetLastError();
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplCopyFile: open of source '" FORMAT_LPTSTR
|
|
"' gave status " FORMAT_API_STATUS ".\n",
|
|
SourcePath, ApiStatus ));
|
|
|
|
NetpAssert( ApiStatus != NO_ERROR );
|
|
|
|
if (DestSecurityAttr != NULL) {
|
|
NetpMemoryFree( DestSecurityAttr );
|
|
}
|
|
|
|
// DON'T GOTO CLEANUP FROM HERE, AS IT WOULD DELETE FILE!!!
|
|
return (ApiStatus);
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Delete the destination file. Why? Because...
|
|
// We're going to set the security on destination file using
|
|
// the CreateFile API. This will ignore setting the security if the
|
|
// file already exists. So, we need to delete it to force this to
|
|
// happen.
|
|
//
|
|
|
|
if (DestExists) {
|
|
ApiStatus = ReplDeleteFile( DestPath );
|
|
if (ApiStatus != NO_ERROR) {
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
NetpAssert( !ReplFileOrDirExists( DestPath ) );
|
|
|
|
//
|
|
// Open dest file. Since we already deleted any existing one, we know
|
|
// this will create the file from scratch. This is important, because
|
|
// we're depending on CreateFile's use of DestSecurityAttr to set
|
|
// security on the file.
|
|
//
|
|
|
|
DestHandle = CreateFile(
|
|
DestPath,
|
|
GENERIC_WRITE // desired...
|
|
| WRITE_DAC // ...
|
|
| WRITE_OWNER, // access
|
|
FILE_SHARE_WRITE, // share mode: none
|
|
DestSecurityAttr, // desired security attributes
|
|
CREATE_NEW, // disposition create new (fail exist)
|
|
#ifdef USE_BACKUP_APIS
|
|
FILE_FLAG_BACKUP_SEMANTICS, // flags
|
|
#else
|
|
0, // flags
|
|
#endif
|
|
(HANDLE) NULL // no template
|
|
);
|
|
|
|
if (DestHandle == INVALID_HANDLE_VALUE) {
|
|
ApiStatus = (NET_API_STATUS) GetLastError();
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplCopyFile: open of dest '" FORMAT_LPTSTR
|
|
"' gave status " FORMAT_API_STATUS ".\n",
|
|
DestPath, ApiStatus ));
|
|
|
|
NetpAssert( ApiStatus != NO_ERROR );
|
|
goto Cleanup;
|
|
}
|
|
|
|
#ifndef USE_BACKUP_APIS
|
|
|
|
//
|
|
// Close the dest file and copy the data.
|
|
// (In this case, we're using CopyFile API, which is not handle-based.)
|
|
//
|
|
|
|
NetpAssert( DestHandle != INVALID_HANDLE_VALUE );
|
|
(VOID) CloseHandle( DestHandle );
|
|
DestHandle = INVALID_HANDLE_VALUE;
|
|
|
|
if ( !CopyFile(
|
|
(LPTSTR) SourcePath,
|
|
(LPTSTR) DestPath,
|
|
FALSE /* don't fail if exists */ ) ) {
|
|
|
|
ApiStatus = (NET_API_STATUS) GetLastError();
|
|
NetpAssert( ApiStatus != NO_ERROR );
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
NetpAssert( ReplFileOrDirExists( DestPath ) );
|
|
|
|
// Timestamps and attributes will be copied below.
|
|
// BUGBUG no way to copy EAs, alt data.
|
|
ApiStatus = NO_ERROR;
|
|
|
|
|
|
#else // defined(USE_BACKUP_APIS)
|
|
|
|
BackupBuffer = NetpMemoryAllocate( REPL_BACKUP_BUFFER_SIZE );
|
|
if (BackupBuffer == NULL) {
|
|
ApiStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplCopyFile: copying" ));
|
|
}
|
|
|
|
for (;;) { // until end of file...
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( "<" ));
|
|
}
|
|
|
|
if ( !BackupRead(
|
|
SourceHandle,
|
|
BackupBuffer,
|
|
REPL_BACKUP_BUFFER_SIZE,
|
|
& ActualBufferSizeRead,
|
|
FALSE, // don't abort yet
|
|
REPL_PROCESS_SECURITY,
|
|
& SourceContext
|
|
) ) {
|
|
|
|
// Process read error.
|
|
ApiStatus = (NET_API_STATUS) GetLastError();
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplCopyFile: BackupRead"
|
|
" gave status " FORMAT_API_STATUS ".\n",
|
|
ApiStatus ));
|
|
NetpAssert( ApiStatus != NO_ERROR );
|
|
goto Cleanup;
|
|
}
|
|
|
|
// No error on read, how about EOF?
|
|
if (ActualBufferSizeRead == 0) { // normal end of file.
|
|
ApiStatus = NO_ERROR;
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( "DONE(OK)\n" ));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( ">" ));
|
|
}
|
|
|
|
if ( !BackupWrite(
|
|
DestHandle,
|
|
BackupBuffer,
|
|
ActualBufferSizeRead,
|
|
& ActualBufferSizeWritten,
|
|
FALSE, // don't abort yet
|
|
REPL_PROCESS_SECURITY,
|
|
& DestContext
|
|
) ) {
|
|
|
|
ApiStatus = (NET_API_STATUS) GetLastError();
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplCopyFile: BackupWrite"
|
|
" gave status " FORMAT_API_STATUS ".\n",
|
|
ApiStatus ));
|
|
NetpAssert( ApiStatus != NO_ERROR );
|
|
goto Cleanup;
|
|
}
|
|
|
|
} // until end of file
|
|
ApiStatus = NO_ERROR;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
} // else (just single file)
|
|
|
|
|
|
Cleanup:
|
|
|
|
#ifdef USE_BACKUP_APIS
|
|
|
|
if (DestHandle != INVALID_HANDLE_VALUE) {
|
|
if (DestContext) {
|
|
(VOID) BackupWrite(
|
|
DestHandle,
|
|
BackupBuffer,
|
|
ActualBufferSizeRead,
|
|
& ActualBufferSizeWritten,
|
|
TRUE, // yes, it is time to abort.
|
|
REPL_PROCESS_SECURITY,
|
|
& DestContext
|
|
);
|
|
}
|
|
(VOID) CloseHandle( DestHandle );
|
|
} else {
|
|
NetpAssert( DestContext == NULL );
|
|
}
|
|
|
|
if (SourceHandle != INVALID_HANDLE_VALUE) {
|
|
if (SourceContext != NULL) {
|
|
(VOID) BackupRead(
|
|
SourceHandle,
|
|
BackupBuffer,
|
|
REPL_BACKUP_BUFFER_SIZE,
|
|
& ActualBufferSizeRead,
|
|
TRUE, // yes, it is time to abort.
|
|
REPL_PROCESS_SECURITY,
|
|
& SourceContext
|
|
);
|
|
}
|
|
(VOID) CloseHandle( SourceHandle );
|
|
} else {
|
|
NetpAssert( SourceContext == NULL );
|
|
}
|
|
|
|
if (BackupBuffer != NULL) {
|
|
NetpMemoryFree( BackupBuffer );
|
|
}
|
|
|
|
#endif
|
|
|
|
//
|
|
// Copy the timestamps, and make sure we round them in a manner consistent
|
|
// with our checksum. CopyFile and GetFileTime/SetFileTime all round
|
|
// in the opposite direction.
|
|
//
|
|
if (ApiStatus == NO_ERROR) {
|
|
ReplCopyJustDateTime(
|
|
SourcePath,
|
|
DestPath,
|
|
FALSE ); // no, we're not copying directories.
|
|
}
|
|
|
|
if ( (ApiStatus==NO_ERROR) && (SourceAttributes != (DWORD)-1) ) {
|
|
|
|
//
|
|
// Set the attributes (hidden, system, archive, etc) for the dir.
|
|
//
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"Setting client's file attributes to "
|
|
FORMAT_HEX_DWORD " for '" FORMAT_LPTSTR "'.\n",
|
|
SourceAttributes, DestPath ));
|
|
}
|
|
|
|
if ( !SetFileAttributes(
|
|
(LPTSTR) DestPath,
|
|
SourceAttributes) ) {
|
|
|
|
ApiStatus = (NET_API_STATUS) GetLastError();
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplCopyFile: unexpected ret code from "
|
|
"SetFileAttributes(" FORMAT_LPTSTR ", " FORMAT_HEX_DWORD
|
|
"): " FORMAT_API_STATUS ".\n",
|
|
DestPath, SourceAttributes, ApiStatus ));
|
|
NetpAssert( ApiStatus != NO_ERROR );
|
|
|
|
// Already cleaning up, so just continue...
|
|
}
|
|
}
|
|
|
|
if (DestSecurityAttr != NULL) {
|
|
NetpMemoryFree( DestSecurityAttr );
|
|
}
|
|
|
|
//
|
|
// Last, but not least: the BackupWrite API has a problem where if
|
|
// we don't have permission to set the ACLs, it writes the file without
|
|
// them. At least it gives us a return code in this case.
|
|
// So, if the copy wasn't perfect, get rid of anything which might
|
|
// be a security problem.
|
|
//
|
|
if (ApiStatus != NO_ERROR) {
|
|
|
|
// Log this!
|
|
ReplErrorLog(
|
|
NULL, // local (no server name)
|
|
NELOG_ReplSysErr, // log code
|
|
ApiStatus,
|
|
NULL, // no optional str1
|
|
NULL ); // no optional str2
|
|
|
|
// BUGBUG: Log on remote server too if we got UNC name.
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplCopyFile: making sure target file '" FORMAT_LPTSTR
|
|
"' is gone due to error " FORMAT_API_STATUS
|
|
" while copying.\n", DestPath, ApiStatus ));
|
|
|
|
(VOID) ReplDeleteFile( DestPath );
|
|
}
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplCopyFile: file (etc) copy of " FORMAT_LPTSTR " to "
|
|
FORMAT_LPTSTR " gave status " FORMAT_API_STATUS ".\n",
|
|
SourcePath, DestPath, ApiStatus ));
|
|
}
|
|
|
|
return (ApiStatus);
|
|
|
|
}
|