Files
duplicati/Duplicati/Library/Common/IO/PosixFile.cs
T
Marcelo Ceccon d4d9c47824 Further progress on removal of build warnings.
None of these changes have impact on code execution, they are mainly part of the effort to remove all build warnings.

The removal of Thread.ResetAbort leaves a TODO for us to integrate CancelationToken when it gets propagated down here on future changes.
2024-11-07 18:02:15 +01:00

368 lines
14 KiB
C#

// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Runtime.Versioning;
using Mono.Unix.Native;
namespace Duplicati.Library.Common.IO
{
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macOS")]
public static class PosixFile
{
private static readonly bool SUPPORTS_LLISTXATTR;
static PosixFile ()
{
bool works = false;
try
{
string[] v;
Mono.Unix.Native.Syscall.llistxattr("/", out v);
works = true;
}
catch (EntryPointNotFoundException)
{
}
catch
{
}
SUPPORTS_LLISTXATTR = works;
}
/// <summary>
/// Opens the file and honors advisory locking.
/// </summary>
/// <returns>A open stream that references the file</returns>
/// <param name="path">The full path to the file</param>
public static System.IO.Stream OpenExclusive(string path, System.IO.FileAccess mode)
{
return OpenExclusive(path, mode, (int)Mono.Unix.Native.FilePermissions.DEFFILEMODE);
}
/// <summary>
/// Opens the file and honors advisory locking.
/// </summary>
/// <returns>A open stream that references the file</returns>
/// <param name="path">The full path to the file</param>
/// <param name="filemode">The file create mode</param>
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()))
{
}
}
/// <summary>
/// Gets the symlink target for the given path
/// </summary>
/// <param name="path">The path to get the symlink target for</param>
/// <returns>The symlink target</returns>
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()));
}
/// <summary>
/// Creates a new symlink
/// </summary>
/// <param name="path">The path to create the symbolic link entry</param>
/// <param name="target">The path the symbolic link points to</param>
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()));
}
/// <summary>
/// Enum that describes the different filesystem entry types
/// </summary>
public enum FileType
{
File,
Directory,
Symlink,
Fifo,
Socket,
CharacterDevice,
BlockDevice,
Unknown
}
/// <summary>
/// Gets the type of the file.
/// </summary>
/// <returns>The file type</returns>
/// <param name="path">The full path to look up</param>
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.BlockDevice;
else
return FileType.Unknown;
}
/// <summary>
/// Gets the extended attributes.
/// </summary>
/// <returns>The extended attributes.</returns>
/// <param name="path">The full path to look up</param>
/// <param name="isSymlink">A flag indicating if the target is a symlink</param>
/// <param name="followSymlink">A flag indicating if a symlink should be followed</param>
public static Dictionary<string, byte[]> 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
var err = Syscall.GetLastError();
if (err == Errno.EOPNOTSUPP || err == Errno.ENODATA)
return null;
throw new FileAccesException(path, use_llistxattr ? "llistxattr" : "listxattr");
}
var dict = new Dictionary<string, byte[]>();
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;
}
/// <summary>
/// Sets an extended attribute.
/// </summary>
/// <param name="path">The full path to set the values for</param>
/// <param name="key">The extended attribute key</param>
/// <param name="value">The value to set</param>
public static void SetExtendedAttribute(string path, string key, byte[] value)
{
Mono.Unix.Native.Syscall.setxattr(path, key, value);
}
/// <summary>
/// Describes the basic user/group/perm tuplet for a file or folder
/// </summary>
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;
}
}
}
/// <summary>
/// Gets the basic user/group/perm tuplet for a file or folder
/// </summary>
/// <returns>The basic user/group/perm tuplet for a file or folder</returns>
/// <param name="path">The full path to look up</param>
public static FileInfo GetUserGroupAndPermissions(string path)
{
return new FileInfo(Mono.Unix.UnixFileInfo.GetFileSystemEntry(path));
}
/// <summary>
/// Sets the basic user/group/perm tuplet for a file or folder
/// </summary>
/// <param name="path">The full path to look up</param>
/// <param name="uid">The owner user id to set</param>
/// <param name="gid">The owner group id to set</param>
/// <param name="permissions">The file access permissions to set</param>
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;
}
/// <summary>
/// Gets the UID from a user name
/// </summary>
/// <returns>The user ID.</returns>
/// <param name="name">The user name.</param>
public static long GetUserID(string name)
{
return new Mono.Unix.UnixUserInfo(name).UserId;
}
/// <summary>
/// Gets the GID from a group name
/// </summary>
/// <returns>The user ID.</returns>
/// <param name="name">The group name.</param>
public static long GetGroupID(string name)
{
return new Mono.Unix.UnixGroupInfo(name).GroupId;
}
/// <summary>
/// Gets the number of hard links for a file
/// </summary>
/// <returns>The hardlink count</returns>
/// <param name="path">The full path to look up</param>
public static long GetHardlinkCount(string path)
{
var fse = Mono.Unix.UnixFileInfo.GetFileSystemEntry(path);
if (fse.IsRegularFile || fse.IsDirectory)
return fse.LinkCount;
else
return 0;
}
/// <summary>
/// Gets a unique ID for the path inode target,
/// which is the device ID and inode ID
/// joined with a &quot;:&quot;
/// </summary>
/// <returns>The inode target ID.</returns>
/// <param name="path">The full path to look up</param>
public static string GetInodeTargetID(string path)
{
var fse = Mono.Unix.UnixFileInfo.GetFileSystemEntry(path);
return fse.Device + ":" + fse.Inode;
}
}
}