Merge pull request #3014 from tygill/fix/reparse-points-arent-symlinks
Fix 2829: Reparse points aren't always symlinks
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user