Merged r1413 and r1414 into trunk.

git-svn-id: https://duplicati.googlecode.com/svn/branches/1.3.x@1415 59da171f-624f-0410-aa54-27559c288bec
This commit is contained in:
kenneth@hexad.dk
2012-08-06 20:45:45 +00:00
parent ed73bea40b
commit c67f44d145
6 changed files with 344 additions and 44 deletions
+2
View File
@@ -168,6 +168,7 @@ namespace Duplicati.Library.Main
"signature-control-files",
"snapshot-policy",
"vss-exclude-writers",
"vss-use-mapping",
"usn-policy",
"open-file-policy",
"exclude-empty-folders",
@@ -367,6 +368,7 @@ namespace Duplicati.Library.Main
new CommandLineArgument("snapshot-policy", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.SnapshotpolicyShort, Strings.Options.SnapshotpolicyLong, "off", null, new string[] {"auto", "off", "on", "required"}),
new CommandLineArgument("vss-exclude-writers", CommandLineArgument.ArgumentType.String, Strings.Options.VssexcludewritersShort, Strings.Options.VssexcludewritersLong),
new CommandLineArgument("vss-use-mapping", CommandLineArgument.ArgumentType.Boolean, Strings.Options.VssusemappingShort, Strings.Options.VssusemappingLong, "false"),
new CommandLineArgument("usn-policy", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.UsnpolicyShort, Strings.Options.UsnpolicyLong, "off", null, new string[] {"auto", "off", "on", "required"}),
new CommandLineArgument("open-file-policy", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.OpenfilepolicyShort, Strings.Options.OpenfilepolicyLong, "snapshot", null, new string[] { "ignore", "snapshot", "copy" }),
+45 -28
View File
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.239
// Runtime Version:4.0.30319.544
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -474,6 +474,24 @@ namespace Duplicati.Library.Main.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to Use this option to exclude files with certain attributes. Use a comma separated list of attribute names to specify more that one. Possible values are: {0}.
/// </summary>
internal static string ExcludefilesattributesLong {
get {
return ResourceManager.GetString("ExcludefilesattributesLong", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Exclude files by attribute.
/// </summary>
internal static string ExcludefilesattributesShort {
get {
return ResourceManager.GetString("ExcludefilesattributesShort", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Exclude files that match this filter. The filter is a &quot;file-globbing&quot; filter, much like commandline options. You can use &quot;*.txt&quot; to exclude all text files..
/// </summary>
@@ -1077,6 +1095,24 @@ namespace Duplicati.Library.Main.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to Using this option to handle symlinks different. The &quot;{0}&quot; option will simply record a symlink with its name and destination, and a restore will recreate the symlink as a link. Use the option &quot;{1}&quot; to ignore all symlinks and not store any information about them. Previous versions of Duplicati used the setting &quot;{2}&quot;, which will cause symlinked files to be included and restore as normal files..
/// </summary>
internal static string SymlinkpolicyLong {
get {
return ResourceManager.GetString("SymlinkpolicyLong", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Symlink handling.
/// </summary>
internal static string SymlinkpolicyShort {
get {
return ResourceManager.GetString("SymlinkpolicyShort", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Duplicati will upload files while scanning the disk and producing volumes, which usually makes the backup faster. Use this flag to turn the behavior off, so that Duplicati will wait for each volume to complete..
/// </summary>
@@ -1186,7 +1222,7 @@ namespace Duplicati.Library.Main.Strings {
}
/// <summary>
/// Looks up a localized string similar to Upload empty backup files .
/// Looks up a localized string similar to Upload empty backup files.
/// </summary>
internal static string UploadUnchangedBackupsShort {
get {
@@ -1265,41 +1301,22 @@ namespace Duplicati.Library.Main.Strings {
return ResourceManager.GetString("VssexcludewritersShort", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Using this option to handle symlinks different. The "{0}" option will simply record a symlink with its name and destination, and a restore will recreate the symlink as a link. Use the option "{1}" to ignore all symlinks and not store any information about them. Previous versions of Duplicati used the setting "{2}", which will cause symlinked files to be included and restore as normal files..
/// Looks up a localized string similar to Activate this option to map VSS snapshots to a drive (similar to SUBST, using Win32 DefineDosDevice). This will create temporary drives that are then used to access the contents of a snapshot. This workaround can speed up file access on Windows XP..
/// </summary>
internal static string SymlinkpolicyLong {
internal static string VssusemappingLong {
get {
return ResourceManager.GetString("SymlinkpolicyLong", resourceCulture);
return ResourceManager.GetString("VssusemappingLong", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Symlink handling.
/// Looks up a localized string similar to Map snapshots to a drive (Windows only).
/// </summary>
internal static string SymlinkpolicyShort {
internal static string VssusemappingShort {
get {
return ResourceManager.GetString("SymlinkpolicyShort", resourceCulture);
}
}
/// <summary>
/// Exclude files by attribute.
/// </summary>
internal static string ExcludefilesattributesShort {
get {
return ResourceManager.GetString("ExcludefilesattributesShort", resourceCulture);
}
}
/// <summary>
/// Use this option to exclude files with certain attributes. Use a comma separated list of attribute names to specify more that one. Possible values are: {0}.
/// </summary>
internal static string ExcludefilesattributesLong {
get {
return ResourceManager.GetString("ExcludefilesattributesLong", resourceCulture);
return ResourceManager.GetString("VssusemappingShort", resourceCulture);
}
}
}
+7 -2
View File
@@ -518,7 +518,7 @@
</data>
<data name="QuotasizeShort" xml:space="preserve">
<value>A reported maximum storage</value>
</data>
</data>
<data name="SymlinkpolicyShort" xml:space="preserve">
<value>Symlink handling</value>
</data>
@@ -531,5 +531,10 @@
<data name="ExcludefilesattributesLong" xml:space="preserve">
<value>Use this option to exclude files with certain attributes. Use a comma separated list of attribute names to specify more that one. Possible values are: {0}</value>
</data>
<data name="VssusemappingLong" xml:space="preserve">
<value>Activate this option to map VSS snapshots to a drive (similar to SUBST, using Win32 DefineDosDevice). This will create temporary drives that are then used to access the contents of a snapshot. This workaround can speed up file access on Windows XP.</value>
</data>
<data name="VssusemappingShort" xml:space="preserve">
<value>Map snapshots to a drive (Windows only)</value>
</data>
</root>
@@ -0,0 +1,167 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace Duplicati.Library.Snapshots
{
/// <summary>
/// Implements a convenient wrapping for mapping a path to a drive letter
/// </summary>
public class DefineDosDevice : IDisposable
{
/// <summary>
/// Encapsulation of Win32 calls
/// </summary>
private static class Win32API
{
/// <summary>
/// Flags that can be used with DefineDosDevice
/// </summary>
[Flags]
public enum DDD_Flags : uint
{
/// <summary>
/// Uses the targetpath string as is. Otherwise, it is converted from an MS-DOS path to a path.
/// </summary>
DDD_RAW_TARGET_PATH = 0x1,
/// <summary>
/// Removes the specified definition for the specified device. To determine which definition to remove, the function walks the list of mappings for the device, looking for a match of targetpath against a prefix of each mapping associated with this device. The first mapping that matches is the one removed, and then the function returns.
/// If targetpath is NULL or a pointer to a NULL string, the function will remove the first mapping associated with the device and pop the most recent one pushed. If there is nothing left to pop, the device name will be removed.
/// If this value is not specified, the string pointed to by the targetpath parameter will become the new mapping for this device.
/// </summary>
DDD_REMOVE_DEFINITION = 0x2,
/// <summary>
/// If this value is specified along with DDD_REMOVE_DEFINITION, the function will use an exact match to determine which mapping to remove. Use this value to ensure that you do not delete something that you did not define.
/// </summary>
DDD_EXACT_MATCH_ON_REMOVE = 0x4,
/// <summary>
/// Do not broadcast the WM_SETTINGCHANGE message. By default, this message is broadcast to notify the shell and applications of the change.
/// </summary>
DDD_NO_BROADCAST_SYSTEM = 0x8
}
/// <summary>
/// Defines, redefines, or deletes MS-DOS device names.
/// </summary>
/// <param name="flags">The controllable aspects of the DefineDosDevice function</param>
/// <param name="devicename">A pointer to an MS-DOS device name string specifying the device the function is defining, redefining, or deleting. The device name string must not have a colon as the last character, unless a drive letter is being defined, redefined, or deleted. For example, drive C would be the string &quot;C:&quot;. In no case is a trailing backslash (&quot;\&quot;) allowed.</param>
/// <param name="targetpath">A pointer to a path string that will implement this device. The string is an MS-DOS path string unless the DDD_RAW_TARGET_PATH flag is specified, in which case this string is a path string.</param>
/// <returns>True on success, false otherwise</returns>
[System.Runtime.InteropServices.DllImport("kernel32", CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)]
public static extern bool DefineDosDevice(DDD_Flags flags, string devicename, string targetpath);
}
/// <summary>
/// The drive the path is mapped to
/// </summary>
private string m_drive;
/// <summary>
/// The path that is mapped to the drive
/// </summary>
private string m_targetPath;
/// <summary>
/// A value indicating if the shell should be notified of changes
/// </summary>
private bool m_shellBroadcast;
/// <summary>
/// Gets the drive that this mapping represents
/// </summary>
public string Drive { get { return m_drive; } }
/// <summary>
/// Gets the path that this mapping represents
/// </summary>
public string Targetpath { get { return m_targetPath; } }
/// <summary>
/// Creates a new mapping, using default settings
/// </summary>
/// <param name="path">The path to map</param>
public DefineDosDevice(string path)
: this(path, null, false)
{
}
/// <summary>
/// Creates a new mapping
/// </summary>
/// <param name="path">The path to map</param>
/// <param name="drive">The drive to map to, use null to get a free drive letter</param>
/// <param name="notifyShell">True to notify the shell of the change, false otherwise</param>
public DefineDosDevice(string path, string drive, bool notifyShell)
{
if (string.IsNullOrEmpty(drive))
{
List<char> drives = new List<char>("DEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray());
foreach (DriveInfo di in DriveInfo.GetDrives())
{
if ((di.RootDirectory.FullName.Length == 2 && di.RootDirectory.FullName[1] == ':') || ((di.RootDirectory.FullName.Length == 3 && di.RootDirectory.FullName.EndsWith(":\\"))))
{
int i = drives.IndexOf(di.RootDirectory.FullName[0]);
if (i >= 0)
drives.RemoveAt(i);
}
}
if (drives.Count == 0)
throw new IOException("No drive letters available");
drive = drives[0].ToString() + ':';
}
while (drive.EndsWith("\\"))
drive = drive.Substring(0, drive.Length - 1);
if (!drive.EndsWith(":"))
throw new ArgumentException("drive");
Win32API.DDD_Flags flags = 0;
if (!notifyShell)
flags |= Win32API.DDD_Flags.DDD_NO_BROADCAST_SYSTEM;
if (!Win32API.DefineDosDevice(flags, drive, path))
throw new System.ComponentModel.Win32Exception();
m_drive = drive;
m_targetPath = path;
m_shellBroadcast = notifyShell;
}
/// <summary>
/// Disposes all resources held
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// Disposes all resources
/// </summary>
/// <param name="disposing">True if called from Disposing, false otherwise</param>
protected void Dispose(bool disposing)
{
if (m_drive != null)
{
Win32API.DDD_Flags flags = Win32API.DDD_Flags.DDD_REMOVE_DEFINITION | Win32API.DDD_Flags.DDD_EXACT_MATCH_ON_REMOVE;
if (m_shellBroadcast)
flags |= Win32API.DDD_Flags.DDD_NO_BROADCAST_SYSTEM;
bool success = Win32API.DefineDosDevice(flags, m_drive, m_targetPath);
m_drive = null;
}
if (disposing)
GC.SuppressFinalize(this);
}
/// <summary>
/// Destroys the object and releases all held resources
/// </summary>
~DefineDosDevice()
{
Dispose(false);
}
}
}
@@ -76,6 +76,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="DefineDosDevice.cs" />
<Compile Include="FileHasChangedException.cs" />
<Compile Include="ISnapshotService.cs" />
<Compile Include="LinuxSnapshot.cs" />
+122 -14
View File
@@ -52,9 +52,22 @@ namespace Duplicati.Library.Snapshots
/// </summary>
private Dictionary<string, Guid> m_volumes;
/// <summary>
/// The mapping of snapshot sources to their snapshot entries , key is the path root, eg C:\.
/// The dictionary is case insensitive
/// </summary>
private Dictionary<string, string> m_volumeMap;
/// <summary>
/// A list of mapped drives
/// </summary>
private List<DefineDosDevice> m_mappedDrives;
/// <summary>
/// Commonly used string element
/// </summary>
private static string SLASH = Path.DirectorySeparatorChar.ToString();
/// <summary>
/// A cached lookup for windows methods for dealing with long filenames
/// </summary>
private static readonly SystemIOWindows _ioWin = new SystemIOWindows();
/// <summary>
/// Constructs a new backup snapshot, using all the required disks
@@ -78,6 +91,9 @@ namespace Duplicati.Library.Snapshots
excludedWriters.Add(new Guid(s));
}
//Check if we should map any drives
bool useSubst = Utility.Utility.ParseBoolOption(options, "vss-use-mapping");
//Prepare the backup
m_backup = vss.CreateVssBackupComponents();
m_backup.InitializeForBackup(null);
@@ -111,7 +127,7 @@ namespace Duplicati.Library.Snapshots
m_volumes = new Dictionary<string, Guid>(StringComparer.InvariantCultureIgnoreCase);
foreach (string s in m_sourcepaths)
{
string drive = Path.GetPathRoot(s);
string drive = Alphaleonis.Win32.Filesystem.Path.GetPathRoot(s);
if (!m_volumes.ContainsKey(drive))
{
if (!m_backup.IsVolumeSupported(drive))
@@ -131,6 +147,27 @@ namespace Duplicati.Library.Snapshots
//Create the shadow volumes
using (IVssAsync async = m_backup.DoSnapshotSet())
async.Wait();
//Make a little lookup table for faster translation
m_volumeMap = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
foreach(KeyValuePair<string, Guid> kvp in m_volumes)
m_volumeMap.Add(kvp.Key, m_backup.GetSnapshotProperties(kvp.Value).SnapshotDeviceObject);
//If we should map the drives, we do that now and update the volumeMap
if (useSubst)
{
m_mappedDrives = new List<DefineDosDevice>();
foreach (string k in new List<string>(m_volumeMap.Keys))
{
try
{
DefineDosDevice d;
m_mappedDrives.Add(d = new DefineDosDevice(m_volumeMap[k]));
m_volumeMap[k] = Utility.Utility.AppendDirSeparator(d.Drive);
}
catch { }
}
}
}
catch
{
@@ -202,10 +239,28 @@ namespace Duplicati.Library.Snapshots
/// <returns>A list of non-shadow paths</returns>
private string[] ListFolders(string folder)
{
string root = Utility.Utility.AppendDirSeparator(Path.GetPathRoot(folder));
string root = Utility.Utility.AppendDirSeparator(Alphaleonis.Win32.Filesystem.Path.GetPathRoot(folder));
string volumePath = Utility.Utility.AppendDirSeparator(GetSnapshotPath(root));
string[] tmp = Alphaleonis.Win32.Filesystem.Directory.GetDirectories(GetSnapshotPath(folder));
string[] tmp = null;
string spath = GetSnapshotPath(folder);
if (SystemIOWindows.IsPathTooLong(spath))
try { tmp = Alphaleonis.Win32.Filesystem.Directory.GetDirectories(spath); }
catch (PathTooLongException) { }
catch (DirectoryNotFoundException) { }
else
try { tmp = System.IO.Directory.GetDirectories(spath); }
catch (PathTooLongException) { }
if (tmp == null)
{
spath = SystemIOWindows.PrefixWithUNC(spath);
volumePath = SystemIOWindows.PrefixWithUNC(volumePath);
tmp = Alphaleonis.Win32.Filesystem.Directory.GetDirectories(spath);
}
for (int i = 0; i < tmp.Length; i++)
tmp[i] = root + tmp[i].Substring(volumePath.Length);
return tmp;
@@ -220,9 +275,27 @@ namespace Duplicati.Library.Snapshots
/// <returns>A list of non-shadow paths</returns>
private string[] ListFiles(string folder)
{
string root = Utility.Utility.AppendDirSeparator(Path.GetPathRoot(folder));
string root = Utility.Utility.AppendDirSeparator(Alphaleonis.Win32.Filesystem.Path.GetPathRoot(folder));
string volumePath = Utility.Utility.AppendDirSeparator(GetSnapshotPath(root));
string[] tmp = Alphaleonis.Win32.Filesystem.Directory.GetFiles(GetSnapshotPath(folder));
string[] tmp = null;
string spath = GetSnapshotPath(folder);
if (SystemIOWindows.IsPathTooLong(spath))
try { tmp = Alphaleonis.Win32.Filesystem.Directory.GetFiles(spath); }
catch (PathTooLongException) { }
catch (DirectoryNotFoundException) { }
else
try { tmp = System.IO.Directory.GetFiles(spath); }
catch (PathTooLongException) { }
if (tmp == null)
{
spath = SystemIOWindows.PrefixWithUNC(spath);
volumePath = SystemIOWindows.PrefixWithUNC(volumePath);
tmp = Alphaleonis.Win32.Filesystem.Directory.GetFiles(spath);
}
for (int i = 0; i < tmp.Length; i++)
tmp[i] = root + tmp[i].Substring(volumePath.Length);
return tmp;
@@ -238,15 +311,14 @@ namespace Duplicati.Library.Snapshots
if (!Path.IsPathRooted(localPath))
throw new InvalidOperationException();
string root = Path.GetPathRoot(localPath);
string root = Alphaleonis.Win32.Filesystem.Path.GetPathRoot(localPath);
if (!m_volumes.ContainsKey(root))
string volumePath;
if (!m_volumeMap.TryGetValue(root, out volumePath))
throw new InvalidOperationException();
localPath = localPath.Replace(root, String.Empty);
string volumePath = m_backup.GetSnapshotProperties(m_volumes[root]).SnapshotDeviceObject;
if (!volumePath.EndsWith(SLASH) && !localPath.StartsWith(SLASH))
localPath = localPath.Insert(0, SLASH);
localPath = localPath.Insert(0, volumePath);
@@ -294,7 +366,15 @@ namespace Duplicati.Library.Snapshots
/// <returns>The last write time of the file</returns>
public DateTime GetLastWriteTime(string file)
{
return Alphaleonis.Win32.Filesystem.File.GetLastWriteTime(GetSnapshotPath(file));
string spath = GetSnapshotPath(file);
if (!SystemIOWindows.IsPathTooLong(spath))
try
{
return File.GetLastWriteTime(spath);
}
catch (PathTooLongException) { }
return Alphaleonis.Win32.Filesystem.File.GetLastWriteTime(SystemIOWindows.PrefixWithUNC(spath));
}
/// <summary>
@@ -304,7 +384,17 @@ namespace Duplicati.Library.Snapshots
/// <returns>An open filestream that can be read</returns>
public System.IO.Stream OpenRead(string file)
{
return Alphaleonis.Win32.Filesystem.File.OpenRead(GetSnapshotPath(file));
return _ioWin.FileOpenRead(GetSnapshotPath(file));
}
/// <summary>
/// Returns the size of a file
/// </summary>
/// <param name="file">The full path to the file in non-snapshot format</param>
/// <returns>The lenth of the file</returns>
public long GetFileSize(string file)
{
return _ioWin.FileLength(GetSnapshotPath(file));
}
/// <summary>
@@ -314,7 +404,7 @@ namespace Duplicati.Library.Snapshots
/// <param name="file">The file or folder to examine</param>
public System.IO.FileAttributes GetAttributes(string file)
{
return (System.IO.FileAttributes)Alphaleonis.Win32.Filesystem.File.GetAttributes(GetSnapshotPath(file));
return _ioWin.GetFileAttributes(GetSnapshotPath(file));
}
/// <summary>
@@ -324,17 +414,35 @@ namespace Duplicati.Library.Snapshots
/// <returns>The symlink target</returns>
public string GetSymlinkTarget(string file)
{
return Alphaleonis.Win32.Filesystem.File.GetLinkTargetInfo(file).PrintName;
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;
}
#endregion
#region IDisposable Members
/// <summary>
/// Cleans up any resources and closes the backup set
/// </summary>
public void Dispose()
{
try
{
if (m_mappedDrives != null)
{
foreach (DefineDosDevice d in m_mappedDrives)
d.Dispose();
m_mappedDrives = null;
m_volumeMap = null;
}
}
catch { }
try
{
if (m_backup != null)