diff --git a/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs b/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs index 8c1fab599..df67b6bc7 100644 --- a/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs +++ b/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -1046,6 +1046,9 @@ namespace NzbDrone.Common.Test.DiskTests Mocker.GetMock() .Setup(v => v.GetFileInfos(It.IsAny())) .Returns(new List()); + + Mocker.GetMock() + .Setup(v => v.CopyPermissions(It.IsAny(), It.IsAny(), false)); } private void WithRealDiskProvider() @@ -1105,6 +1108,9 @@ namespace NzbDrone.Common.Test.DiskTests Mocker.GetMock() .Setup(v => v.OpenReadStream(It.IsAny())) .Returns(s => new FileStream(s, FileMode.Open, FileAccess.Read)); + + Mocker.GetMock() + .Setup(v => v.CopyPermissions(It.IsAny(), It.IsAny(), false)); } private void WithMockMount(string root) diff --git a/src/NzbDrone.Common/Disk/DiskProviderBase.cs b/src/NzbDrone.Common/Disk/DiskProviderBase.cs index 81dccaa1c..727e788e9 100644 --- a/src/NzbDrone.Common/Disk/DiskProviderBase.cs +++ b/src/NzbDrone.Common/Disk/DiskProviderBase.cs @@ -32,6 +32,7 @@ namespace NzbDrone.Common.Disk public abstract long? GetAvailableSpace(string path); public abstract void InheritFolderPermissions(string filename); public abstract void SetPermissions(string path, string mask, string user, string group); + public abstract void CopyPermissions(string sourcePath, string targetPath, bool includeOwner); public abstract long? GetTotalSize(string path); public DateTime FolderGetCreationTime(string path) diff --git a/src/NzbDrone.Common/Disk/DiskTransferService.cs b/src/NzbDrone.Common/Disk/DiskTransferService.cs index 6c668e36b..984557462 100644 --- a/src/NzbDrone.Common/Disk/DiskTransferService.cs +++ b/src/NzbDrone.Common/Disk/DiskTransferService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Threading; @@ -75,6 +75,8 @@ namespace NzbDrone.Common.Disk if (!_diskProvider.FolderExists(targetPath)) { _diskProvider.CreateFolder(targetPath); + + _diskProvider.CopyPermissions(sourcePath, targetPath); } var result = mode; diff --git a/src/NzbDrone.Common/Disk/IDiskProvider.cs b/src/NzbDrone.Common/Disk/IDiskProvider.cs index 005fdf82e..554079f78 100644 --- a/src/NzbDrone.Common/Disk/IDiskProvider.cs +++ b/src/NzbDrone.Common/Disk/IDiskProvider.cs @@ -11,6 +11,7 @@ namespace NzbDrone.Common.Disk long? GetAvailableSpace(string path); void InheritFolderPermissions(string filename); void SetPermissions(string path, string mask, string user, string group); + void CopyPermissions(string sourcePath, string targetPath, bool includeOwner = false); long? GetTotalSize(string path); DateTime FolderGetCreationTime(string path); DateTime FolderGetLastWrite(string path); diff --git a/src/NzbDrone.Mono.Test/DiskProviderTests/DiskProviderFixture.cs b/src/NzbDrone.Mono.Test/DiskProviderTests/DiskProviderFixture.cs index 65c126403..585c19eb4 100644 --- a/src/NzbDrone.Mono.Test/DiskProviderTests/DiskProviderFixture.cs +++ b/src/NzbDrone.Mono.Test/DiskProviderTests/DiskProviderFixture.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using FluentAssertions; using Mono.Unix; +using Mono.Unix.Native; using Moq; using NUnit.Framework; using NzbDrone.Common.Disk; @@ -127,5 +128,34 @@ namespace NzbDrone.Mono.Test.DiskProviderTests mount.Should().NotBeNull(); mount.RootDirectory.Should().Be(rootDir); } + + [Test] + public void should_copy_folder_permissions() + { + var src = GetTempFilePath(); + var dst = GetTempFilePath(); + + Directory.CreateDirectory(src); + + // Toggle one of the permission flags + Syscall.stat(src, out var origStat); + Syscall.chmod(src, origStat.st_mode ^ FilePermissions.S_IWGRP); + + // Verify test setup + Syscall.stat(src, out var srcStat); + srcStat.st_mode.Should().NotBe(origStat.st_mode); + + Subject.CreateFolder(dst); + + // Verify test setup + Syscall.stat(dst, out var dstStat); + dstStat.st_mode.Should().Be(origStat.st_mode); + + Subject.CopyPermissions(src, dst, false); + + // Verify CopyPermissions + Syscall.stat(dst, out dstStat); + dstStat.st_mode.Should().Be(srcStat.st_mode); + } } } diff --git a/src/NzbDrone.Mono/Disk/DiskProvider.cs b/src/NzbDrone.Mono/Disk/DiskProvider.cs index 9b091eaf2..e62a4eeda 100644 --- a/src/NzbDrone.Mono/Disk/DiskProvider.cs +++ b/src/NzbDrone.Mono/Disk/DiskProvider.cs @@ -76,6 +76,29 @@ namespace NzbDrone.Mono.Disk SetOwner(path, user, group); } + public override void CopyPermissions(string sourcePath, string targetPath, bool includeOwner) + { + try + { + Syscall.stat(sourcePath, out var srcStat); + Syscall.stat(targetPath, out var tgtStat); + + if (srcStat.st_mode != tgtStat.st_mode) + { + Syscall.chmod(targetPath, srcStat.st_mode); + } + + if (includeOwner && (srcStat.st_uid != tgtStat.st_uid || srcStat.st_gid != tgtStat.st_gid)) + { + Syscall.chown(targetPath, srcStat.st_uid, srcStat.st_gid); + } + } + catch (Exception ex) + { + _logger.Debug(ex, "Failed to copy permissions from {0} to {1}", sourcePath, targetPath); + } + } + protected override List GetAllMounts() { return _procMountProvider.GetMounts() diff --git a/src/NzbDrone.Windows/Disk/DiskProvider.cs b/src/NzbDrone.Windows/Disk/DiskProvider.cs index 10d1fe116..e23190055 100644 --- a/src/NzbDrone.Windows/Disk/DiskProvider.cs +++ b/src/NzbDrone.Windows/Disk/DiskProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Runtime.InteropServices; using NLog; @@ -51,6 +51,10 @@ namespace NzbDrone.Windows.Disk { } + public override void CopyPermissions(string sourcePath, string targetPath, bool includeOwner) + { + } + public override long? GetTotalSize(string path) { Ensure.That(path, () => path).IsValidPath();