322 lines
11 KiB
C#
322 lines
11 KiB
C#
|
//------------------------------------------------------------------------------
|
||
|
// <copyright file="WriteFileContext.cs" company="Microsoft">
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
// </copyright>
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
namespace System.Configuration.Internal {
|
||
|
using System.Configuration;
|
||
|
using System.IO;
|
||
|
using System.Security.Permissions;
|
||
|
using System.Reflection;
|
||
|
using System.Threading;
|
||
|
using System.Security;
|
||
|
using System.CodeDom.Compiler;
|
||
|
using Microsoft.Win32;
|
||
|
#if !FEATURE_PAL
|
||
|
using System.Security.AccessControl;
|
||
|
#endif
|
||
|
|
||
|
internal class WriteFileContext {
|
||
|
private const int SAVING_TIMEOUT = 10000; // 10 seconds
|
||
|
private const int SAVING_RETRY_INTERVAL = 100; // 100 milliseconds
|
||
|
private static volatile bool _osPlatformDetermined;
|
||
|
private static volatile PlatformID _osPlatform;
|
||
|
|
||
|
private TempFileCollection _tempFiles;
|
||
|
private string _tempNewFilename;
|
||
|
private string _templateFilename;
|
||
|
|
||
|
internal WriteFileContext(string filename, string templateFilename) {
|
||
|
string directoryname = UrlPath.GetDirectoryOrRootName(filename);
|
||
|
|
||
|
_templateFilename = templateFilename;
|
||
|
_tempFiles = new TempFileCollection(directoryname);
|
||
|
try {
|
||
|
_tempNewFilename = _tempFiles.AddExtension("newcfg");
|
||
|
}
|
||
|
catch {
|
||
|
((IDisposable)_tempFiles).Dispose();
|
||
|
_tempFiles = null;
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static WriteFileContext() {
|
||
|
_osPlatformDetermined = false;
|
||
|
}
|
||
|
|
||
|
internal string TempNewFilename {
|
||
|
get {return _tempNewFilename;}
|
||
|
}
|
||
|
|
||
|
// Complete
|
||
|
//
|
||
|
// Cleanup the WriteFileContext object based on either success
|
||
|
// or failure
|
||
|
//
|
||
|
// Note: The current algorithm guarantess
|
||
|
// 1) The file we are saving to will always be present
|
||
|
// on the file system (ie. there will be no window
|
||
|
// during saving in which there won't be a file there)
|
||
|
// 2) It will always be available for reading from a
|
||
|
// client and it will be complete and accurate.
|
||
|
//
|
||
|
// ... This means that writing is a bit more complicated, and may
|
||
|
// have to be retried (because of reading lock), but I don't see
|
||
|
// anyway to get around this given 1 and 2.
|
||
|
//
|
||
|
internal void Complete(string filename, bool success) {
|
||
|
try {
|
||
|
if (success) {
|
||
|
if ( File.Exists( filename ) ) {
|
||
|
// Test that we can write to the file
|
||
|
ValidateWriteAccess( filename );
|
||
|
|
||
|
// Copy Attributes from original
|
||
|
DuplicateFileAttributes( filename, _tempNewFilename );
|
||
|
}
|
||
|
else {
|
||
|
if ( _templateFilename != null ) {
|
||
|
// Copy Acl from template file
|
||
|
DuplicateTemplateAttributes( _templateFilename, _tempNewFilename );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ReplaceFile(_tempNewFilename, filename);
|
||
|
|
||
|
// Don't delete, since we just moved it.
|
||
|
_tempFiles.KeepFiles = true;
|
||
|
}
|
||
|
}
|
||
|
finally {
|
||
|
((IDisposable)_tempFiles).Dispose();
|
||
|
_tempFiles = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// DuplicateFileAttributes
|
||
|
//
|
||
|
// Copy all the files attributes that we care about from the source
|
||
|
// file to the destination file
|
||
|
//
|
||
|
private void DuplicateFileAttributes( string source, string destination )
|
||
|
{
|
||
|
#if !FEATURE_PAL
|
||
|
FileAttributes attributes;
|
||
|
DateTime creationTime;
|
||
|
|
||
|
// Copy File Attributes, ie. Hidden, Readonly, etc.
|
||
|
attributes = File.GetAttributes( source );
|
||
|
File.SetAttributes( destination, attributes );
|
||
|
|
||
|
// Copy Creation Time
|
||
|
creationTime = File.GetCreationTimeUtc( source );
|
||
|
File.SetCreationTimeUtc( destination, creationTime );
|
||
|
|
||
|
// Copy ACL's
|
||
|
DuplicateTemplateAttributes( source, destination );
|
||
|
#endif // FEATURE_PAL
|
||
|
}
|
||
|
|
||
|
// DuplicateTemplateAttributes
|
||
|
//
|
||
|
// Copy over all the attributes you would want copied from a template file.
|
||
|
// As of right now this is just acl's
|
||
|
//
|
||
|
private void DuplicateTemplateAttributes( string source, string destination ) {
|
||
|
#if !FEATURE_PAL
|
||
|
if (IsWinNT) {
|
||
|
FileSecurity fileSecurity;
|
||
|
|
||
|
// Copy Security information
|
||
|
fileSecurity = File.GetAccessControl( source, AccessControlSections.Access );
|
||
|
|
||
|
// Mark dirty, so effective for write
|
||
|
fileSecurity.SetAccessRuleProtection( fileSecurity.AreAccessRulesProtected, true );
|
||
|
File.SetAccessControl( destination, fileSecurity );
|
||
|
}
|
||
|
else {
|
||
|
FileAttributes fileAttributes;
|
||
|
|
||
|
fileAttributes = File.GetAttributes( source );
|
||
|
File.SetAttributes( destination, fileAttributes );
|
||
|
}
|
||
|
#endif // FEATURE_PAL
|
||
|
}
|
||
|
|
||
|
// ValidateWriteAccess
|
||
|
//
|
||
|
// Validate that we can write to the file. This will enforce the ACL's
|
||
|
// on the file. Since we do our moving of files to replace, this is
|
||
|
// nice to ensure we are not by-passing some security permission
|
||
|
// that someone set (although that could bypass this via move themselves)
|
||
|
//
|
||
|
// Note: 1) This is really just a nice to have, since with directory permissions
|
||
|
// they could do the same thing we are doing
|
||
|
//
|
||
|
// 2) We are depending on the current behavior that if the file is locked
|
||
|
// and we can not open it, that we will get an UnauthorizedAccessException
|
||
|
// and not the IOException.
|
||
|
//
|
||
|
private void ValidateWriteAccess( string filename ) {
|
||
|
FileStream fs = null;
|
||
|
|
||
|
try {
|
||
|
// Try to open file for write access
|
||
|
fs = new FileStream( filename,
|
||
|
FileMode.Open,
|
||
|
FileAccess.Write,
|
||
|
FileShare.ReadWrite );
|
||
|
}
|
||
|
catch ( UnauthorizedAccessException ) {
|
||
|
// Access was denied, make sure we throw this
|
||
|
throw;
|
||
|
}
|
||
|
catch ( IOException ) {
|
||
|
// Someone else was using the file. Since we did not get
|
||
|
// the unauthorizedAccessException we have access to the file
|
||
|
}
|
||
|
catch ( Exception ) {
|
||
|
// Unexpected, so just throw for safety sake
|
||
|
throw;
|
||
|
}
|
||
|
finally {
|
||
|
if ( fs != null ) {
|
||
|
fs.Close();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ReplaceFile
|
||
|
//
|
||
|
// Replace one file with another using MoveFileEx. This will
|
||
|
// retry the operation if the file is locked because someone
|
||
|
// is reading it
|
||
|
//
|
||
|
private void ReplaceFile( string Source, string Target )
|
||
|
{
|
||
|
bool WriteSucceeded = false;
|
||
|
int Duration = 0;
|
||
|
|
||
|
WriteSucceeded = AttemptMove( Source, Target );
|
||
|
|
||
|
// The file may be open for read, if it is then
|
||
|
// lets try again because maybe they will finish
|
||
|
// soon, and we will be able to replace
|
||
|
while ( !WriteSucceeded &&
|
||
|
( Duration < SAVING_TIMEOUT ) &&
|
||
|
File.Exists( Target ) &&
|
||
|
!FileIsWriteLocked( Target ) ) {
|
||
|
|
||
|
Thread.Sleep( SAVING_RETRY_INTERVAL );
|
||
|
|
||
|
Duration += SAVING_RETRY_INTERVAL;
|
||
|
|
||
|
WriteSucceeded = AttemptMove( Source, Target );
|
||
|
}
|
||
|
|
||
|
if ( !WriteSucceeded ) {
|
||
|
|
||
|
throw new ConfigurationErrorsException(
|
||
|
SR.GetString(SR.Config_write_failed, Target) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// AttemptMove
|
||
|
//
|
||
|
// Attempt to move a file from one location to another
|
||
|
//
|
||
|
// Return Values:
|
||
|
// TRUE - Move Successful
|
||
|
// FALSE - Move Failed
|
||
|
private bool AttemptMove( string Source, string Target ) {
|
||
|
bool MoveSuccessful = false;
|
||
|
|
||
|
if ( IsWinNT ) {
|
||
|
|
||
|
// We can only call this when we have kernel32.dll
|
||
|
MoveSuccessful = UnsafeNativeMethods.MoveFileEx(
|
||
|
Source,
|
||
|
Target,
|
||
|
UnsafeNativeMethods.MOVEFILE_REPLACE_EXISTING );
|
||
|
}
|
||
|
else {
|
||
|
|
||
|
try {
|
||
|
// VSWhidbey 548017:
|
||
|
// File.Move isn't supported on Win9x. We'll use File.Copy
|
||
|
// instead. Please note that Source is a temporary file which
|
||
|
// will be deleted when _tempFiles is disposed.
|
||
|
File.Copy(Source, Target, true);
|
||
|
MoveSuccessful = true;
|
||
|
}
|
||
|
catch {
|
||
|
|
||
|
MoveSuccessful = false;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return MoveSuccessful;
|
||
|
}
|
||
|
|
||
|
// FileIsWriteLocked
|
||
|
//
|
||
|
// Is the file write locked or not?
|
||
|
//
|
||
|
private bool FileIsWriteLocked( string FileName ) {
|
||
|
Stream FileStream = null;
|
||
|
bool WriteLocked = true;
|
||
|
|
||
|
if (!FileUtil.FileExists(FileName, true)) {
|
||
|
// It can't be locked if it doesn't exist
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
FileShare fileShare = FileShare.Read;
|
||
|
|
||
|
if (IsWinNT) {
|
||
|
fileShare |= FileShare.Delete;
|
||
|
}
|
||
|
|
||
|
// Try to open for shared reading
|
||
|
FileStream = new FileStream( FileName,
|
||
|
FileMode.Open,
|
||
|
FileAccess.Read,
|
||
|
fileShare);
|
||
|
|
||
|
// If we can open it for shared reading, it is not
|
||
|
// write locked
|
||
|
WriteLocked = false;
|
||
|
}
|
||
|
finally {
|
||
|
if ( FileStream != null ) {
|
||
|
|
||
|
FileStream.Close();
|
||
|
FileStream = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return WriteLocked;
|
||
|
}
|
||
|
|
||
|
// IsWinNT
|
||
|
//
|
||
|
// Are we running in WinNT or not?
|
||
|
//
|
||
|
private bool IsWinNT {
|
||
|
get {
|
||
|
if ( !_osPlatformDetermined ) {
|
||
|
|
||
|
_osPlatform = Environment.OSVersion.Platform;
|
||
|
_osPlatformDetermined = true;
|
||
|
}
|
||
|
|
||
|
return ( _osPlatform == System.PlatformID.Win32NT );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|