2024-02-28 15:45:30 +01:00
// 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.
2016-11-03 13:18:00 +01:00
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 ;
}
/// <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 )
{
2020-02-29 17:36:58 -08:00
2016-11-03 13:18:00 +01:00
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 )
2020-02-29 17:36:58 -08:00
return FileType . BlockDevice ;
2016-11-03 13:18:00 +01:00
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
2018-08-14 22:25:22 +02:00
var err = Syscall . GetLastError ();
if ( err == Errno . EOPNOTSUPP || err == Errno . ENODATA )
2016-11-03 13:18:00 +01:00
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 ":"
/// </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 ;
}
}
}