| File: System\Configuration\Internal\WriteFileContext.cs | Web Access |
| Project: src\runtime\src\libraries\System.Configuration.ConfigurationManager\src\System.Configuration.ConfigurationManager.csproj (System.Configuration.ConfigurationManager) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.IO; using System.Threading; // The CODEDOM check is here to support versions of the framework that may not have fully // incorporated all of .NET Core, but want to use System.Configuration.ConfigurationManager. // TempFileCollection was moved around in .NET Core. #if CODEDOM using System.CodeDom.Compiler; #else using System.IO.Internal; #endif namespace System.Configuration.Internal { internal sealed class WriteFileContext { private const int SavingTimeout = 10000; // 10 seconds private const int SavingRetryInterval = 100; // 100 milliseconds private readonly string _templateFilename; private TempFileCollection _tempFiles; 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; } } internal string TempNewFilename { get; } // 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) return; 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; } } // Copy all the files attributes that we care about from the source // file to the destination file private static void DuplicateFileAttributes(string source, string destination) { DateTime creationTime; // Copy File Attributes, ie. Hidden, Readonly, etc. FileAttributes 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); } // Copy over all the attributes you would want copied from a template file. // As of right now this is just acl's private static void DuplicateTemplateAttributes(string source, string destination) { FileAttributes fileAttributes = File.GetAttributes(source); File.SetAttributes(destination, fileAttributes); } // 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 static 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 (IOException) { // Someone else was using the file. Since we did not get // the unauthorizedAccessException we have access to the file } finally { fs?.Close(); } } /// <summary> /// Replace one file with another, retrying if locked. /// </summary> private static void ReplaceFile(string source, string target) { bool writeSucceeded; 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 < SavingTimeout) && File.Exists(target) && !FileIsWriteLocked(target)) { Thread.Sleep(SavingRetryInterval); duration += SavingRetryInterval; writeSucceeded = AttemptMove(source, target); } if (!writeSucceeded) { throw new ConfigurationErrorsException(SR.Format(SR.Config_write_failed, target)); } } // Attempt to move a file from one location to another, overwriting if needed private static bool AttemptMove(string source, string target) { try { if (File.Exists(target)) { File.Replace(source, target, null); } else { File.Move(source, target); } return true; } catch { return false; } } private static bool FileIsWriteLocked(string fileName) { FileStream fileStream = null; if (!File.Exists(fileName)) { // It can't be locked if it doesn't exist return false; } try { // Try to open for shared reading fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete); // If we can open it for shared reading, it is not write locked return false; } catch { return true; } finally { fileStream?.Close(); } } } }