1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-11-05 02:22:31 +01:00

Fixed: Workaround for mono 6.x file copy/move issues

This commit is contained in:
Taloth Saldono 2020-02-27 21:20:50 +00:00 committed by Qstick
parent ed72713ba7
commit 755fa9e865

View File

@ -7,8 +7,8 @@
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation;
namespace NzbDrone.Mono.Disk namespace NzbDrone.Mono.Disk
{ {
@ -18,15 +18,15 @@ public class DiskProvider : DiskProviderBase
// `unchecked((uint)-1)` and `uint.MaxValue` are the same thing. // `unchecked((uint)-1)` and `uint.MaxValue` are the same thing.
private const uint UNCHANGED_ID = uint.MaxValue; private const uint UNCHANGED_ID = uint.MaxValue;
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(DiskProvider)); private readonly Logger _logger;
private readonly IProcMountProvider _procMountProvider; private readonly IProcMountProvider _procMountProvider;
private readonly ISymbolicLinkResolver _symLinkResolver; private readonly ISymbolicLinkResolver _symLinkResolver;
public DiskProvider(IProcMountProvider procMountProvider, ISymbolicLinkResolver symLinkResolver) public DiskProvider(IProcMountProvider procMountProvider, ISymbolicLinkResolver symLinkResolver, Logger logger)
{ {
_procMountProvider = procMountProvider; _procMountProvider = procMountProvider;
_symLinkResolver = symLinkResolver; _symLinkResolver = symLinkResolver;
_logger = logger;
} }
public override IMount GetMount(string path) public override IMount GetMount(string path)
@ -44,7 +44,7 @@ public override IMount GetMount(string path)
if (mount == null) if (mount == null)
{ {
Logger.Debug("Unable to get free space for '{0}', unable to find suitable drive", path); _logger.Debug("Unable to get free space for '{0}', unable to find suitable drive", path);
return null; return null;
} }
@ -144,6 +144,12 @@ protected override void CopyFileInternal(string source, string destination, bool
newFile.CreateSymbolicLinkTo(fullPath); newFile.CreateSymbolicLinkTo(fullPath);
} }
} }
else if (((PlatformInfo.Platform == PlatformType.Mono && PlatformInfo.GetVersion() >= new Version(6, 0)) ||
PlatformInfo.Platform == PlatformType.NetCore) &&
(!FileExists(destination) || overwrite))
{
TransferFilePatched(source, destination, overwrite, false);
}
else else
{ {
base.CopyFileInternal(source, destination, overwrite); base.CopyFileInternal(source, destination, overwrite);
@ -185,12 +191,128 @@ protected override void MoveFileInternal(string source, string destination, bool
throw; throw;
} }
} }
else if ((PlatformInfo.Platform == PlatformType.Mono && PlatformInfo.GetVersion() >= new Version(6, 0)) ||
PlatformInfo.Platform == PlatformType.NetCore)
{
TransferFilePatched(source, destination, overwrite, true);
}
else else
{ {
base.MoveFileInternal(source, destination, overwrite); base.MoveFileInternal(source, destination, overwrite);
} }
} }
private void TransferFilePatched(string source, string destination, bool overwrite, bool move)
{
// Mono 6.x throws errors if permissions or timestamps cannot be set
// - In 6.0 it'll leave a full length file
// - In 6.6 it'll leave a zero length file
// Catch the exception and attempt to handle these edgecases
try
{
if (move)
{
base.MoveFileInternal(source, destination, overwrite);
}
else
{
base.CopyFileInternal(source, destination, overwrite);
}
}
catch (UnauthorizedAccessException)
{
var srcInfo = new FileInfo(source);
var dstInfo = new FileInfo(destination);
var exists = dstInfo.Exists && srcInfo.Exists;
if (PlatformInfo.Platform == PlatformType.Mono && PlatformInfo.GetVersion() >= new Version(6, 6) &&
exists && dstInfo.Length == 0 && srcInfo.Length != 0)
{
// mono >=6.6 bug: zero length file since chmod happens at the start
_logger.Debug("{3} failed to {2} file likely due to known {3} bug, attempting to {2} directly. '{0}' -> '{1}'", source, destination, move ? "move" : "copy", PlatformInfo.PlatformName);
try
{
_logger.Trace("Copying content from {0} to {1} ({2} bytes)", source, destination, srcInfo.Length);
using (var srcStream = new FileStream(source, FileMode.Open, FileAccess.Read))
using (var dstStream = new FileStream(destination, FileMode.Create, FileAccess.Write))
{
srcStream.CopyTo(dstStream);
}
}
catch
{
// If it fails again then bail
throw;
}
}
else if (((PlatformInfo.Platform == PlatformType.Mono &&
PlatformInfo.GetVersion() >= new Version(6, 0) &&
PlatformInfo.GetVersion() < new Version(6, 6)) ||
PlatformInfo.Platform == PlatformType.NetCore) &&
exists && dstInfo.Length == srcInfo.Length)
{
// mono 6.0, mono 6.4 and netcore 3.1 bug: full length file since utime and chmod happens at the end
_logger.Debug("{3} failed to {2} file likely due to known {3} bug, attempting to {2} directly. '{0}' -> '{1}'", source, destination, move ? "move" : "copy", PlatformInfo.PlatformName);
// Check at least part of the file since UnauthorizedAccess can happen due to legitimate reasons too
var checkLength = (int)Math.Min(64 * 1024, dstInfo.Length);
if (checkLength > 0)
{
var srcData = new byte[checkLength];
var dstData = new byte[checkLength];
_logger.Trace("Check last {0} bytes from {1}", checkLength, destination);
using (var srcStream = new FileStream(source, FileMode.Open, FileAccess.Read))
using (var dstStream = new FileStream(destination, FileMode.Open, FileAccess.Read))
{
srcStream.Position = srcInfo.Length - checkLength;
dstStream.Position = dstInfo.Length - checkLength;
srcStream.Read(srcData, 0, checkLength);
dstStream.Read(dstData, 0, checkLength);
}
for (var i = 0; i < checkLength; i++)
{
if (srcData[i] != dstData[i])
{
// Files aren't the same, the UnauthorizedAccess was unrelated
_logger.Trace("Copy was incomplete, rethrowing original error");
throw;
}
}
_logger.Trace("Copy was complete, finishing {0} operation", move ? "move" : "copy");
}
}
else
{
// Unrecognized situation, the UnauthorizedAccess was unrelated
throw;
}
if (exists)
{
try
{
dstInfo.LastWriteTimeUtc = srcInfo.LastWriteTimeUtc;
}
catch
{
_logger.Debug("Unable to change last modified date for {0}, skipping.", destination);
}
if (move)
{
_logger.Trace("Removing source file {0}", source);
File.Delete(source);
}
}
}
}
public override bool TryCreateHardLink(string source, string destination) public override bool TryCreateHardLink(string source, string destination)
{ {
try try
@ -207,14 +329,14 @@ public override bool TryCreateHardLink(string source, string destination)
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Debug(ex, string.Format("Hardlink '{0}' to '{1}' failed.", source, destination)); _logger.Debug(ex, string.Format("Hardlink '{0}' to '{1}' failed.", source, destination));
return false; return false;
} }
} }
private void SetPermissions(string path, string mask) private void SetPermissions(string path, string mask)
{ {
Logger.Debug("Setting permissions: {0} on {1}", mask, path); _logger.Debug("Setting permissions: {0} on {1}", mask, path);
var filePermissions = NativeConvert.FromOctalPermissionString(mask); var filePermissions = NativeConvert.FromOctalPermissionString(mask);
@ -230,7 +352,7 @@ private void SetOwner(string path, string user, string group)
{ {
if (string.IsNullOrWhiteSpace(user) && string.IsNullOrWhiteSpace(group)) if (string.IsNullOrWhiteSpace(user) && string.IsNullOrWhiteSpace(group))
{ {
Logger.Debug("User and Group for chown not configured, skipping chown."); _logger.Debug("User and Group for chown not configured, skipping chown.");
return; return;
} }