Files
duplicati/Duplicati/Library/Common/IO/SystemIOWindows.cs
T

543 lines
20 KiB
C#
Raw Normal View History

// Copyright (C) 2015, The Duplicati Team
2014-03-18 10:59:06 +01:00
2015-01-20 21:44:52 +01:00
// http://www.duplicati.com, info@duplicati.com
2014-03-18 10:59:06 +01:00
//
// 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 System.Collections.Generic;
2014-11-15 17:43:42 +01:00
using System.Security.AccessControl;
using System.IO;
2018-11-28 16:21:11 +01:00
using System.Linq;
2014-03-18 10:59:06 +01:00
using AlphaFS = Alphaleonis.Win32.Filesystem;
2018-11-01 17:56:48 +01:00
using Duplicati.Library.Interface;
2019-12-24 07:21:25 -08:00
using Newtonsoft.Json;
2014-03-18 10:59:06 +01:00
2018-11-02 21:34:07 +01:00
namespace Duplicati.Library.Common.IO
2014-03-18 10:59:06 +01:00
{
public struct SystemIOWindows : ISystemIO
{
private const string UNCPREFIX = @"\\?\";
private const string UNCPREFIX_SERVER = @"\\?\UNC\";
private const string PATHPREFIX_SERVER = @"\\";
private static readonly string DIRSEP = Util.DirectorySeparatorString;
2014-03-18 10:59:06 +01:00
public static string PrefixWithUNC(string path)
{
2020-07-15 20:14:47 -07:00
if (IsPrefixedWithUNC(path))
{
return path;
2020-07-15 20:14:47 -07:00
}
2018-11-28 16:21:11 +01:00
return path.StartsWith(PATHPREFIX_SERVER, StringComparison.Ordinal)
2020-07-15 20:14:47 -07:00
? UNCPREFIX_SERVER + path.Substring(PATHPREFIX_SERVER.Length)
2018-11-28 16:21:11 +01:00
: UNCPREFIX + path;
2014-03-18 10:59:06 +01:00
}
2020-07-15 20:14:47 -07:00
private static bool IsPrefixedWithUNC(string path)
{
return path.StartsWith(UNCPREFIX_SERVER, StringComparison.Ordinal) ||
path.StartsWith(UNCPREFIX, StringComparison.Ordinal);
}
2014-03-18 10:59:06 +01:00
public static string StripUNCPrefix(string path)
{
2020-07-19 16:24:51 -07:00
if (path.StartsWith(UNCPREFIX_SERVER, StringComparison.Ordinal))
2020-07-15 20:14:47 -07:00
{
// @"\\?\UNC\example.com\share\file.txt" to @"\\example.com\share\file.txt"
return PATHPREFIX_SERVER + path.Substring(UNCPREFIX_SERVER.Length);
}
2020-07-19 16:24:51 -07:00
else if (path.StartsWith(UNCPREFIX, StringComparison.Ordinal))
2020-07-15 20:14:47 -07:00
{
// @"\\?\C:\file.txt" to @"C:\file.txt"
return path.Substring(UNCPREFIX.Length);
}
else
{
return path;
}
2014-03-18 10:59:06 +01:00
}
/// <summary>
/// Convert forward slashes to backslashes.
/// </summary>
/// <returns>Path with forward slashes replaced by backslashes.</returns>
private static string ConvertSlashes(string path)
{
return path.Replace("/", Util.DirectorySeparatorString);
}
2018-11-29 09:13:13 +01:00
private class FileSystemAccess
{
2019-12-24 07:21:25 -08:00
// Use JsonProperty Attribute to allow readonly fields to be set by deserializer
// https://github.com/duplicati/duplicati/issues/4028
[JsonProperty]
public readonly FileSystemRights Rights;
2019-12-24 07:21:25 -08:00
[JsonProperty]
public readonly AccessControlType ControlType;
2019-12-24 07:21:25 -08:00
[JsonProperty]
public readonly string SID;
2019-12-24 07:21:25 -08:00
[JsonProperty]
public readonly bool Inherited;
2019-12-24 07:21:25 -08:00
[JsonProperty]
public readonly InheritanceFlags Inheritance;
2019-12-24 07:21:25 -08:00
[JsonProperty]
public readonly PropagationFlags Propagation;
2018-11-29 09:13:13 +01:00
public FileSystemAccess()
{
}
public FileSystemAccess(FileSystemAccessRule rule)
{
Rights = rule.FileSystemRights;
ControlType = rule.AccessControlType;
SID = rule.IdentityReference.Value;
Inherited = rule.IsInherited;
Inheritance = rule.InheritanceFlags;
Propagation = rule.PropagationFlags;
}
public FileSystemAccessRule Create(System.Security.AccessControl.FileSystemSecurity owner)
{
return (FileSystemAccessRule)owner.AccessRuleFactory(
new System.Security.Principal.SecurityIdentifier(SID),
(int)Rights,
Inherited,
Inheritance,
Propagation,
ControlType);
}
}
private static Newtonsoft.Json.JsonSerializer _cachedSerializer;
private Newtonsoft.Json.JsonSerializer Serializer
{
get
2018-11-29 09:32:42 +01:00
{
if (_cachedSerializer != null)
{
return _cachedSerializer;
}
_cachedSerializer = Newtonsoft.Json.JsonSerializer.Create(
new Newtonsoft.Json.JsonSerializerSettings { Culture = System.Globalization.CultureInfo.InvariantCulture });
return _cachedSerializer;
}
}
2018-11-29 09:32:42 +01:00
2018-11-29 09:13:13 +01:00
private string SerializeObject<T>(T o)
{
using (var tw = new System.IO.StringWriter())
{
2018-11-29 09:32:42 +01:00
Serializer.Serialize(tw, o);
2018-11-29 09:13:13 +01:00
tw.Flush();
return tw.ToString();
}
}
private T DeserializeObject<T>(string data)
{
using (var tr = new System.IO.StringReader(data))
2018-11-29 09:32:42 +01:00
{
return (T)Serializer.Deserialize(tr, typeof(T));
}
2018-11-29 09:13:13 +01:00
}
private System.Security.AccessControl.FileSystemSecurity GetAccessControlDir(string path)
{
2020-07-15 20:14:47 -07:00
return System.IO.Directory.GetAccessControl(PrefixWithUNC(path));
2018-11-29 09:13:13 +01:00
}
private System.Security.AccessControl.FileSystemSecurity GetAccessControlFile(string path)
{
2020-07-15 20:14:47 -07:00
return System.IO.File.GetAccessControl(PrefixWithUNC(path));
2018-11-29 09:13:13 +01:00
}
private void SetAccessControlFile(string path, FileSecurity rules)
{
2020-07-15 20:14:47 -07:00
System.IO.File.SetAccessControl(PrefixWithUNC(path), rules);
2018-11-29 09:13:13 +01:00
}
private void SetAccessControlDir(string path, DirectorySecurity rules)
{
2020-07-15 20:14:47 -07:00
System.IO.Directory.SetAccessControl(PrefixWithUNC(path), rules);
2014-03-18 10:59:06 +01:00
}
2018-11-28 16:21:11 +01:00
#region ISystemIO implementation
public void DirectoryCreate(string path)
{
2020-07-15 20:14:47 -07:00
System.IO.Directory.CreateDirectory(PrefixWithUNC(path));
2014-03-18 10:59:06 +01:00
}
public void DirectoryDelete(string path, bool recursive)
{
System.IO.Directory.Delete(PrefixWithUNC(path), recursive);
}
2018-11-28 16:21:11 +01:00
public bool DirectoryExists(string path)
2014-03-18 10:59:06 +01:00
{
2020-07-15 20:14:47 -07:00
return System.IO.Directory.Exists(PrefixWithUNC(path));
2018-11-28 16:21:11 +01:00
}
2014-03-18 10:59:06 +01:00
public void DirectoryMove(string sourceDirName, string destDirName)
{
System.IO.Directory.Move(PrefixWithUNC(sourceDirName), PrefixWithUNC(destDirName));
}
2018-11-28 16:21:11 +01:00
public void FileDelete(string path)
{
2020-07-15 20:14:47 -07:00
System.IO.File.Delete(PrefixWithUNC(path));
2014-03-18 10:59:06 +01:00
}
public void FileSetLastWriteTimeUtc(string path, DateTime time)
{
2020-07-15 20:14:47 -07:00
System.IO.File.SetLastWriteTimeUtc(PrefixWithUNC(path), time);
2014-03-18 10:59:06 +01:00
}
public void FileSetCreationTimeUtc(string path, DateTime time)
{
2020-07-15 20:14:47 -07:00
System.IO.File.SetCreationTimeUtc(PrefixWithUNC(path), time);
}
public DateTime FileGetLastWriteTimeUtc(string path)
{
2020-07-15 20:14:47 -07:00
return System.IO.File.GetLastWriteTimeUtc(PrefixWithUNC(path));
}
public DateTime FileGetCreationTimeUtc(string path)
{
2020-07-15 20:14:47 -07:00
return System.IO.File.GetCreationTimeUtc(PrefixWithUNC(path));
}
2014-03-18 10:59:06 +01:00
public bool FileExists(string path)
{
2020-07-15 20:14:47 -07:00
return System.IO.File.Exists(PrefixWithUNC(path));
2014-03-18 10:59:06 +01:00
}
2018-11-01 17:56:48 +01:00
public System.IO.FileStream FileOpenRead(string path)
2014-03-18 10:59:06 +01:00
{
2020-07-15 20:14:47 -07:00
return System.IO.File.Open(PrefixWithUNC(path), System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite);
2014-03-18 10:59:06 +01:00
}
2018-11-01 17:56:48 +01:00
public System.IO.FileStream FileOpenWrite(string path)
2014-03-18 10:59:06 +01:00
{
2018-11-28 16:21:11 +01:00
return !FileExists(path)
? FileCreate(path)
2020-07-15 20:14:47 -07:00
: System.IO.File.OpenWrite(PrefixWithUNC(path));
2014-03-18 10:59:06 +01:00
}
2018-11-01 17:56:48 +01:00
public System.IO.FileStream FileCreate(string path)
2014-03-18 10:59:06 +01:00
{
2020-07-15 20:14:47 -07:00
return System.IO.File.Create(PrefixWithUNC(path));
2014-03-18 10:59:06 +01:00
}
public System.IO.FileAttributes GetFileAttributes(string path)
{
2020-07-15 20:14:47 -07:00
return System.IO.File.GetAttributes(PrefixWithUNC(path));
2014-03-18 10:59:06 +01:00
}
public void SetFileAttributes(string path, System.IO.FileAttributes attributes)
{
2020-07-15 20:14:47 -07:00
System.IO.File.SetAttributes(PrefixWithUNC(path), attributes);
}
/// <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
{
2020-07-15 20:14:47 -07:00
return AlphaFS.File.GetLinkTargetInfo(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)
{
2020-07-15 20:14:47 -07:00
return System.IO.Directory.EnumerateFileSystemEntries(PrefixWithUNC(path)).Select(StripUNCPrefix);
}
public IEnumerable<string> EnumerateFiles(string path)
{
2020-07-15 20:14:47 -07:00
return System.IO.Directory.EnumerateFiles(PrefixWithUNC(path)).Select(StripUNCPrefix);
}
public IEnumerable<string> EnumerateFiles(string path, string searchPattern, SearchOption searchOption)
{
return System.IO.Directory.EnumerateFiles(PrefixWithUNC(path), searchPattern, searchOption).Select(StripUNCPrefix);
}
public string PathGetFileName(string path)
{
2020-07-15 20:14:47 -07:00
return StripUNCPrefix(System.IO.Path.GetFileName(PrefixWithUNC(path)));
}
2014-03-18 10:59:06 +01:00
public string PathGetDirectoryName(string path)
{
2020-07-15 20:14:47 -07:00
return StripUNCPrefix(System.IO.Path.GetDirectoryName(PrefixWithUNC(path)));
2014-03-18 10:59:06 +01:00
}
public string PathGetExtension(string path)
{
2020-07-15 20:14:47 -07:00
return StripUNCPrefix(System.IO.Path.GetExtension(PrefixWithUNC(path)));
2014-03-18 10:59:06 +01:00
}
2014-03-18 10:59:06 +01:00
public string PathChangeExtension(string path, string extension)
{
2020-07-15 20:14:47 -07:00
return StripUNCPrefix(System.IO.Path.ChangeExtension(PrefixWithUNC(path), extension));
2014-03-18 10:59:06 +01:00
}
public void DirectorySetLastWriteTimeUtc(string path, DateTime time)
{
2020-07-15 20:14:47 -07:00
System.IO.Directory.SetLastWriteTimeUtc(PrefixWithUNC(path), time);
2014-03-18 10:59:06 +01:00
}
public void DirectorySetCreationTimeUtc(string path, DateTime time)
{
2020-07-15 20:14:47 -07:00
System.IO.Directory.SetCreationTimeUtc(PrefixWithUNC(path), time);
}
2014-03-18 10:59:06 +01:00
public void FileMove(string source, string target)
{
2020-07-15 20:14:47 -07:00
System.IO.File.Move(PrefixWithUNC(source), PrefixWithUNC(target));
2014-03-18 10:59:06 +01:00
}
public long FileLength(string path)
{
2020-07-15 20:14:47 -07:00
return new System.IO.FileInfo(PrefixWithUNC(path)).Length;
2014-03-18 10:59:06 +01:00
}
public string GetPathRoot(string path)
2018-10-31 13:08:14 +01:00
{
2020-07-15 20:14:47 -07:00
if (IsPrefixedWithUNC(path))
{
return Path.GetPathRoot(path);
}
else
{
return StripUNCPrefix(Path.GetPathRoot(PrefixWithUNC(path)));
}
}
public string[] GetDirectories(string path)
{
2020-07-15 20:14:47 -07:00
if (IsPrefixedWithUNC(path))
{
return Directory.GetDirectories(path);
}
else
{
return Directory.GetDirectories(PrefixWithUNC(path)).Select(StripUNCPrefix).ToArray();
}
}
public string[] GetFiles(string path)
{
2020-07-15 20:14:47 -07:00
if (IsPrefixedWithUNC(path))
{
return Directory.GetFiles(path);
}
else
{
return Directory.GetFiles(PrefixWithUNC(path)).Select(StripUNCPrefix).ToArray();
}
}
public string[] GetFiles(string path, string searchPattern)
{
2020-07-15 20:14:47 -07:00
if (IsPrefixedWithUNC(path))
{
return Directory.GetFiles(path, searchPattern);
}
else
{
return Directory.GetFiles(PrefixWithUNC(path), searchPattern).Select(StripUNCPrefix).ToArray();
}
}
public DateTime GetCreationTimeUtc(string path)
{
2020-07-15 20:14:47 -07:00
return Directory.GetCreationTimeUtc(PrefixWithUNC(path));
}
public DateTime GetLastWriteTimeUtc(string path)
{
2020-07-15 20:14:47 -07:00
return Directory.GetLastWriteTimeUtc(PrefixWithUNC(path));
}
public IEnumerable<string> EnumerateDirectories(string path)
{
2020-07-15 20:14:47 -07:00
if (IsPrefixedWithUNC(path))
{
return Directory.EnumerateDirectories(path);
}
else
{
return Directory.EnumerateDirectories(PrefixWithUNC(path)).Select(StripUNCPrefix);
}
}
public void FileCopy(string source, string target, bool overwrite)
{
2020-07-15 20:14:47 -07:00
File.Copy(PrefixWithUNC(source), PrefixWithUNC(target), overwrite);
}
public string PathGetFullPath(string path)
{
2020-07-15 20:14:47 -07:00
if (IsPrefixedWithUNC(path))
{
return System.IO.Path.GetFullPath(ConvertSlashes(path));
2020-07-15 20:14:47 -07:00
}
else
{
return StripUNCPrefix(System.IO.Path.GetFullPath(PrefixWithUNC(ConvertSlashes(path))));
2020-07-15 20:14:47 -07:00
}
}
public IFileEntry DirectoryEntry(string path)
2018-11-28 16:21:11 +01:00
{
2020-07-15 20:14:47 -07:00
var dInfo = new DirectoryInfo(PrefixWithUNC(path));
return new FileEntry(dInfo.Name, 0, dInfo.LastAccessTime, dInfo.LastWriteTime)
2018-11-01 17:56:48 +01:00
{
2020-07-15 20:14:47 -07:00
IsFolder = true
};
}
public IFileEntry FileEntry(string path)
2018-11-28 16:21:11 +01:00
{
2020-07-15 20:14:47 -07:00
var fileInfo = new FileInfo(PrefixWithUNC(path));
return new FileEntry(fileInfo.Name, fileInfo.Length, fileInfo.LastAccessTime, fileInfo.LastWriteTime);
2018-11-29 09:13:13 +01:00
}
public Dictionary<string, string> GetMetadata(string path, bool isSymlink, bool followSymlink)
{
var isDirTarget = path.EndsWith(DIRSEP, StringComparison.Ordinal);
var targetpath = isDirTarget ? path.Substring(0, path.Length - 1) : path;
var dict = new Dictionary<string, string>();
FileSystemSecurity rules = isDirTarget ? GetAccessControlDir(targetpath) : GetAccessControlFile(targetpath);
var objs = new List<FileSystemAccess>();
foreach (var f in rules.GetAccessRules(true, false, typeof(System.Security.Principal.SecurityIdentifier)))
objs.Add(new FileSystemAccess((FileSystemAccessRule)f));
dict["win-ext:accessrules"] = SerializeObject(objs);
// Only include the following key when its value is True.
// This prevents unnecessary 'metadata change' detections when upgrading from
2020-03-18 04:57:25 -07:00
// older versions (pre-2.0.5.101) that didn't store this value at all.
// When key is not present, its value is presumed False by the restore code.
if (rules.AreAccessRulesProtected)
{
dict["win-ext:accessrulesprotected"] = "True";
}
2018-11-29 09:13:13 +01:00
return dict;
}
public void SetMetadata(string path, Dictionary<string, string> data, bool restorePermissions)
{
var isDirTarget = path.EndsWith(DIRSEP, StringComparison.Ordinal);
var targetpath = isDirTarget ? path.Substring(0, path.Length - 1) : path;
2019-12-26 07:51:30 -08:00
if (restorePermissions)
2019-12-25 20:56:28 -08:00
{
FileSystemSecurity rules = isDirTarget ? GetAccessControlDir(targetpath) : GetAccessControlFile(targetpath);
2019-12-26 07:51:30 -08:00
if (data.ContainsKey("win-ext:accessrulesprotected"))
2019-12-25 20:56:28 -08:00
{
2019-12-28 20:37:23 -08:00
bool isProtected = bool.Parse(data["win-ext:accessrulesprotected"]);
if (rules.AreAccessRulesProtected != isProtected)
2019-12-26 07:51:30 -08:00
{
2019-12-28 20:37:23 -08:00
rules.SetAccessRuleProtection(isProtected, false);
2019-12-26 07:51:30 -08:00
}
2019-12-25 20:56:28 -08:00
}
2019-12-26 07:51:30 -08:00
if (data.ContainsKey("win-ext:accessrules"))
{
var content = DeserializeObject<FileSystemAccess[]>(data["win-ext:accessrules"]);
var c = rules.GetAccessRules(true, false, typeof(System.Security.Principal.SecurityIdentifier));
for (var i = c.Count - 1; i >= 0; i--)
rules.RemoveAccessRule((System.Security.AccessControl.FileSystemAccessRule)c[i]);
2018-11-29 09:13:13 +01:00
2019-12-26 07:51:30 -08:00
Exception ex = null;
2018-11-29 09:13:13 +01:00
2019-12-26 07:51:30 -08:00
foreach (var r in content)
2018-11-29 09:13:13 +01:00
{
2019-12-26 07:51:30 -08:00
// Attempt to apply as many rules as we can
try
{
rules.AddAccessRule(r.Create(rules));
}
catch (Exception e)
{
ex = e;
}
2018-11-29 09:13:13 +01:00
}
2019-12-26 07:51:30 -08:00
if (ex != null)
throw ex;
}
if (isDirTarget)
SetAccessControlDir(targetpath, (DirectorySecurity)rules);
else
SetAccessControlFile(targetpath, (FileSecurity)rules);
2018-11-29 09:13:13 +01:00
}
}
public string PathCombine(params string[] paths)
{
2020-07-15 20:14:47 -07:00
return Path.Combine(paths);
2018-11-29 09:13:13 +01:00
}
public void CreateSymlink(string symlinkfile, string target, bool asDir)
{
if (FileExists(symlinkfile) || DirectoryExists(symlinkfile))
throw new System.IO.IOException(string.Format("File already exists: {0}", symlinkfile));
if (asDir)
{
Alphaleonis.Win32.Filesystem.Directory.CreateSymbolicLink(PrefixWithUNC(symlinkfile), target, AlphaFS.PathFormat.LongFullPath);
}
else
{
Alphaleonis.Win32.Filesystem.File.CreateSymbolicLink(PrefixWithUNC(symlinkfile), target, AlphaFS.PathFormat.LongFullPath);
}
//Sadly we do not get a notification if the creation fails :(
System.IO.FileAttributes attr = 0;
if ((!asDir && FileExists(symlinkfile)) || (asDir && DirectoryExists(symlinkfile)))
try { attr = GetFileAttributes(symlinkfile); }
catch { }
if ((attr & System.IO.FileAttributes.ReparsePoint) == 0)
throw new System.IO.IOException(string.Format("Unable to create symlink, check account permissions: {0}", symlinkfile));
}
#endregion
2014-03-18 10:59:06 +01:00
}
}