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