2016-01-28 22:24:51 +01:00
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System ;
using CoCoL ;
using System.Threading.Tasks ;
using System.IO ;
using System.Collections.Generic ;
using System.Linq ;
2016-02-09 09:17:31 +01:00
using Duplicati.Library.Main.Operation.Common ;
2018-04-11 23:02:47 +02:00
using Duplicati.Library.Snapshots ;
2016-01-28 22:24:51 +01:00
namespace Duplicati.Library.Main.Operation.Backup
{
/// <summary>
/// The file enumeration process takes a list of source folders as input,
/// applies all filters requested and emits the filtered set of filenames
/// to its output channel
/// </summary>
2016-02-27 02:15:42 +01:00
internal static class FileEnumerationProcess
2016-01-28 22:24:51 +01:00
{
2018-04-11 23:02:47 +02:00
/// <summary>
/// The log tag to use
/// </summary>
private static readonly string FILTER_LOGTAG = Logging . Log . LogTagFromType ( typeof ( FileEnumerationProcess ));
2016-02-27 02:15:42 +01:00
public static Task Run ( Snapshots . ISnapshotService snapshot , FileAttributes attributeFilter , Duplicati . Library . Utility . IFilter sourcefilter , Duplicati . Library . Utility . IFilter emitfilter , Options . SymlinkStrategy symlinkPolicy , Options . HardlinkStrategy hardlinkPolicy , string [] changedfilelist , Common . ITaskReader taskreader )
{
return AutomationExtensions . RunTask (
new {
Output = Backup . Channels . SourcePaths . ForWrite
},
async self =>
{
var hardlinkmap = new Dictionary < string , string >();
var mixinqueue = new Queue < string >();
Duplicati . Library . Utility . IFilter enumeratefilter = emitfilter ;
bool includes ;
bool excludes ;
Library . Utility . FilterExpression . AnalyzeFilters ( emitfilter , out includes , out excludes );
if ( includes && ! excludes )
enumeratefilter = Library . Utility . FilterExpression . Combine ( emitfilter , new Duplicati . Library . Utility . FilterExpression ( "*" + System . IO . Path . DirectorySeparatorChar , true ));
// If we have a specific list, use that instead of enumerating the filesystem
IEnumerable < string > worklist ;
if ( changedfilelist != null && changedfilelist . Length > 0 )
{
worklist = changedfilelist . Where ( x =>
{
var fa = FileAttributes . Normal ;
try
{
fa = snapshot . GetAttributes ( x );
}
catch
{
}
2018-04-11 23:02:47 +02:00
return AttributeFilterAsync ( null , x , fa , snapshot , sourcefilter , hardlinkPolicy , symlinkPolicy , hardlinkmap , attributeFilter , enumeratefilter , mixinqueue ). WaitForTask (). Result ;
2016-02-27 02:15:42 +01:00
});
}
else
{
worklist = snapshot . EnumerateFilesAndFolders (( root , path , attr ) => {
2018-04-11 23:02:47 +02:00
return AttributeFilterAsync ( root , path , attr , snapshot , sourcefilter , hardlinkPolicy , symlinkPolicy , hardlinkmap , attributeFilter , enumeratefilter , mixinqueue ). WaitForTask (). Result ;
2017-05-30 00:35:31 +02:00
}, ( rootpath , path , ex ) => {
2018-04-11 23:02:47 +02:00
Logging . Log . WriteWarningMessage ( FILTER_LOGTAG , "FileAccessError" , ex , "Error reported while accessing file: {0}" , path );
2016-02-27 02:15:42 +01:00
});
}
2016-01-28 22:24:51 +01:00
2016-02-27 02:15:42 +01:00
// Process each path, and dequeue the mixins with symlinks as we go
foreach ( var s in worklist )
{
if (! await taskreader . ProgressAsync )
return ;
while ( mixinqueue . Count > 0 )
await self . Output . WriteAsync ( mixinqueue . Dequeue ());
2016-01-28 22:24:51 +01:00
2016-02-27 02:15:42 +01:00
Library . Utility . IFilter m ;
if ( emitfilter != enumeratefilter && ! Library . Utility . FilterExpression . Matches ( emitfilter , s , out m ))
continue ;
2016-01-28 22:24:51 +01:00
2016-02-27 02:15:42 +01:00
await self . Output . WriteAsync ( s );
}
2016-01-28 22:24:51 +01:00
2016-02-27 02:15:42 +01:00
// Trailing symlinks are caught here
while ( mixinqueue . Count > 0 )
await self . Output . WriteAsync ( mixinqueue . Dequeue ());
});
2016-01-28 22:24:51 +01:00
}
2016-02-27 02:15:42 +01:00
2016-01-28 22:24:51 +01:00
/// <summary>
/// Plugin filter for enumerating a list of files.
/// </summary>
/// <returns>True if the path should be returned, false otherwise.</returns>
/// <param name="rootpath">The root path that initiated this enumeration.</param>
/// <param name="path">The current path.</param>
/// <param name="attributes">The file or folder attributes.</param>
2018-04-11 23:02:47 +02:00
private static async Task < bool > AttributeFilterAsync ( string rootpath , string path , FileAttributes attributes , Snapshots . ISnapshotService snapshot , Library . Utility . IFilter sourcefilter , Options . HardlinkStrategy hardlinkPolicy , Options . SymlinkStrategy symlinkPolicy , Dictionary < string , string > hardlinkmap , FileAttributes attributeFilter , Duplicati . Library . Utility . IFilter enumeratefilter , Queue < string > mixinqueue )
2016-01-28 22:24:51 +01:00
{
2016-08-31 14:17:35 +02:00
// Step 1, exclude block devices
try
2016-01-28 22:24:51 +01:00
{
2016-02-27 02:15:42 +01:00
if ( snapshot . IsBlockDevice ( path ))
2016-01-28 22:24:51 +01:00
{
2018-04-11 23:02:47 +02:00
Logging . Log . WriteVerboseMessage ( FILTER_LOGTAG , "ExcludingBlockDevice" , "Excluding block device: {0}" , path );
2016-01-28 22:24:51 +01:00
return false ;
}
}
catch ( Exception ex )
{
2018-04-11 23:02:47 +02:00
Logging . Log . WriteWarningMessage ( FILTER_LOGTAG , "PathProcessingError" , ex , "Failed to process path: {0}" , path );
2016-01-28 22:24:51 +01:00
return false ;
}
// Check if we explicitly include this entry
Duplicati . Library . Utility . IFilter sourcematch ;
bool sourcematches ;
2016-02-27 02:15:42 +01:00
if ( sourcefilter . Matches ( path , out sourcematches , out sourcematch ) && sourcematches )
2016-01-28 22:24:51 +01:00
{
2018-04-11 23:02:47 +02:00
Logging . Log . WriteVerboseMessage ( FILTER_LOGTAG , "IncludingSourcePath" , "Including source path: {0}" , path );
2016-01-28 22:24:51 +01:00
return true ;
}
// If we have a hardlink strategy, obey it
2016-02-27 02:15:42 +01:00
if ( hardlinkPolicy != Options . HardlinkStrategy . All )
2016-01-28 22:24:51 +01:00
{
try
{
2016-02-27 02:15:42 +01:00
var id = snapshot . HardlinkTargetID ( path );
2016-01-28 22:24:51 +01:00
if ( id != null )
{
2016-02-27 02:15:42 +01:00
if ( hardlinkPolicy == Options . HardlinkStrategy . None )
2016-01-28 22:24:51 +01:00
{
2018-04-11 23:02:47 +02:00
Logging . Log . WriteVerboseMessage ( FILTER_LOGTAG , "ExcludingHardlinkByPolicy" , "Excluding hardlink: {0} ({1})" , path , id );
2016-01-28 22:24:51 +01:00
return false ;
}
2016-02-27 02:15:42 +01:00
else if ( hardlinkPolicy == Options . HardlinkStrategy . First )
2016-01-28 22:24:51 +01:00
{
string prevPath ;
2016-02-27 02:15:42 +01:00
if ( hardlinkmap . TryGetValue ( id , out prevPath ))
2016-01-28 22:24:51 +01:00
{
2018-04-11 23:02:47 +02:00
Logging . Log . WriteVerboseMessage ( FILTER_LOGTAG , "ExcludingDuplicateHardlink" , "Excluding hardlink ({1}) for: {0}, previous hardlink: {2}" , path , id , prevPath );
2016-01-28 22:24:51 +01:00
return false ;
}
else
{
2016-02-27 02:15:42 +01:00
hardlinkmap . Add ( id , path );
2016-01-28 22:24:51 +01:00
}
}
}
}
catch ( Exception ex )
{
2018-04-11 23:02:47 +02:00
Logging . Log . WriteWarningMessage ( FILTER_LOGTAG , "PathProcessingError" , ex , "Failed to process path: {0}" , path );
2016-01-28 22:24:51 +01:00
return false ;
}
}
// If we exclude files based on attributes, filter that
2016-02-27 02:15:42 +01:00
if (( attributeFilter & attributes ) != 0 )
2016-01-28 22:24:51 +01:00
{
2018-04-11 23:02:47 +02:00
Logging . Log . WriteVerboseMessage ( FILTER_LOGTAG , "ExcludingPathFromAttributes" , "Excluding path due to attribute filter: {0}" , path );
2016-01-28 22:24:51 +01:00
return false ;
}
// Then check if the filename is not explicitly excluded by a filter
Library . Utility . IFilter match ;
2016-02-27 02:15:42 +01:00
if (! Library . Utility . FilterExpression . Matches ( enumeratefilter , path , out match ))
2016-01-28 22:24:51 +01:00
{
2018-04-11 23:02:47 +02:00
Logging . Log . WriteVerboseMessage ( FILTER_LOGTAG , "ExcludingPathFromFilter" , "Excluding path due to filter: {0} => {1}" , path , match == null ? "null" : match . ToString ());
2016-01-28 22:24:51 +01:00
return false ;
}
else if ( match != null )
{
2018-04-11 23:02:47 +02:00
Logging . Log . WriteVerboseMessage ( FILTER_LOGTAG , "IncludingPathFromFilter" , "Including path due to filter: {0} => {1}" , path , match . ToString ());
2016-01-28 22:24:51 +01:00
}
// If the file is a symlink, apply special handling
2018-04-11 23:02:47 +02:00
var isSymlink = snapshot . IsSymlink ( path , attributes );
string symlinkTarget = null ;
if ( isSymlink )
try { symlinkTarget = snapshot . GetSymlinkTarget ( path ); }
catch ( Exception ex ) { Logging . Log . WriteExplicitMessage ( FILTER_LOGTAG , "SymlinkTargetReadError" , ex , "Failed to read symlink target for path: {0}" , path ); }
2016-01-28 22:24:51 +01:00
2018-04-11 23:02:47 +02:00
if ( isSymlink )
2016-01-28 22:24:51 +01:00
{
2018-04-11 23:02:47 +02:00
if (! string . IsNullOrWhiteSpace ( symlinkTarget ))
{
if ( symlinkPolicy == Options . SymlinkStrategy . Ignore )
{
Logging . Log . WriteVerboseMessage ( FILTER_LOGTAG , "ExcludeSymlink" , "Excluding symlink: {0}" , path );
return false ;
}
2016-01-28 22:24:51 +01:00
2018-04-11 23:02:47 +02:00
if ( symlinkPolicy == Options . SymlinkStrategy . Store )
{
Logging . Log . WriteVerboseMessage ( FILTER_LOGTAG , "StoreSymlink" , "Storing symlink: {0}" , path );
// We return false because we do not want to recurse into the path,
// but we add the symlink to the mixin so we process the symlink itself
mixinqueue . Enqueue ( path );
return false ;
}
}
else
{
Logging . Log . WriteVerboseMessage ( FILTER_LOGTAG , "FollowingEmptySymlink" , "Treating empty symlink as regular path {0}" , path );
}
2016-01-28 22:24:51 +01:00
}
// All the way through, yes!
return true ;
}
}
}