From 1c92ea58dafc97a60d4dea48c81f89a518041357 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Thu, 11 Feb 2016 01:13:07 +0100 Subject: [PATCH] Fixed: Replaced mono symlink resolve logic to better handle errors. --- src/NzbDrone.Common/Disk/DiskProviderBase.cs | 18 ++- src/NzbDrone.Mono/DiskProvider.cs | 11 +- src/NzbDrone.Mono/NzbDrone.Mono.csproj | 1 + src/NzbDrone.Mono/ProcMountProvider.cs | 2 - src/NzbDrone.Mono/SymbolicLinkResolver.cs | 120 +++++++++++++++++++ 5 files changed, 144 insertions(+), 8 deletions(-) create mode 100644 src/NzbDrone.Mono/SymbolicLinkResolver.cs diff --git a/src/NzbDrone.Common/Disk/DiskProviderBase.cs b/src/NzbDrone.Common/Disk/DiskProviderBase.cs index 5db864af2..5155d3ea5 100644 --- a/src/NzbDrone.Common/Disk/DiskProviderBase.cs +++ b/src/NzbDrone.Common/Disk/DiskProviderBase.cs @@ -383,12 +383,20 @@ public virtual List GetMounts() public virtual IMount GetMount(string path) { - var mounts = GetMounts(); + try + { + var mounts = GetMounts(); - return mounts.Where(drive => drive.RootDirectory.PathEquals(path) || - drive.RootDirectory.IsParentPath(path)) - .OrderByDescending(drive => drive.RootDirectory.Length) - .FirstOrDefault(); + return mounts.Where(drive => drive.RootDirectory.PathEquals(path) || + drive.RootDirectory.IsParentPath(path)) + .OrderByDescending(drive => drive.RootDirectory.Length) + .FirstOrDefault(); + } + catch (Exception ex) + { + Logger.DebugException(string.Format("Failed to get mount for path {0}", path), ex); + return null; + } } protected List GetDriveInfoMounts() diff --git a/src/NzbDrone.Mono/DiskProvider.cs b/src/NzbDrone.Mono/DiskProvider.cs index 141cc2dfd..d480e9066 100644 --- a/src/NzbDrone.Mono/DiskProvider.cs +++ b/src/NzbDrone.Mono/DiskProvider.cs @@ -16,10 +16,19 @@ public class DiskProvider : DiskProviderBase private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(DiskProvider)); private readonly IProcMountProvider _procMountProvider; + private readonly ISymbolicLinkResolver _symLinkResolver; - public DiskProvider(IProcMountProvider procMountProvider) + public DiskProvider(IProcMountProvider procMountProvider, ISymbolicLinkResolver symLinkResolver) { _procMountProvider = procMountProvider; + _symLinkResolver = symLinkResolver; + } + + public override IMount GetMount(string path) + { + path = _symLinkResolver.GetCompleteRealPath(path); + + return base.GetMount(path); } public override long? GetAvailableSpace(string path) diff --git a/src/NzbDrone.Mono/NzbDrone.Mono.csproj b/src/NzbDrone.Mono/NzbDrone.Mono.csproj index ff183e318..91c065b27 100644 --- a/src/NzbDrone.Mono/NzbDrone.Mono.csproj +++ b/src/NzbDrone.Mono/NzbDrone.Mono.csproj @@ -74,6 +74,7 @@ + diff --git a/src/NzbDrone.Mono/ProcMountProvider.cs b/src/NzbDrone.Mono/ProcMountProvider.cs index cdf7d0359..f2e0953be 100644 --- a/src/NzbDrone.Mono/ProcMountProvider.cs +++ b/src/NzbDrone.Mono/ProcMountProvider.cs @@ -2,11 +2,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; -using Mono.Unix; namespace NzbDrone.Mono { diff --git a/src/NzbDrone.Mono/SymbolicLinkResolver.cs b/src/NzbDrone.Mono/SymbolicLinkResolver.cs new file mode 100644 index 000000000..dad2f05b5 --- /dev/null +++ b/src/NzbDrone.Mono/SymbolicLinkResolver.cs @@ -0,0 +1,120 @@ +using System; +using System.Text; +using Mono.Unix; +using Mono.Unix.Native; +using NLog; + +namespace NzbDrone.Mono +{ + public interface ISymbolicLinkResolver + { + string GetCompleteRealPath(string path); + } + + // Mono's own implementation doesn't handle exceptions very well. + // All of this code was copied from mono with minor changes. + public class SymbolicLinkResolver : ISymbolicLinkResolver + { + private readonly Logger _logger; + + public SymbolicLinkResolver(Logger logger) + { + _logger = logger; + } + + public string GetCompleteRealPath(string path) + { + if (path == null) return null; + + try + { + string[] dirs; + int lastIndex; + GetPathComponents(path, out dirs, out lastIndex); + + var realPath = new StringBuilder(); + if (dirs.Length > 0) + { + var dir = UnixPath.IsPathRooted(path) ? "/" : ""; + dir += dirs[0]; + realPath.Append(GetRealPath(dir)); + } + for (var i = 1; i < lastIndex; ++i) + { + realPath.Append("/").Append(dirs[i]); + var realSubPath = GetRealPath(realPath.ToString()); + realPath.Remove(0, realPath.Length); + realPath.Append(realSubPath); + } + return realPath.ToString(); + } + catch (Exception ex) + { + _logger.DebugException(string.Format("Failed to check for symlinks in the path {0}", path), ex); + return path; + } + } + + + private static void GetPathComponents(string path, out string[] components, out int lastIndex) + { + var dirs = path.Split(UnixPath.DirectorySeparatorChar); + var target = 0; + for (var i = 0; i < dirs.Length; ++i) + { + if (dirs[i] == "." || dirs[i] == string.Empty) + { + continue; + } + + if (dirs[i] == "..") + { + if (target != 0) + { + target--; + } + else + { + target++; + } + } + else + { + dirs[target++] = dirs[i]; + } + } + components = dirs; + lastIndex = target; + } + + public string GetRealPath(string path) + { + do + { + var link = UnixPath.TryReadLink(path); + + if (link == null) + { + var errno = Stdlib.GetLastError(); + if (errno != Errno.EINVAL) + { + _logger.Trace("Checking path {0} for symlink returned error {1}, assuming it's not a symlink.", path, errno); + } + + return path; + } + + if (UnixPath.IsPathRooted(link)) + { + path = link; + } + else + { + path = UnixPath.GetDirectoryName(path) + UnixPath.DirectorySeparatorChar + link; + path = UnixPath.GetCanonicalPath(path); + } + } while (true); + } + + } +}