e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
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 );
|
|
}
|
|
}
|
|
}
|
|
}
|