@ -7,8 +7,8 @@ using Mono.Unix.Native;
using NLog ;
using NzbDrone.Common.Disk ;
using NzbDrone.Common.EnsureThat ;
using NzbDrone.Common.EnvironmentInfo ;
using NzbDrone.Common.Extensions ;
using NzbDrone.Common.Instrumentation ;
namespace NzbDrone.Mono.Disk
{
@ -18,15 +18,15 @@ namespace NzbDrone.Mono.Disk
// `unchecked((uint)-1)` and `uint.MaxValue` are the same thing.
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 ISymbolicLinkResolver _symLinkResolver ;
public DiskProvider ( IProcMountProvider procMountProvider , ISymbolicLinkResolver symLinkResolver )
public DiskProvider ( IProcMountProvider procMountProvider , ISymbolicLinkResolver symLinkResolver , Logger logger )
{
_procMountProvider = procMountProvider ;
_symLinkResolver = symLinkResolver ;
_logger = logger ;
}
public override IMount GetMount ( string path )
@ -44,7 +44,7 @@ namespace NzbDrone.Mono.Disk
if ( mount = = null )
{
L ogger. Debug ( "Unable to get free space for '{0}', unable to find suitable drive" , path ) ;
_l ogger. Debug ( "Unable to get free space for '{0}', unable to find suitable drive" , path ) ;
return null ;
}
@ -144,6 +144,12 @@ namespace NzbDrone.Mono.Disk
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
{
base . CopyFileInternal ( source , destination , overwrite ) ;
@ -185,12 +191,128 @@ namespace NzbDrone.Mono.Disk
throw ;
}
}
else if ( ( PlatformInfo . Platform = = PlatformType . Mono & & PlatformInfo . GetVersion ( ) > = new Version ( 6 , 0 ) ) | |
PlatformInfo . Platform = = PlatformType . NetCore )
{
TransferFilePatched ( source , destination , overwrite , true ) ;
}
else
{
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 )
{
try
@ -207,14 +329,14 @@ namespace NzbDrone.Mono.Disk
}
catch ( Exception ex )
{
L ogger. Debug ( ex , string . Format ( "Hardlink '{0}' to '{1}' failed." , source , destination ) ) ;
_l ogger. Debug ( ex , string . Format ( "Hardlink '{0}' to '{1}' failed." , source , destination ) ) ;
return false ;
}
}
private void SetPermissions ( string path , string mask )
{
L ogger. Debug ( "Setting permissions: {0} on {1}" , mask , path ) ;
_l ogger. Debug ( "Setting permissions: {0} on {1}" , mask , path ) ;
var filePermissions = NativeConvert . FromOctalPermissionString ( mask ) ;
@ -230,7 +352,7 @@ namespace NzbDrone.Mono.Disk
{
if ( string . IsNullOrWhiteSpace ( user ) & & string . IsNullOrWhiteSpace ( group ) )
{
L ogger. Debug ( "User and Group for chown not configured, skipping chown." ) ;
_l ogger. Debug ( "User and Group for chown not configured, skipping chown." ) ;
return ;
}