From 1f7110503a25572a5070eb4913d9a485a6f44052 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Thu, 3 Nov 2016 13:11:53 +0100 Subject: Updated unix symlinks handling to store correct metadata for symlinks --- Duplicati/Library/Main/Operation/BackupHandler.cs | 2 +- Duplicati/Library/Snapshots/ISnapshotService.cs | 4 +- Duplicati/Library/Snapshots/ISystemIO.cs | 2 +- Duplicati/Library/Snapshots/LinuxSnapshot.cs | 6 +- Duplicati/Library/Snapshots/NoSnapshot.cs | 6 +- Duplicati/Library/Snapshots/NoSnapshotLinux.cs | 6 +- Duplicati/Library/Snapshots/NoSnapshotWindows.cs | 6 +- Duplicati/Library/Snapshots/SystemIOLinux.cs | 4 +- Duplicati/Library/Snapshots/SystemIOWindows.cs | 2 +- Duplicati/Library/Snapshots/WindowsSnapshot.cs | 6 +- thirdparty/UnixSupport/File.cs | 678 +++++++++++----------- thirdparty/UnixSupport/UnixSupport.dll | Bin 8192 -> 8192 bytes 12 files changed, 371 insertions(+), 351 deletions(-) diff --git a/Duplicati/Library/Main/Operation/BackupHandler.cs b/Duplicati/Library/Main/Operation/BackupHandler.cs index e4a90b082..90e5df11a 100644 --- a/Duplicati/Library/Main/Operation/BackupHandler.cs +++ b/Duplicati/Library/Main/Operation/BackupHandler.cs @@ -835,7 +835,7 @@ namespace Duplicati.Library.Main.Operation if (m_options.StoreMetadata) { - metadata = snapshot.GetMetadata(path); + metadata = snapshot.GetMetadata(path, attributes.HasFlag(System.IO.FileAttributes.ReparsePoint), m_symlinkPolicy == Options.SymlinkStrategy.Follow); if (metadata == null) metadata = new Dictionary(); diff --git a/Duplicati/Library/Snapshots/ISnapshotService.cs b/Duplicati/Library/Snapshots/ISnapshotService.cs index 2060a466e..314e8b66e 100644 --- a/Duplicati/Library/Snapshots/ISnapshotService.cs +++ b/Duplicati/Library/Snapshots/ISnapshotService.cs @@ -81,7 +81,9 @@ namespace Duplicati.Library.Snapshots /// /// The metadata for the given file or folder /// The file or folder to examine - Dictionary GetMetadata(string file); + /// A flag indicating if the target is a symlink + /// A flag indicating if a symlink should be followed + Dictionary GetMetadata(string file, bool isSymlink, bool followSymlink); /// /// Gets a value indicating if the path points to a block device diff --git a/Duplicati/Library/Snapshots/ISystemIO.cs b/Duplicati/Library/Snapshots/ISystemIO.cs index 417f0698b..cf3fef902 100644 --- a/Duplicati/Library/Snapshots/ISystemIO.cs +++ b/Duplicati/Library/Snapshots/ISystemIO.cs @@ -57,7 +57,7 @@ namespace Duplicati.Library.Snapshots IEnumerable EnumerateFileSystemEntries(string path); void SetMetadata(string path, Dictionary metdata, bool restorePermissions); - Dictionary GetMetadata(string path); + Dictionary GetMetadata(string path, bool isSymlink, bool followSymlink); } } diff --git a/Duplicati/Library/Snapshots/LinuxSnapshot.cs b/Duplicati/Library/Snapshots/LinuxSnapshot.cs index 8978cfa26..e2a5d6b08 100644 --- a/Duplicati/Library/Snapshots/LinuxSnapshot.cs +++ b/Duplicati/Library/Snapshots/LinuxSnapshot.cs @@ -463,10 +463,12 @@ namespace Duplicati.Library.Snapshots /// /// The metadata for the given file or folder /// The file or folder to examine - public Dictionary GetMetadata(string file) + /// A flag indicating if the target is a symlink + /// A flag indicating if a symlink should be followed + public Dictionary GetMetadata(string file, bool isSymlink, bool followSymlink) { var local = ConvertToSnapshotPath(FindSnapShotByLocalPath(file), file); - return _sysIO.GetMetadata(local); + return _sysIO.GetMetadata(local, isSymlink, followSymlink); } /// diff --git a/Duplicati/Library/Snapshots/NoSnapshot.cs b/Duplicati/Library/Snapshots/NoSnapshot.cs index bd228a3e0..edfca74ba 100644 --- a/Duplicati/Library/Snapshots/NoSnapshot.cs +++ b/Duplicati/Library/Snapshots/NoSnapshot.cs @@ -176,7 +176,9 @@ namespace Duplicati.Library.Snapshots /// /// The metadata for the given file or folder /// The file or folder to examine - public abstract Dictionary GetMetadata(string file); + /// A flag indicating if the target is a symlink + /// A flag indicating if a symlink should be followed + public abstract Dictionary GetMetadata(string file, bool isSymlink, bool followSymlink); /// /// Gets a value indicating if the path points to a block device @@ -189,7 +191,7 @@ namespace Duplicati.Library.Snapshots /// Gets a unique hardlink target ID /// /// The hardlink ID - /// The file or folder to examine + /// The file or folder to examine public abstract string HardlinkTargetID(string path); #endregion } diff --git a/Duplicati/Library/Snapshots/NoSnapshotLinux.cs b/Duplicati/Library/Snapshots/NoSnapshotLinux.cs index cd586349e..a6d18c3b7 100644 --- a/Duplicati/Library/Snapshots/NoSnapshotLinux.cs +++ b/Duplicati/Library/Snapshots/NoSnapshotLinux.cs @@ -53,9 +53,11 @@ namespace Duplicati.Library.Snapshots /// /// The metadata for the given file or folder /// The file or folder to examine - public override Dictionary GetMetadata(string file) + /// A flag indicating if the target is a symlink + /// A flag indicating if a symlink should be followed + public override Dictionary GetMetadata(string file, bool isSymlink, bool followSymlink) { - return _sysIO.GetMetadata(file); + return _sysIO.GetMetadata(file, isSymlink, followSymlink); } /// diff --git a/Duplicati/Library/Snapshots/NoSnapshotWindows.cs b/Duplicati/Library/Snapshots/NoSnapshotWindows.cs index 00cb23c86..e708042ac 100644 --- a/Duplicati/Library/Snapshots/NoSnapshotWindows.cs +++ b/Duplicati/Library/Snapshots/NoSnapshotWindows.cs @@ -146,9 +146,11 @@ namespace Duplicati.Library.Snapshots /// /// The metadata for the given file or folder /// The file or folder to examine - public override Dictionary GetMetadata(string file) + /// A flag indicating if the target is a symlink + /// A flag indicating if a symlink should be followed + public override Dictionary GetMetadata(string file, bool isSymlink, bool followSymlink) { - return m_sysIO.GetMetadata(file); + return m_sysIO.GetMetadata(file, isSymlink, followSymlink); } /// diff --git a/Duplicati/Library/Snapshots/SystemIOLinux.cs b/Duplicati/Library/Snapshots/SystemIOLinux.cs index d6e8e1a72..2e05cabc9 100644 --- a/Duplicati/Library/Snapshots/SystemIOLinux.cs +++ b/Duplicati/Library/Snapshots/SystemIOLinux.cs @@ -165,12 +165,12 @@ namespace Duplicati.Library.Snapshots Directory.Delete(NoSnapshot.NormalizePath(path), recursive); } - public Dictionary GetMetadata(string file) + public Dictionary GetMetadata(string file, bool isSymlink, bool followSymlink) { var f = NoSnapshot.NormalizePath(file); var dict = new Dictionary(); - var n = UnixSupport.File.GetExtendedAttributes(f); + var n = UnixSupport.File.GetExtendedAttributes(f, isSymlink, followSymlink); if (n != null) foreach(var x in n) dict["unix-ext:" + x.Key] = Convert.ToBase64String(x.Value); diff --git a/Duplicati/Library/Snapshots/SystemIOWindows.cs b/Duplicati/Library/Snapshots/SystemIOWindows.cs index cba47b071..c4b3cc44c 100644 --- a/Duplicati/Library/Snapshots/SystemIOWindows.cs +++ b/Duplicati/Library/Snapshots/SystemIOWindows.cs @@ -500,7 +500,7 @@ namespace Duplicati.Library.Snapshots Alphaleonis.Win32.Filesystem.Directory.SetAccessControl(PrefixWithUNC(path), rules, AccessControlSections.All); } - public Dictionary GetMetadata(string path) + public Dictionary GetMetadata(string path, bool isSymlink, bool followSymlink) { var isDirTarget = path.EndsWith(DIRSEP); var targetpath = isDirTarget ? path.Substring(0, path.Length - 1) : path; diff --git a/Duplicati/Library/Snapshots/WindowsSnapshot.cs b/Duplicati/Library/Snapshots/WindowsSnapshot.cs index 78fd4b343..fd3f4d3ee 100644 --- a/Duplicati/Library/Snapshots/WindowsSnapshot.cs +++ b/Duplicati/Library/Snapshots/WindowsSnapshot.cs @@ -394,9 +394,11 @@ namespace Duplicati.Library.Snapshots /// /// The metadata for the given file or folder /// The file or folder to examine - public Dictionary GetMetadata(string file) + /// A flag indicating if the target is a symlink + /// A flag indicating if a symlink should be followed + public Dictionary GetMetadata(string file, bool isSymlink, bool followSymlink) { - return _ioWin.GetMetadata(GetSnapshotPath(file)); + return _ioWin.GetMetadata(GetSnapshotPath(file), isSymlink, followSymlink); } /// diff --git a/thirdparty/UnixSupport/File.cs b/thirdparty/UnixSupport/File.cs index 18ace7fca..6bb48665b 100644 --- a/thirdparty/UnixSupport/File.cs +++ b/thirdparty/UnixSupport/File.cs @@ -1,335 +1,343 @@ -using System; -using System.Collections.Generic; -using Mono.Unix.Native; - -namespace UnixSupport -{ - public static class File - { - - private static readonly bool SUPPORTS_LLISTXATTR; - - static File () - { - bool works = false; - try - { - string[] v; - Mono.Unix.Native.Syscall.llistxattr("/", out v); - works = true; - } - catch (EntryPointNotFoundException e) - { - } - catch - { - } - SUPPORTS_LLISTXATTR = works; - } - - /// - /// Opens the file and honors advisory locking. - /// - /// A open stream that references the file - /// The full path to the file - public static System.IO.Stream OpenExclusive(string path, System.IO.FileAccess mode) - { - return OpenExclusive(path, mode, (int)Mono.Unix.Native.FilePermissions.DEFFILEMODE); - } - - /// - /// Opens the file and honors advisory locking. - /// - /// A open stream that references the file - /// The full path to the file - /// The file create mode - public static System.IO.Stream OpenExclusive(string path, System.IO.FileAccess mode, int filemode) - { - Flock lck; - lck.l_len = 0; - lck.l_pid = Syscall.getpid(); - lck.l_start = 0; - lck.l_type = LockType.F_WRLCK; - lck.l_whence = SeekFlags.SEEK_SET; - - OpenFlags flags = OpenFlags.O_CREAT; - if (mode == System.IO.FileAccess.Read) - { - lck.l_type = LockType.F_RDLCK; - flags |= OpenFlags.O_RDONLY; - } else if (mode == System.IO.FileAccess.Write) { - flags |= OpenFlags.O_WRONLY; - } else { - flags |= OpenFlags.O_RDWR; - } - - int fd = Syscall.open(path, flags, (Mono.Unix.Native.FilePermissions)filemode); - if (fd > 0) - { - //This does not work on OSX, it gives ENOTTY - //int res = Syscall.fcntl(fd, Mono.Unix.Native.FcntlCommand.F_SETLK, ref lck); - - //This is the same (at least for our purpose, and works on OSX) - int res = Syscall.lockf(fd, LockfCommand.F_TLOCK, 0); - - //If we have the lock, return the stream - if (res == 0) - return new Mono.Unix.UnixStream(fd); - else - { - Mono.Unix.Native.Syscall.close(fd); - throw new LockedFileException(path, mode); - } - } - - throw new BadFileException(path); - } - - [Serializable] - private class BadFileException : System.IO.IOException - { - public BadFileException(string filename) - : base(string.Format("Unable to open the file \"{0}\", error: {1} ({2})", filename, Syscall.GetLastError(), (int)Syscall.GetLastError())) - { - } - } - - [Serializable] - private class LockedFileException : System.IO.IOException - { - public LockedFileException(string filename, System.IO.FileAccess mode) - : base(string.Format("Unable to open the file \"{0}\" in mode {1}, error: {2} ({3})", filename, mode, Syscall.GetLastError(), (int)Syscall.GetLastError())) - { - } - } - - [Serializable] - private class FileAccesException : System.IO.IOException - { - public FileAccesException(string filename, string method) - : base(string.Format("Unable to access the file \"{0}\" with method {1}, error: {2} ({3})", filename, method, Syscall.GetLastError(), (int)Syscall.GetLastError())) - { - } - } - - /// - /// Gets the symlink target for the given path - /// - /// The path to get the symlink target for - /// The symlink target - public static string GetSymlinkTarget(string path) - { - System.Text.StringBuilder sb = new System.Text.StringBuilder(2048); //2kb, should cover utf16 * 1023 chars - if (Mono.Unix.Native.Syscall.readlink(path, sb, (ulong)sb.Capacity) >= 0) - return sb.ToString(); - - throw new System.IO.FileLoadException(string.Format("Unable to get symlink for \"{0}\", error: {1} ({2})", path, Syscall.GetLastError(), (int)Syscall.GetLastError())); - } - - /// - /// Creates a new symlink - /// - /// The path to create the symbolic link entry - /// The path the symbolic link points to - public static void CreateSymlink(string path, string target) - { - if (Mono.Unix.Native.Syscall.symlink(target, path) != 0) - throw new System.IO.IOException(string.Format("Unable to create symlink from \"{0}\" to \"{1}\", error: {2} ({3})", path, target, Syscall.GetLastError(), (int)Syscall.GetLastError())); - } - - /// - /// Enum that describes the different filesystem entry types - /// - public enum FileType - { - File, - Directory, - Symlink, - Fifo, - Socket, - CharacterDevice, - BlockDevice, - Unknown - } - - /// - /// Gets the type of the file. - /// - /// The file type - /// The full path to look up - public static FileType GetFileType(string path) - { - - var fse = Mono.Unix.UnixFileInfo.GetFileSystemEntry(path); - if (fse.IsRegularFile) - return FileType.File; - else if (fse.IsDirectory) - return FileType.Directory; - else if (fse.IsSymbolicLink) - return FileType.Symlink; - else if (fse.IsFifo) - return FileType.Fifo; - else if (fse.IsSocket) - return FileType.Socket; - else if (fse.IsCharacterDevice) - return FileType.CharacterDevice; - else if (fse.IsBlockDevice) - return FileType.CharacterDevice; - else - return FileType.Unknown; - } - - - /// - /// Gets the extended attributes. - /// - /// The extended attributes. - /// The full path to look up - public static Dictionary GetExtendedAttributes(string path) - { - string[] values; - var size = SUPPORTS_LLISTXATTR ? Mono.Unix.Native.Syscall.llistxattr(path, out values) : Mono.Unix.Native.Syscall.listxattr(path, out values); - if (size < 0) - { - // In case the underlying filesystem does not support extended attributes, - // we simply return that there are no attributes - if (Syscall.GetLastError() == Errno.EOPNOTSUPP) - return null; - - throw new FileAccesException(path, "llistxattr"); - } - - var dict = new Dictionary(); - foreach(var s in values) - { - byte[] v; - var n = SUPPORTS_LLISTXATTR ? Mono.Unix.Native.Syscall.lgetxattr(path, s, out v) : Mono.Unix.Native.Syscall.getxattr(path, s, out v); - if (n > 0) - dict.Add(s, v); - } - - return dict; - } - - /// - /// Sets an extended attribute. - /// - /// The full path to set the values for - /// The extended attribute key - /// The value to set - public static void SetExtendedAttribute(string path, string key, byte[] value) - { - Mono.Unix.Native.Syscall.setxattr(path, key, value); - } - - /// - /// Describes the basic user/group/perm tuplet for a file or folder - /// - public struct FileInfo - { - public readonly long UID; - public readonly long GID; - public readonly long Permissions; - public readonly string OwnerName; - public readonly string GroupName; - - internal FileInfo(Mono.Unix.UnixFileSystemInfo fse) - { - UID = fse.OwnerUserId; - GID = fse.OwnerGroupId; - Permissions = (long)fse.FileAccessPermissions; - - try - { - OwnerName = fse.OwnerUser.UserName; - } - catch (ArgumentException) - { - // Could not retrieve user name, possibly the user is not defined on the local system - OwnerName = null; - } - - try - { - GroupName = fse.OwnerGroup.GroupName; - } - catch (ArgumentException) - { - // Could not retrieve group name, possibly the group is not defined on the local system - GroupName = null; - } - } - } - - /// - /// Gets the basic user/group/perm tuplet for a file or folder - /// - /// The basic user/group/perm tuplet for a file or folder - /// The full path to look up - public static FileInfo GetUserGroupAndPermissions(string path) - { - return new FileInfo(Mono.Unix.UnixFileInfo.GetFileSystemEntry(path)); - } - - /// - /// Sets the basic user/group/perm tuplet for a file or folder - /// - /// The full path to look up - /// The owner user id to set - /// The owner group id to set - /// The file access permissions to set - public static void SetUserGroupAndPermissions(string path, long uid, long gid, long permissions) - { - Mono.Unix.UnixFileInfo.GetFileSystemEntry(path).SetOwner(uid, gid); - Mono.Unix.UnixFileInfo.GetFileSystemEntry(path).FileAccessPermissions = (Mono.Unix.FileAccessPermissions)permissions; - } - - /// - /// Gets the UID from a user name - /// - /// The user ID. - /// The user name. - public static long GetUserID(string name) - { - return new Mono.Unix.UnixUserInfo(name).UserId; - } - - /// - /// Gets the GID from a group name - /// - /// The user ID. - /// The group name. - public static long GetGroupID(string name) - { - return new Mono.Unix.UnixGroupInfo(name).GroupId; - } - - /// - /// Gets the number of hard links for a file - /// - /// The hardlink count - /// The full path to look up - public static long GetHardlinkCount(string path) - { - var fse = Mono.Unix.UnixFileInfo.GetFileSystemEntry(path); - if (fse.IsRegularFile || fse.IsDirectory) - return fse.LinkCount; - else - return 0; - } - - /// - /// Gets a unique ID for the path inode target, - /// which is the device ID and inode ID - /// joined with a ":" - /// - /// The inode target ID. - /// The full path to look up - public static string GetInodeTargetID(string path) - { - var fse = Mono.Unix.UnixFileInfo.GetFileSystemEntry(path); - return fse.Device + ":" + fse.Inode; - } - } -} - +using System; +using System.Collections.Generic; +using Mono.Unix.Native; + +namespace UnixSupport +{ + public static class File + { + + private static readonly bool SUPPORTS_LLISTXATTR; + + static File () + { + bool works = false; + try + { + string[] v; + Mono.Unix.Native.Syscall.llistxattr("/", out v); + works = true; + } + catch (EntryPointNotFoundException e) + { + } + catch + { + } + SUPPORTS_LLISTXATTR = works; + } + + /// + /// Opens the file and honors advisory locking. + /// + /// A open stream that references the file + /// The full path to the file + public static System.IO.Stream OpenExclusive(string path, System.IO.FileAccess mode) + { + return OpenExclusive(path, mode, (int)Mono.Unix.Native.FilePermissions.DEFFILEMODE); + } + + /// + /// Opens the file and honors advisory locking. + /// + /// A open stream that references the file + /// The full path to the file + /// The file create mode + public static System.IO.Stream OpenExclusive(string path, System.IO.FileAccess mode, int filemode) + { + Flock lck; + lck.l_len = 0; + lck.l_pid = Syscall.getpid(); + lck.l_start = 0; + lck.l_type = LockType.F_WRLCK; + lck.l_whence = SeekFlags.SEEK_SET; + + OpenFlags flags = OpenFlags.O_CREAT; + if (mode == System.IO.FileAccess.Read) + { + lck.l_type = LockType.F_RDLCK; + flags |= OpenFlags.O_RDONLY; + } else if (mode == System.IO.FileAccess.Write) { + flags |= OpenFlags.O_WRONLY; + } else { + flags |= OpenFlags.O_RDWR; + } + + int fd = Syscall.open(path, flags, (Mono.Unix.Native.FilePermissions)filemode); + if (fd > 0) + { + //This does not work on OSX, it gives ENOTTY + //int res = Syscall.fcntl(fd, Mono.Unix.Native.FcntlCommand.F_SETLK, ref lck); + + //This is the same (at least for our purpose, and works on OSX) + int res = Syscall.lockf(fd, LockfCommand.F_TLOCK, 0); + + //If we have the lock, return the stream + if (res == 0) + return new Mono.Unix.UnixStream(fd); + else + { + Mono.Unix.Native.Syscall.close(fd); + throw new LockedFileException(path, mode); + } + } + + throw new BadFileException(path); + } + + [Serializable] + private class BadFileException : System.IO.IOException + { + public BadFileException(string filename) + : base(string.Format("Unable to open the file \"{0}\", error: {1} ({2})", filename, Syscall.GetLastError(), (int)Syscall.GetLastError())) + { + } + } + + [Serializable] + private class LockedFileException : System.IO.IOException + { + public LockedFileException(string filename, System.IO.FileAccess mode) + : base(string.Format("Unable to open the file \"{0}\" in mode {1}, error: {2} ({3})", filename, mode, Syscall.GetLastError(), (int)Syscall.GetLastError())) + { + } + } + + [Serializable] + private class FileAccesException : System.IO.IOException + { + public FileAccesException(string filename, string method) + : base(string.Format("Unable to access the file \"{0}\" with method {1}, error: {2} ({3})", filename, method, Syscall.GetLastError(), (int)Syscall.GetLastError())) + { + } + } + + /// + /// Gets the symlink target for the given path + /// + /// The path to get the symlink target for + /// The symlink target + public static string GetSymlinkTarget(string path) + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(2048); //2kb, should cover utf16 * 1023 chars + if (Mono.Unix.Native.Syscall.readlink(path, sb, (ulong)sb.Capacity) >= 0) + return sb.ToString(); + + throw new System.IO.FileLoadException(string.Format("Unable to get symlink for \"{0}\", error: {1} ({2})", path, Syscall.GetLastError(), (int)Syscall.GetLastError())); + } + + /// + /// Creates a new symlink + /// + /// The path to create the symbolic link entry + /// The path the symbolic link points to + public static void CreateSymlink(string path, string target) + { + if (Mono.Unix.Native.Syscall.symlink(target, path) != 0) + throw new System.IO.IOException(string.Format("Unable to create symlink from \"{0}\" to \"{1}\", error: {2} ({3})", path, target, Syscall.GetLastError(), (int)Syscall.GetLastError())); + } + + /// + /// Enum that describes the different filesystem entry types + /// + public enum FileType + { + File, + Directory, + Symlink, + Fifo, + Socket, + CharacterDevice, + BlockDevice, + Unknown + } + + /// + /// Gets the type of the file. + /// + /// The file type + /// The full path to look up + public static FileType GetFileType(string path) + { + + var fse = Mono.Unix.UnixFileInfo.GetFileSystemEntry(path); + if (fse.IsRegularFile) + return FileType.File; + else if (fse.IsDirectory) + return FileType.Directory; + else if (fse.IsSymbolicLink) + return FileType.Symlink; + else if (fse.IsFifo) + return FileType.Fifo; + else if (fse.IsSocket) + return FileType.Socket; + else if (fse.IsCharacterDevice) + return FileType.CharacterDevice; + else if (fse.IsBlockDevice) + return FileType.CharacterDevice; + else + return FileType.Unknown; + } + + + /// + /// Gets the extended attributes. + /// + /// The extended attributes. + /// The full path to look up + /// A flag indicating if the target is a symlink + /// A flag indicating if a symlink should be followed + public static Dictionary GetExtendedAttributes(string path, bool isSymlink, bool followSymlink) + { + // If we get a symlink that we should not follow, we need llistxattr support + if (isSymlink && !followSymlink && !SUPPORTS_LLISTXATTR) + return null; + + var use_llistxattr = SUPPORTS_LLISTXATTR && !followSymlink; + + string[] values; + var size = use_llistxattr ? Mono.Unix.Native.Syscall.llistxattr(path, out values) : Mono.Unix.Native.Syscall.listxattr(path, out values); + if (size < 0) + { + // In case the underlying filesystem does not support extended attributes, + // we simply return that there are no attributes + if (Syscall.GetLastError() == Errno.EOPNOTSUPP) + return null; + + throw new FileAccesException(path, use_llistxattr ? "llistxattr" : "listxattr"); + } + + var dict = new Dictionary(); + foreach(var s in values) + { + byte[] v; + var n = SUPPORTS_LLISTXATTR ? Mono.Unix.Native.Syscall.lgetxattr(path, s, out v) : Mono.Unix.Native.Syscall.getxattr(path, s, out v); + if (n > 0) + dict.Add(s, v); + } + + return dict; + } + + /// + /// Sets an extended attribute. + /// + /// The full path to set the values for + /// The extended attribute key + /// The value to set + public static void SetExtendedAttribute(string path, string key, byte[] value) + { + Mono.Unix.Native.Syscall.setxattr(path, key, value); + } + + /// + /// Describes the basic user/group/perm tuplet for a file or folder + /// + public struct FileInfo + { + public readonly long UID; + public readonly long GID; + public readonly long Permissions; + public readonly string OwnerName; + public readonly string GroupName; + + internal FileInfo(Mono.Unix.UnixFileSystemInfo fse) + { + UID = fse.OwnerUserId; + GID = fse.OwnerGroupId; + Permissions = (long)fse.FileAccessPermissions; + + try + { + OwnerName = fse.OwnerUser.UserName; + } + catch (ArgumentException) + { + // Could not retrieve user name, possibly the user is not defined on the local system + OwnerName = null; + } + + try + { + GroupName = fse.OwnerGroup.GroupName; + } + catch (ArgumentException) + { + // Could not retrieve group name, possibly the group is not defined on the local system + GroupName = null; + } + } + } + + /// + /// Gets the basic user/group/perm tuplet for a file or folder + /// + /// The basic user/group/perm tuplet for a file or folder + /// The full path to look up + public static FileInfo GetUserGroupAndPermissions(string path) + { + return new FileInfo(Mono.Unix.UnixFileInfo.GetFileSystemEntry(path)); + } + + /// + /// Sets the basic user/group/perm tuplet for a file or folder + /// + /// The full path to look up + /// The owner user id to set + /// The owner group id to set + /// The file access permissions to set + public static void SetUserGroupAndPermissions(string path, long uid, long gid, long permissions) + { + Mono.Unix.UnixFileInfo.GetFileSystemEntry(path).SetOwner(uid, gid); + Mono.Unix.UnixFileInfo.GetFileSystemEntry(path).FileAccessPermissions = (Mono.Unix.FileAccessPermissions)permissions; + } + + /// + /// Gets the UID from a user name + /// + /// The user ID. + /// The user name. + public static long GetUserID(string name) + { + return new Mono.Unix.UnixUserInfo(name).UserId; + } + + /// + /// Gets the GID from a group name + /// + /// The user ID. + /// The group name. + public static long GetGroupID(string name) + { + return new Mono.Unix.UnixGroupInfo(name).GroupId; + } + + /// + /// Gets the number of hard links for a file + /// + /// The hardlink count + /// The full path to look up + public static long GetHardlinkCount(string path) + { + var fse = Mono.Unix.UnixFileInfo.GetFileSystemEntry(path); + if (fse.IsRegularFile || fse.IsDirectory) + return fse.LinkCount; + else + return 0; + } + + /// + /// Gets a unique ID for the path inode target, + /// which is the device ID and inode ID + /// joined with a ":" + /// + /// The inode target ID. + /// The full path to look up + public static string GetInodeTargetID(string path) + { + var fse = Mono.Unix.UnixFileInfo.GetFileSystemEntry(path); + return fse.Device + ":" + fse.Inode; + } + } +} + diff --git a/thirdparty/UnixSupport/UnixSupport.dll b/thirdparty/UnixSupport/UnixSupport.dll index 12ff4d9e9..5a48244d8 100755 Binary files a/thirdparty/UnixSupport/UnixSupport.dll and b/thirdparty/UnixSupport/UnixSupport.dll differ -- cgit v1.2.3