Access to Read Only Settings file causes failure

Feb 6, 2013 at 1:05 AM
Edited Feb 6, 2013 at 1:06 AM
The premise here is that all of our settings and templates, and even Cfg.exe, are kept in source control. (Perforce). As a step in the build process we want to generate our config files.

I find that in my local workspace, when the settings file is writable, cfg.exe will work. However, once it is checked into Perforce, and the ReadOnly flag is set, the application will fail:

Access to the path 'e51a199a-08c7-4189-8598-4f3c2b0ac911.Config.Settings.xls' is denied.

Likewise, when I execute via UNC path, since I have a copy of my P4 repository checked out read-only to a share, I get the same error.

"\myserver\DeploymentAutomation\bin\ConfigGen\cfg.exe" -t ".\src\Web.config" -s "\myserver\DeploymentAutomation\MyApp\MyApp.Config.Settings.xls" -o .\src -n web.config -fmr Dev -f


The specified settings file path was not found: \myserver\DeploymentAutomation\MyApp\MyApp.Config.Settings.xls

Why does this app need to open the Excel file as read/write? I'm trying to debug it to find the flag, but am having trouble finding this section of code.

Help!

Thanks.

Mike
Feb 9, 2013 at 5:38 PM
Hi,

I can probably guess what is happening here: ConfigGen takes a copy of the settings file before it attempts to open it: as the library I use to read the settings file insists on a non-shared lock (and I suspect it also opens the file read/write). This is why the error you see has your settings file name prefixed with a guid - it is a temporary copy.

However, looking at the code, it doesn't reset the read-only attribute on the copy of the file. So if your file is read-only, so is the copy, and so the library won't open the file. Given the whole point of copying the file in the first place was to avoid issues like this - this is a bug.

The fix would be to put a line of code to reset the read-only flag of the copy of the file. I'll see if I can reproduce the error here, and if so I'll add a fix.

If you fancy looking yourself - the copy is performed at the top of ConfigGen.Core.SettingsLoader.ConfigGen.Core.SettingsLoader

Thanks for using ConfigGen, and reporting the problem,

Rob
Feb 9, 2013 at 6:08 PM
Edited Feb 9, 2013 at 6:09 PM
OK - I have a candidate fix, but haven't fully tested it yet. This both resets the read-only flag that I suggested previously, but also address a problem in the way some ConfigGen code cleans up after itself: it too didn't handle read-only files correctly.

If you have time, can you try applying the following patch to the latest source code using hg, and see if the problem goes away?

If not, don't worry - the fix is on the way anyway.

Cheers,

Rob
# HG changeset patch
# User robadmin@RobDevWin8.dev.robathome.co.uk
# Date 1360433256 0
# Node ID e0150808c3f13fd774c53967b40846b0b362ce36
# Parent  06e62000722901228450e1f81f87ab087ce9b8b8
Experimental fix to "Access to Read Only Settings file causes failure": http://configgen.codeplex.com/discussions/432114

diff -r 06e620007229 -r e0150808c3f1 ConfigGen.Core.Tests/EndToEndTests/RegressionTests.cs
--- a/ConfigGen.Core.Tests/EndToEndTests/RegressionTests.cs Thu Aug 30 09:49:46 2012 +0100
+++ b/ConfigGen.Core.Tests/EndToEndTests/RegressionTests.cs Sat Feb 09 18:07:36 2013 +0000
@@ -182,5 +182,35 @@
                 Assert.IsNull(environment, "environment: should be null");
             }
         }
+
+        [Test]
+        public void ReadOnlySettingsFileCausesError()
+        {
+            using (var testDirectory = new DisposableDirectory())
+            {
+                string settingsFile = Path.Combine(testDirectory.FullName, "App.Config.Settings.xls");
+                Assembly.GetExecutingAssembly().CopyEmbeddedResourceFileTo("TestResources.Simple.App.Config.Settings.xls", settingsFile);
+
+                var settingsFileInfo = new FileInfo(settingsFile);
+                settingsFileInfo.Attributes |= FileAttributes.ReadOnly;
+
+                string templateFile = Path.Combine(testDirectory.FullName, "App.Config.Template.xml");
+                Assembly.GetExecutingAssembly().CopyEmbeddedResourceFileTo("TestResources.Simple.App.Config.Template.xml", templateFile);
+
+                var settings = new Preferences
+                {
+                    SettingsFile = settingsFile,
+                    TemplateFile = templateFile,
+                    OutputDirectory = Path.Combine(testDirectory.FullName, "Configs"),
+                    ForcedFilename = "ForcedFilename.xml",
+                    FlatDirectoryOutput = true,
+                    MachineSuffix = true
+                };
+
+                var results = TestHelper.ExecuteCfgTest(settings);
+
+                TestHelper.CheckExitResults(results, false);
+            }
+        }
     }
 }
diff -r 06e620007229 -r e0150808c3f1 ConfigGen.Core/SettingsLoader/ExcelFileLoader.cs
--- a/ConfigGen.Core/SettingsLoader/ExcelFileLoader.cs  Thu Aug 30 09:49:46 2012 +0100
+++ b/ConfigGen.Core/SettingsLoader/ExcelFileLoader.cs  Sat Feb 09 18:07:36 2013 +0000
@@ -53,6 +53,8 @@
             using (var tempCopyOfSettingsFile = new DisposableFile(settingsFile.CopyTo(tempCopyPath)))
             {
                 var tempCopyFileInfo = new FileInfo(tempCopyOfSettingsFile.FullName);
+                tempCopyFileInfo.Attributes &= ~FileAttributes.ReadOnly;
+
                 using (var settingsFileStream = tempCopyFileInfo.OpenRead())
                 {
                     if (!string.IsNullOrEmpty(tempCopyFileInfo.Extension))
diff -r 06e620007229 -r e0150808c3f1 ConfigGen.Utilities/ConfigGen.Utilities.csproj
--- a/ConfigGen.Utilities/ConfigGen.Utilities.csproj    Thu Aug 30 09:49:46 2012 +0100
+++ b/ConfigGen.Utilities/ConfigGen.Utilities.csproj    Sat Feb 09 18:07:36 2013 +0000
@@ -64,6 +64,7 @@
     <Compile Include="Extensions\System\StringExtensions.cs" />
     <Compile Include="Extensions\System\Xml\Linq\XElementExtensions.cs" />
     <Compile Include="Extensions\System\Xml\XmlReaderExtensions.cs" />
+    <Compile Include="IO\FileLocator.cs" />
     <Compile Include="Xml\IXmlStreamFormatter.cs" />
     <Compile Include="Xml\OnCopyCallback.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
diff -r 06e620007229 -r e0150808c3f1 ConfigGen.Utilities/DisposableDirectory.cs
--- a/ConfigGen.Utilities/DisposableDirectory.cs    Thu Aug 30 09:49:46 2012 +0100
+++ b/ConfigGen.Utilities/DisposableDirectory.cs    Sat Feb 09 18:07:36 2013 +0000
@@ -20,6 +20,7 @@
 #endregion
 using System;
 using System.IO;
+using ConfigGen.Utilities.IO;
 
 namespace ConfigGen.Utilities
 {
@@ -92,6 +93,8 @@
                 {
                     try
                     {
+                        var fileLocator = new FileLocator();
+                        fileLocator.FindFile("*.*", _wrappedDirectory, true, info => info.Attributes &= ~FileAttributes.ReadOnly);
                         _wrappedDirectory.Delete(true);
                         _wrappedDirectory.Refresh();
                     }
diff -r 06e620007229 -r e0150808c3f1 ConfigGen.Utilities/DisposableFile.cs
--- a/ConfigGen.Utilities/DisposableFile.cs Thu Aug 30 09:49:46 2012 +0100
+++ b/ConfigGen.Utilities/DisposableFile.cs Sat Feb 09 18:07:36 2013 +0000
@@ -62,6 +62,8 @@
             _file.Refresh();
             if (_file.Exists)
             {
+                _file.Attributes &= ~FileAttributes.ReadOnly;
+                _file.Refresh();
                 _file.Delete();
             }
             _file.Refresh();
diff -r 06e620007229 -r e0150808c3f1 ConfigGen.Utilities/IO/FileLocator.cs
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/ConfigGen.Utilities/IO/FileLocator.cs Sat Feb 09 18:07:36 2013 +0000
@@ -0,0 +1,46 @@
+using System;
+using System.IO;
+
+namespace ConfigGen.Utilities.IO
+{
+    /// <summary>
+    /// Locates files matching the search pattern in the specified directory and calls the supplied callback when each matching file is located.
+    /// </summary>
+    public class FileLocator
+    {
+        /// <summary>
+        /// Locates files matching the search pattern in the specified directory and calls the supplied callback when each matching file is located.
+        /// </summary>
+        /// <param name="searchPattern">Search pattern of file(s) to find, e.g. *.txt or *.*</param>
+        /// <param name="directory">Directory in which to look for files.</param>
+        /// <param name="recurse">True to recurse into sub directories, otherwise false.</param>
+        /// <param name="onFileFoundCallback">Callback to call on each matching file</param>
+        public void FindFile(string searchPattern, string directory, bool recurse, Action<FileInfo> onFileFoundCallback)
+        {
+            this.FindFile(searchPattern, new DirectoryInfo(directory), recurse, onFileFoundCallback);
+        }
+
+        /// <summary>
+        /// Locates files matching the search pattern in the specified directory and calls the supplied callback when each matching file is located.
+        /// </summary>
+        /// <param name="searchPattern">Search pattern of file(s) to find, e.g. *.txt or *.*</param>
+        /// <param name="directory">Directory in which to look for files.</param>
+        /// <param name="recurse">True to recurse into sub directories, otherwise false.</param>
+        /// <param name="onFileFoundCallback">Callback to call on each matching file</param>
+        public void FindFile(string searchPattern, DirectoryInfo directory, bool recurse, Action<FileInfo> onFileFoundCallback)
+        {
+            if (recurse)
+            {
+                foreach (var subDirectory in directory.GetDirectories())
+                {
+                    this.FindFile(searchPattern, subDirectory, recurse, onFileFoundCallback);
+                }
+            }
+
+            foreach (var file in directory.GetFiles(searchPattern))
+            {
+                onFileFoundCallback(file);
+            }
+        }
+    }
+}
\ No newline at end of file
Jun 12, 2014 at 6:50 PM
Hi, Was this fix ever incorporated? I'm not familiar with how to apply diffs like this.

Thanks.

MIke
Jun 23, 2014 at 10:17 AM
Yes - this was incorporated as of v1.0.2.1, so the problem you experienced should be resolved.