Merge pull request #3014 from tygill/fix/reparse-points-arent-symlinks

Fix 2829: Reparse points aren't always symlinks
This commit is contained in:
Kenneth Skovhede
2018-02-07 22:56:41 +01:00
committed by GitHub
11 changed files with 149 additions and 58 deletions
@@ -6,6 +6,7 @@ using System.IO;
using Duplicati.Library.Main.Database;
using Duplicati.Library.Main.Volumes;
using Duplicati.Library.Interface;
using Duplicati.Library.Snapshots;
namespace Duplicati.Library.Main.Operation
{
@@ -187,7 +188,7 @@ namespace Duplicati.Library.Main.Operation
m_logWriter.AddVerboseMessage("Including path due to filter: {0} => {1}", path, match.ToString());
}
var isSymlink = (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
var isSymlink = m_snapshot.IsSymlink(path, attributes);
if (isSymlink && m_symlinkPolicy == Options.SymlinkStrategy.Ignore)
{
if (m_logWriter != null)
@@ -281,8 +282,8 @@ namespace Duplicati.Library.Main.Operation
var fa = FileAttributes.Normal;
try { fa = snapshot.GetAttributes(path); }
catch { }
if (followSymlinks && ((fa & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint))
if (followSymlinks && snapshot.IsSymlink(path, fa))
continue;
else if ((fa & FileAttributes.Directory) == FileAttributes.Directory)
continue;
@@ -825,12 +826,12 @@ namespace Duplicati.Library.Main.Operation
backend.WaitForEmpty(m_database, m_transaction);
if (m_result.TaskControlRendevouz() != TaskControlState.Stop)
CompactIfRequired(backend, lastVolumeSize);
using (new Logging.Timer("Async backend wait"))
backend.WaitForComplete(m_database, m_transaction);
if (m_options.UploadVerificationFile)
CompactIfRequired(backend, lastVolumeSize);
using (new Logging.Timer("Async backend wait"))
backend.WaitForComplete(m_database, m_transaction);
if (m_options.UploadVerificationFile)
{
m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_VerificationUpload);
FilelistProcessor.UploadVerificationFile(backend.BackendUrl, m_options, m_result.BackendWriter, m_database, m_transaction);
@@ -970,32 +971,39 @@ namespace Duplicati.Library.Main.Operation
if ((attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
{
if (m_options.SymlinkPolicy == Options.SymlinkStrategy.Ignore)
// Not all reparse points are symlinks.
// For example, on Windows 10 Fall Creator's Update, the OneDrive folder (and all subfolders)
// are reparse points, which allows the folder to hook into the OneDrive service and download things on-demand.
// If we can't find a symlink target for the current path, we won't treat it as a symlink.
string symlinkTarget = snapshot.GetSymlinkTarget(path);
if (!string.IsNullOrWhiteSpace(symlinkTarget))
{
m_result.AddVerboseMessage("Ignoring symlink {0}", path);
return false;
}
if (m_options.SymlinkPolicy == Options.SymlinkStrategy.Store)
{
Dictionary<string, string> metadata = GenerateMetadata(snapshot, path, attributes);
if (!metadata.ContainsKey("CoreSymlinkTarget"))
if (m_options.SymlinkPolicy == Options.SymlinkStrategy.Ignore)
{
var p = snapshot.GetSymlinkTarget(path);
if (string.IsNullOrWhiteSpace(p))
m_result.AddVerboseMessage("Ignoring empty symlink {0}", path);
else
metadata["CoreSymlinkTarget"] = p;
m_result.AddVerboseMessage("Ignoring symlink {0}", path);
return false;
}
var metahash = Utility.WrapMetadata(metadata, m_options);
AddSymlinkToOutput(backend, path, DateTime.UtcNow, metahash);
m_result.AddVerboseMessage("Stored symlink {0}", path);
//Do not recurse symlinks
return false;
if (m_options.SymlinkPolicy == Options.SymlinkStrategy.Store)
{
Dictionary<string, string> metadata = GenerateMetadata(snapshot, path, attributes);
if (!metadata.ContainsKey("CoreSymlinkTarget"))
{
metadata["CoreSymlinkTarget"] = symlinkTarget;
}
var metahash = Utility.WrapMetadata(metadata, m_options);
AddSymlinkToOutput(backend, path, DateTime.UtcNow, metahash);
m_result.AddVerboseMessage("Stored symlink {0}", path);
//Do not recurse symlinks
return false;
}
}
else
{
m_result.AddVerboseMessage("Treating empty symlink as regular path {0}", path);
}
}
@@ -17,6 +17,7 @@
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.IO;
using Duplicati.Library.Snapshots;
namespace Duplicati.Library.Main.Operation
{
@@ -44,7 +45,7 @@ namespace Duplicati.Library.Main.Operation
try { fa = snapshot.GetAttributes(path); }
catch { }
if (storeSymlinks && ((fa & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint))
if (storeSymlinks && snapshot.IsSymlink(path, fa))
{
m_result.AddVerboseMessage("Storing symlink: {0}", path);
}
+1
View File
@@ -50,6 +50,7 @@ namespace Duplicati.Library.Snapshots
FileAttributes GetFileAttributes(string path);
void SetFileAttributes(string path, FileAttributes attributes);
void CreateSymlink(string symlinkfile, string target, bool asDir);
string GetSymlinkTarget(string path);
string PathGetDirectoryName(string path);
string PathGetFileName(string path);
string PathGetExtension(string path);
+1 -1
View File
@@ -455,7 +455,7 @@ namespace Duplicati.Library.Snapshots
public string GetSymlinkTarget(string file)
{
var local = ConvertToSnapshotPath(FindSnapShotByLocalPath(file), file);
return UnixSupport.File.GetSymlinkTarget(NoSnapshot.NormalizePath(local));
return _sysIO.GetSymlinkTarget(local);
}
/// <summary>
@@ -45,7 +45,7 @@ namespace Duplicati.Library.Snapshots
/// <returns>The symlink target</returns>
public override string GetSymlinkTarget(string file)
{
return UnixSupport.File.GetSymlinkTarget(NormalizePath(file));
return _sysIO.GetSymlinkTarget(file);
}
/// <summary>
@@ -46,11 +46,7 @@ namespace Duplicati.Library.Snapshots
/// <returns>The symlink target</returns>
public override string GetSymlinkTarget(string file)
{
try { return File.GetLinkTargetInfo(SystemIOWindows.PrefixWithUNC(file)).PrintName; }
catch (NotAReparsePointException) { }
catch (UnrecognizedReparsePointException) { }
return null;
return m_sysIO.GetSymlinkTarget(file);
}
/// <summary>
@@ -19,6 +19,7 @@
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Duplicati.Library.Snapshots
@@ -71,6 +72,60 @@ namespace Duplicati.Library.Snapshots
return new WindowsSnapshot(folders, options);
}
/// <summary>
/// Extension method for ISnapshotService which determines whether the given path is a symlink.
/// </summary>
/// <param name="snapshot">ISnapshotService implementation</param>
/// <param name="path">File or folder path</param>
/// <returns>Whether the path is a symlink</returns>
public static bool IsSymlink(this ISnapshotService snapshot, string path)
{
return snapshot.IsSymlink(path, snapshot.GetAttributes(path));
}
/// <summary>
/// Extension method for ISnapshotService which determines whether the given path is a symlink.
/// </summary>
/// <param name="snapshot">ISnapshotService implementation</param>
/// <param name="path">File or folder path</param>
/// <param name="attributes">File attributes</param>
/// <returns>Whether the path is a symlink</returns>
public static bool IsSymlink(this ISnapshotService snapshot, string path, FileAttributes attributes)
{
// Not all reparse points are symlinks.
// For example, on Windows 10 Fall Creator's Update, the OneDrive folder (and all subfolders)
// are reparse points, which allows the folder to hook into the OneDrive service and download things on-demand.
// If we can't find a symlink target for the current path, we won't treat it as a symlink.
return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint && !string.IsNullOrEmpty(snapshot.GetSymlinkTarget(path));
}
/// <summary>
/// Extension method for ISystemIO which determines whether the given path is a symlink.
/// </summary>
/// <param name="systemIO">ISystemIO implementation</param>
/// <param name="path">File or folder path</param>
/// <returns>Whether the path is a symlink</returns>
public static bool IsSymlink(this ISystemIO systemIO, string path)
{
return systemIO.IsSymlink(path, systemIO.GetFileAttributes(path));
}
/// <summary>
/// Extension method for ISystemIO which determines whether the given path is a symlink.
/// </summary>
/// <param name="systemIO">ISystemIO implementation</param>
/// <param name="path">File or folder path</param>
/// <param name="attributes">File attributes</param>
/// <returns>Whether the path is a symlink</returns>
public static bool IsSymlink(this ISystemIO systemIO, string path, FileAttributes attributes)
{
// Not all reparse points are symlinks.
// For example, on Windows 10 Fall Creator's Update, the OneDrive folder (and all subfolders)
// are reparse points, which allows the folder to hook into the OneDrive service and download things on-demand.
// If we can't find a symlink target for the current path, we won't treat it as a symlink.
return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint && !string.IsNullOrEmpty(systemIO.GetSymlinkTarget(path));
}
/// <summary>
/// Gets an interface for System.IO, which wraps all operations in a platform consistent manner.
/// </summary>
@@ -104,6 +104,11 @@ namespace Duplicati.Library.Snapshots
{
UnixSupport.File.CreateSymlink(symlinkfile, target);
}
public string GetSymlinkTarget(string path)
{
return UnixSupport.File.GetSymlinkTarget(NoSnapshot.NormalizePath(path));
}
public string PathGetDirectoryName(string path)
{
@@ -21,6 +21,8 @@ using System.Collections.Generic;
using System.Security.AccessControl;
using System.IO;
using AlphaFS = Alphaleonis.Win32.Filesystem;
namespace Duplicati.Library.Snapshots
{
@@ -261,6 +263,32 @@ namespace Duplicati.Library.Snapshots
throw new System.IO.IOException(string.Format("Unable to create symlink, check account permissions: {0}", symlinkfile));
}
/// <summary>
/// Returns the symlink target if the entry is a symlink, and null otherwise
/// </summary>
/// <param name="file">The file or folder to examine</param>
/// <returns>The symlink target</returns>
public string GetSymlinkTarget(string file)
{
try
{
try
{
return AlphaFS.File.GetLinkTargetInfo(file).PrintName;
}
catch (PathTooLongException) { }
return AlphaFS.File.GetLinkTargetInfo(SystemIOWindows.PrefixWithUNC(file)).PrintName;
}
catch (AlphaFS.NotAReparsePointException) { }
catch (AlphaFS.UnrecognizedReparsePointException) { }
// This path looks like it isn't actually a symlink
// (Note that some reparse points aren't actually symlinks -
// things like the OneDrive folder in the Windows 10 Fall Creator's Update for example)
return null;
}
public IEnumerable<string> EnumerateFileSystemEntries(string path)
{
if (!IsPathTooLong(path))
+13 -17
View File
@@ -25,6 +25,8 @@ using System.Collections.Generic;
using System.IO;
using Alphaleonis.Win32.Vss;
using AlphaFS = Alphaleonis.Win32.Filesystem;
namespace Duplicati.Library.Snapshots
{
/// <summary>
@@ -129,7 +131,7 @@ namespace Duplicati.Library.Snapshots
m_volumes = new Dictionary<string, Guid>(StringComparer.OrdinalIgnoreCase);
foreach (string s in m_sourcepaths)
{
string drive = Alphaleonis.Win32.Filesystem.Path.GetPathRoot(s);
string drive = AlphaFS.Path.GetPathRoot(s);
if (!m_volumes.ContainsKey(drive))
{
if (!m_backup.IsVolumeSupported(drive))
@@ -198,14 +200,14 @@ namespace Duplicati.Library.Snapshots
/// <returns>A list of non-shadow paths</returns>
private string[] ListFolders(string folder)
{
string root = Utility.Utility.AppendDirSeparator(Alphaleonis.Win32.Filesystem.Path.GetPathRoot(folder));
string root = Utility.Utility.AppendDirSeparator(AlphaFS.Path.GetPathRoot(folder));
string volumePath = Utility.Utility.AppendDirSeparator(GetSnapshotPath(root));
string[] tmp = null;
string spath = GetSnapshotPath(folder);
if (SystemIOWindows.IsPathTooLong(spath))
try { tmp = Alphaleonis.Win32.Filesystem.Directory.GetDirectories(spath); }
try { tmp = AlphaFS.Directory.GetDirectories(spath); }
catch (PathTooLongException) { }
catch (DirectoryNotFoundException) { }
else
@@ -216,7 +218,7 @@ namespace Duplicati.Library.Snapshots
{
spath = SystemIOWindows.PrefixWithUNC(spath);
volumePath = SystemIOWindows.PrefixWithUNC(volumePath);
tmp = Alphaleonis.Win32.Filesystem.Directory.GetDirectories(spath);
tmp = AlphaFS.Directory.GetDirectories(spath);
}
volumePath = SystemIOWindows.PrefixWithUNC(volumePath);
@@ -235,14 +237,14 @@ namespace Duplicati.Library.Snapshots
/// <returns>A list of non-shadow paths</returns>
private string[] ListFiles(string folder)
{
string root = Utility.Utility.AppendDirSeparator(Alphaleonis.Win32.Filesystem.Path.GetPathRoot(folder));
string root = Utility.Utility.AppendDirSeparator(AlphaFS.Path.GetPathRoot(folder));
string volumePath = Utility.Utility.AppendDirSeparator(GetSnapshotPath(root));
string[] tmp = null;
string spath = GetSnapshotPath(folder);
if (SystemIOWindows.IsPathTooLong(spath))
try { tmp = Alphaleonis.Win32.Filesystem.Directory.GetFiles(spath); }
try { tmp = AlphaFS.Directory.GetFiles(spath); }
catch (PathTooLongException) { }
catch (DirectoryNotFoundException) { }
else
@@ -253,7 +255,7 @@ namespace Duplicati.Library.Snapshots
{
spath = SystemIOWindows.PrefixWithUNC(spath);
volumePath = SystemIOWindows.PrefixWithUNC(volumePath);
tmp = Alphaleonis.Win32.Filesystem.Directory.GetFiles(spath);
tmp = AlphaFS.Directory.GetFiles(spath);
}
volumePath = SystemIOWindows.PrefixWithUNC(volumePath);
@@ -273,7 +275,7 @@ namespace Duplicati.Library.Snapshots
if (!Path.IsPathRooted(localPath))
throw new InvalidOperationException();
string root = Alphaleonis.Win32.Filesystem.Path.GetPathRoot(localPath);
string root = AlphaFS.Path.GetPathRoot(localPath);
string volumePath;
if (!m_volumeMap.TryGetValue(root, out volumePath))
@@ -318,7 +320,7 @@ namespace Duplicati.Library.Snapshots
}
catch (PathTooLongException) { }
return Alphaleonis.Win32.Filesystem.File.GetLastWriteTimeUtc(SystemIOWindows.PrefixWithUNC(spath));
return AlphaFS.File.GetLastWriteTimeUtc(SystemIOWindows.PrefixWithUNC(spath));
}
/// <summary>
@@ -336,7 +338,7 @@ namespace Duplicati.Library.Snapshots
}
catch (PathTooLongException) { }
return Alphaleonis.Win32.Filesystem.File.GetCreationTimeUtc(SystemIOWindows.PrefixWithUNC(spath));
return AlphaFS.File.GetCreationTimeUtc(SystemIOWindows.PrefixWithUNC(spath));
}
/// <summary>
@@ -377,13 +379,7 @@ namespace Duplicati.Library.Snapshots
public string GetSymlinkTarget(string file)
{
string spath = GetSnapshotPath(file);
try
{
return Alphaleonis.Win32.Filesystem.File.GetLinkTargetInfo(spath).PrintName;
}
catch (PathTooLongException) { }
return Alphaleonis.Win32.Filesystem.File.GetLinkTargetInfo(SystemIOWindows.PrefixWithUNC(spath)).PrintName;
return _ioWin.GetSymlinkTarget(spath);
}
/// <summary>
@@ -18,6 +18,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Duplicati.Library.Snapshots;
namespace Duplicati.Server.WebServer.RESTMethods
{
@@ -215,7 +216,7 @@ namespace Duplicati.Server.WebServer.RESTMethods
try
{
var attr = systemIO.GetFileAttributes(s);
var isSymlink = (attr & FileAttributes.ReparsePoint) != 0;
var isSymlink = systemIO.IsSymlink(s, attr);
var isFolder = (attr & FileAttributes.Directory) != 0;
var isFile = !isFolder;
var isHidden = (attr & FileAttributes.Hidden) != 0;