diff --git a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigFinderTest.cs b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigFinderTest.cs index 7d1915ead..a188682e9 100644 --- a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigFinderTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigFinderTest.cs @@ -27,7 +27,7 @@ public class EasyLoggingConfigFinderTest private static Mock t_fileOperations; [ThreadStatic] - private static Mock t_unixFileOperations; + private static Mock t_unixOperations; [ThreadStatic] private static Mock t_directoryOperations; @@ -42,10 +42,10 @@ public class EasyLoggingConfigFinderTest public void Setup() { t_fileOperations = new Mock(); - t_unixFileOperations = new Mock(); + t_unixOperations = new Mock(); t_directoryOperations = new Mock(); t_environmentOperations = new Mock(); - t_finder = new EasyLoggingConfigFinder(t_fileOperations.Object, t_unixFileOperations.Object, t_directoryOperations.Object, t_environmentOperations.Object); + t_finder = new EasyLoggingConfigFinder(t_fileOperations.Object, t_unixOperations.Object, t_directoryOperations.Object, t_environmentOperations.Object); MockDirectoriesExist(); MockHomeDirectory(); } @@ -207,8 +207,8 @@ private static void MockDirectoriesExist() private static void MockHasFlagReturnsTrue() { - t_unixFileOperations - .Setup(f => f.HasFlag( + t_unixOperations + .Setup(f => f.CheckFileHasPermissions( It.Is(p => p.Equals(FileAccessPermissions.GroupWrite | FileAccessPermissions.OtherWrite)))) .Returns(true); } diff --git a/Snowflake.Data.Tests/UnitTests/Session/EasyLoggingStarterTest.cs b/Snowflake.Data.Tests/UnitTests/Session/EasyLoggingStarterTest.cs index 162d90639..9fb6936ac 100644 --- a/Snowflake.Data.Tests/UnitTests/Session/EasyLoggingStarterTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Session/EasyLoggingStarterTest.cs @@ -3,10 +3,10 @@ */ using System; -using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using Mono.Unix; +using Mono.Unix.Native; using Moq; using NUnit.Framework; using Snowflake.Data.Configuration; @@ -19,7 +19,6 @@ namespace Snowflake.Data.Tests.UnitTests.Session [TestFixture] public class EasyLoggingStarterTest { - private static readonly int AllPermissions = 777; private static readonly string HomeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); private static readonly string LogPath = Path.Combine(HomeDirectory, "some-logs-path/some-folder"); private const string ConfigPath = "/some-path/config.json"; @@ -58,6 +57,9 @@ public class EasyLoggingStarterTest [ThreadStatic] private static Mock t_easyLoggerManager; + [ThreadStatic] + private static Mock t_unixOperations; + [ThreadStatic] private static Mock t_directoryOperations; @@ -72,43 +74,15 @@ public void BeforeEach() { t_easyLoggingProvider = new Mock(); t_easyLoggerManager = new Mock(); + t_unixOperations = new Mock(); t_directoryOperations = new Mock(); t_environmentOperations = new Mock(); - t_easyLoggerStarter = new EasyLoggingStarter(t_easyLoggingProvider.Object, t_easyLoggerManager.Object, t_directoryOperations.Object, t_environmentOperations.Object); - } - - [Test] - public void TestThatCreatedDirectoryPermissionsFollowUmask() - { - // Note: To test with a different value than the default umask, it will have to be set before running this test - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - Assert.Ignore("skip test on Windows"); - } - - // arrange - t_easyLoggingProvider - .Setup(provider => provider.ProvideConfig(ConfigPath)) - .Returns(s_configWithInfoLevel); - t_directoryOperations - .Setup(provider => provider.Exists(ConfigPath)) - .Returns(Directory.Exists(ConfigPath)); - t_directoryOperations - .Setup(provider => provider.CreateDirectory(s_expectedLogPath)) - .Returns(Directory.CreateDirectory(s_expectedLogPath)); - - var expectedDirPermissions = AllPermissions - int.Parse(CallBash("umask")); - - // act - t_easyLoggerStarter.Init(ConfigPath); - var dirInfo = new UnixDirectoryInfo(s_expectedLogPath); - var dirPermissions = EasyLoggerUtil.ConvertFileAccessPermissionsToInt(dirInfo.FileAccessPermissions); - - // assert - Assert.AreEqual(expectedDirPermissions, dirPermissions); - - // cleanup - Directory.Delete(s_expectedLogPath); + t_easyLoggerStarter = new EasyLoggingStarter( + t_easyLoggingProvider.Object, + t_easyLoggerManager.Object, + t_unixOperations.Object, + t_directoryOperations.Object, + t_environmentOperations.Object); } [Test] @@ -150,6 +124,34 @@ public void TestThatThrowsErrorWhenLogPathIsNotSetAndHomeDirectoryThrowsAnExcept Assert.AreEqual(thrown.Message, $"Error while trying to retrieve the home directory: {ex}"); } + [Test] + public void TestThatDoesNotFailWhenDirectoryPermissionIsNot700() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Ignore("skip test on Windows"); + } + + // arrange + t_easyLoggingProvider + .Setup(provider => provider.ProvideConfig(ConfigPath)) + .Returns(s_configWithInfoLevel); + t_directoryOperations + .Setup(provider => provider.Exists(ConfigPath)) + .Returns(false); + t_unixOperations + .Setup(provider => provider.GetDirPermissions()) + .Returns(FileAccessPermissions.AllPermissions); + + // act + t_easyLoggerStarter.Init(ConfigPath); + + // assert + t_unixOperations.Verify(u => u.CreateDirectoryWithPermissions(s_expectedLogPath, + FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR), Times.Once); + t_unixOperations.Verify(u => u.SetDirInfo(s_expectedLogPath), Times.Once); + } + [Test] public void TestThatConfiguresEasyLoggingOnlyOnceWhenInitializedWithConfigPath() { @@ -222,18 +224,5 @@ public void TestThatReconfiguresEasyLoggingWithConfigPathIfNotGivenForTheFirstTi t_easyLoggerManager.Verify(manager => manager.ReconfigureEasyLogging(EasyLoggingLogLevel.Info, s_expectedLogPath), Times.Once); t_easyLoggerManager.VerifyNoOtherCalls(); } - - private static string CallBash(string command) - { - using (Process process = new Process()) - { - process.StartInfo.FileName = "/bin/bash"; - process.StartInfo.Arguments = $"-c \"{command}\""; - process.StartInfo.UseShellExecute = false; - process.StartInfo.RedirectStandardOutput = true; - process.Start(); - return process.StandardOutput.ReadToEnd().Replace("\n", string.Empty); - } - } } } diff --git a/Snowflake.Data/Configuration/EasyLoggingConfigFinder.cs b/Snowflake.Data/Configuration/EasyLoggingConfigFinder.cs index aa2072e96..402df0716 100644 --- a/Snowflake.Data/Configuration/EasyLoggingConfigFinder.cs +++ b/Snowflake.Data/Configuration/EasyLoggingConfigFinder.cs @@ -19,16 +19,16 @@ internal class EasyLoggingConfigFinder internal const string ClientConfigEnvironmentName = "SF_CLIENT_CONFIG_FILE"; private readonly FileOperations _fileOperations; - private readonly UnixFileOperations _unixFileOperations; + private readonly UnixOperations _unixOperations; private readonly DirectoryOperations _directoryOperations; private readonly EnvironmentOperations _environmentOperations; - public static readonly EasyLoggingConfigFinder Instance = new EasyLoggingConfigFinder(FileOperations.Instance, UnixFileOperations.Instance, DirectoryOperations.Instance, EnvironmentOperations.Instance); + public static readonly EasyLoggingConfigFinder Instance = new EasyLoggingConfigFinder(FileOperations.Instance, UnixOperations.Instance, DirectoryOperations.Instance, EnvironmentOperations.Instance); - internal EasyLoggingConfigFinder(FileOperations fileOperations, UnixFileOperations unixFileOperations, DirectoryOperations directoryOperations, EnvironmentOperations environmentOperations) + internal EasyLoggingConfigFinder(FileOperations fileOperations, UnixOperations unixFileOperations, DirectoryOperations directoryOperations, EnvironmentOperations environmentOperations) { _fileOperations = fileOperations; - _unixFileOperations = unixFileOperations; + _unixOperations = unixFileOperations; _directoryOperations = directoryOperations; _environmentOperations = environmentOperations; } @@ -112,8 +112,8 @@ private void CheckIfValidPermissions(string filePath) return; // Check if others have permissions to modify the file and fail if so - _unixFileOperations.SetUnixFileInfo(filePath); - if (_unixFileOperations.HasFlag(FileAccessPermissions.GroupWrite | FileAccessPermissions.OtherWrite)) + _unixOperations.SetFileInfo(filePath); + if (_unixOperations.CheckFileHasPermissions(FileAccessPermissions.GroupWrite | FileAccessPermissions.OtherWrite)) { var errorMessage = $"Error due to other users having permission to modify the config file: {filePath}"; s_logger.Error(errorMessage); diff --git a/Snowflake.Data/Core/Session/EasyLoggingStarter.cs b/Snowflake.Data/Core/Session/EasyLoggingStarter.cs index b6950e511..e325bde90 100644 --- a/Snowflake.Data/Core/Session/EasyLoggingStarter.cs +++ b/Snowflake.Data/Core/Session/EasyLoggingStarter.cs @@ -6,6 +6,7 @@ using System.IO; using System.Runtime.InteropServices; using Mono.Unix; +using Mono.Unix.Native; using Snowflake.Data.Configuration; using Snowflake.Data.Core.Tools; using Snowflake.Data.Log; @@ -20,6 +21,8 @@ internal class EasyLoggingStarter private readonly EasyLoggerManager _easyLoggerManager; + private readonly UnixOperations _unixOperations; + private readonly DirectoryOperations _directoryOperations; private readonly EnvironmentOperations _environmentOperations; @@ -29,16 +32,18 @@ internal class EasyLoggingStarter private EasyLoggingInitTrialParameters _initTrialParameters = null; public static readonly EasyLoggingStarter Instance = new EasyLoggingStarter(EasyLoggingConfigProvider.Instance, - EasyLoggerManager.Instance, DirectoryOperations.Instance, EnvironmentOperations.Instance); + EasyLoggerManager.Instance, UnixOperations.Instance, DirectoryOperations.Instance, EnvironmentOperations.Instance); internal EasyLoggingStarter( EasyLoggingConfigProvider easyLoggingConfigProvider, EasyLoggerManager easyLoggerManager, + UnixOperations unixOperations, DirectoryOperations directoryOperations, EnvironmentOperations environmentOperations) { _easyLoggingConfigProvider = easyLoggingConfigProvider; _easyLoggerManager = easyLoggerManager; + _unixOperations = unixOperations; _directoryOperations = directoryOperations; _environmentOperations = environmentOperations; } @@ -127,9 +132,16 @@ private string GetLogPath(string logPath) var pathWithDotnetSubdirectory = Path.Combine(logPathOrDefault, "dotnet"); if (!_directoryOperations.Exists(pathWithDotnetSubdirectory)) { - _directoryOperations.CreateDirectory(pathWithDotnetSubdirectory); - - CheckDirPermissionsOnlyAllowUser(pathWithDotnetSubdirectory); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + _directoryOperations.CreateDirectory(pathWithDotnetSubdirectory); + } + else + { + _unixOperations.CreateDirectoryWithPermissions(pathWithDotnetSubdirectory, + FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR); + CheckDirPermissionsOnlyAllowUser(pathWithDotnetSubdirectory); + } } return pathWithDotnetSubdirectory; @@ -140,11 +152,12 @@ private void CheckDirPermissionsOnlyAllowUser(string dirPath) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; - var dirInfo = new UnixDirectoryInfo(dirPath); - if (dirInfo.Exists && dirInfo.FileAccessPermissions != FileAccessPermissions.UserReadWriteExecute) + _unixOperations.SetDirInfo(dirPath); + var dirPermissions = _unixOperations.GetDirPermissions(); + if (dirPermissions != FileAccessPermissions.UserReadWriteExecute) { - var dirPermissions = EasyLoggerUtil.ConvertFileAccessPermissionsToInt(dirInfo.FileAccessPermissions); - s_logger.Warn($"Access permission for the logs directory is currently {dirPermissions}"); + s_logger.Warn($"Access permission for the logs directory is currently " + + $"{EasyLoggerUtil.ConvertFileAccessPermissionsToInt(dirPermissions)}"); } } } diff --git a/Snowflake.Data/Core/Tools/UnixFileOperations.cs b/Snowflake.Data/Core/Tools/UnixFileOperations.cs deleted file mode 100644 index 1220a460b..000000000 --- a/Snowflake.Data/Core/Tools/UnixFileOperations.cs +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. - */ - -using Mono.Unix; - -namespace Snowflake.Data.Core.Tools -{ - internal class UnixFileOperations - { - public static readonly UnixFileOperations Instance = new UnixFileOperations(); - - private UnixFileInfo _unixFileInfo; - - public virtual void SetUnixFileInfo(string path) - { - _unixFileInfo = new UnixFileInfo(path); - } - - public virtual bool HasFlag(FileAccessPermissions permissions) - { - return _unixFileInfo.FileAccessPermissions.HasFlag(permissions); - } - } -} diff --git a/Snowflake.Data/Core/Tools/UnixOperations.cs b/Snowflake.Data/Core/Tools/UnixOperations.cs new file mode 100644 index 000000000..dd044d180 --- /dev/null +++ b/Snowflake.Data/Core/Tools/UnixOperations.cs @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. + */ + +using Mono.Unix; +using Mono.Unix.Native; +using System.IO; + +namespace Snowflake.Data.Core.Tools +{ + internal class UnixOperations + { + public static readonly UnixOperations Instance = new UnixOperations(); + + private UnixFileInfo _unixFileInfo; + private UnixDirectoryInfo _unixDirInfo; + + public virtual void SetDirInfo(string path) + { + _unixDirInfo = new UnixDirectoryInfo(path); + } + + public virtual void CreateDirectoryWithPermissions(string path, FilePermissions permissions) + { + string subPath = Path.GetDirectoryName(path); + if (!Directory.Exists(subPath)) + { + Directory.CreateDirectory(subPath); + } + Syscall.mkdir(path, permissions); + } + + public virtual FileAccessPermissions GetDirPermissions() + { + return _unixDirInfo.FileAccessPermissions; + } + + public virtual void SetFileInfo(string path) + { + _unixFileInfo = new UnixFileInfo(path); + } + + public virtual bool CheckFileHasPermissions(FileAccessPermissions permissions) + { + return _unixFileInfo.FileAccessPermissions.HasFlag(permissions); + } + } +}