|
|
@@ -1,7 +1,10 @@
|
|
|
-using System;
|
|
|
+#nullable enable
|
|
|
+using System;
|
|
|
using System.Collections.Generic;
|
|
|
-using System.Globalization;
|
|
|
+using System.Diagnostics;
|
|
|
+using System.Diagnostics.CodeAnalysis;
|
|
|
using System.Linq;
|
|
|
+using System.Text;
|
|
|
|
|
|
using Renci.SshNet.Common;
|
|
|
|
|
|
@@ -23,8 +26,8 @@ namespace Renci.SshNet.Sftp
|
|
|
private const uint S_IFCHR = 0x2000; // character device
|
|
|
private const uint S_IFIFO = 0x1000; // FIFO
|
|
|
private const uint S_ISUID = 0x0800; // set UID bit
|
|
|
- private const uint S_ISGID = 0x0400; // set-group-ID bit (see below)
|
|
|
- private const uint S_ISVTX = 0x0200; // sticky bit (see below)
|
|
|
+ private const uint S_ISGID = 0x0400; // set-group-ID bit
|
|
|
+ private const uint S_ISVTX = 0x0200; // sticky bit
|
|
|
private const uint S_IRUSR = 0x0100; // owner has read permission
|
|
|
private const uint S_IWUSR = 0x0080; // owner has write permission
|
|
|
private const uint S_IXUSR = 0x0040; // owner has execute permission
|
|
|
@@ -43,12 +46,7 @@ namespace Renci.SshNet.Sftp
|
|
|
private readonly int _originalUserId;
|
|
|
private readonly int _originalGroupId;
|
|
|
private readonly uint _originalPermissions;
|
|
|
- private readonly IDictionary<string, string> _originalExtensions;
|
|
|
-
|
|
|
- private bool _isBitFiledsBitSet;
|
|
|
- private bool _isUIDBitSet;
|
|
|
- private bool _isGroupIDBitSet;
|
|
|
- private bool _isStickyBitSet;
|
|
|
+ private readonly Dictionary<string, string>? _originalExtensions;
|
|
|
|
|
|
internal bool IsLastAccessTimeChanged
|
|
|
{
|
|
|
@@ -82,6 +80,7 @@ namespace Renci.SshNet.Sftp
|
|
|
|
|
|
internal bool IsExtensionsChanged
|
|
|
{
|
|
|
+ [MemberNotNullWhen(true, nameof(Extensions))]
|
|
|
get { return _originalExtensions != null && Extensions != null && !_originalExtensions.SequenceEqual(Extensions); }
|
|
|
}
|
|
|
|
|
|
@@ -169,7 +168,13 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if file represents a socket; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool IsSocket { get; private set; }
|
|
|
+ public bool IsSocket
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_IFMT) == S_IFSOCK;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets a value indicating whether file represents a symbolic link.
|
|
|
@@ -177,7 +182,13 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if file represents a symbolic link; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool IsSymbolicLink { get; private set; }
|
|
|
+ public bool IsSymbolicLink
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_IFMT) == S_IFLNK;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets a value indicating whether file represents a regular file.
|
|
|
@@ -185,7 +196,13 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if file represents a regular file; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool IsRegularFile { get; private set; }
|
|
|
+ public bool IsRegularFile
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_IFMT) == S_IFREG;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets a value indicating whether file represents a block device.
|
|
|
@@ -193,7 +210,13 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if file represents a block device; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool IsBlockDevice { get; private set; }
|
|
|
+ public bool IsBlockDevice
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_IFMT) == S_IFBLK;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets a value indicating whether file represents a directory.
|
|
|
@@ -201,7 +224,13 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if file represents a directory; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool IsDirectory { get; private set; }
|
|
|
+ public bool IsDirectory
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_IFMT) == S_IFDIR;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets a value indicating whether file represents a character device.
|
|
|
@@ -209,7 +238,13 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if file represents a character device; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool IsCharacterDevice { get; private set; }
|
|
|
+ public bool IsCharacterDevice
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_IFMT) == S_IFCHR;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets a value indicating whether file represents a named pipe.
|
|
|
@@ -217,7 +252,88 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if file represents a named pipe; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool IsNamedPipe { get; private set; }
|
|
|
+ public bool IsNamedPipe
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_IFMT) == S_IFIFO;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a value indicating whether the setuid bit is set.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>
|
|
|
+ /// <see langword="true"/> if the setuid bit is set; otherwise, <see langword="false"/>.
|
|
|
+ /// </value>
|
|
|
+ public bool IsUIDBitSet
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_ISUID) == S_ISUID;
|
|
|
+ }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (value)
|
|
|
+ {
|
|
|
+ Permissions |= S_ISUID;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Permissions &= ~S_ISUID;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a value indicating whether the setgid bit is set.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>
|
|
|
+ /// <see langword="true"/> if the setgid bit is set; otherwise, <see langword="false"/>.
|
|
|
+ /// </value>
|
|
|
+ public bool IsGroupIDBitSet
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_ISGID) == S_ISGID;
|
|
|
+ }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (value)
|
|
|
+ {
|
|
|
+ Permissions |= S_ISGID;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Permissions &= ~S_ISGID;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a value indicating whether the sticky bit is set.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>
|
|
|
+ /// <see langword="true"/> if the sticky bit is set; otherwise, <see langword="false"/>.
|
|
|
+ /// </value>
|
|
|
+ public bool IsStickyBitSet
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_ISVTX) == S_ISVTX;
|
|
|
+ }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (value)
|
|
|
+ {
|
|
|
+ Permissions |= S_ISVTX;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Permissions &= ~S_ISVTX;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets or sets a value indicating whether the owner can read from this file.
|
|
|
@@ -225,7 +341,24 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if owner can read from this file; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool OwnerCanRead { get; set; }
|
|
|
+ public bool OwnerCanRead
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_IRUSR) == S_IRUSR;
|
|
|
+ }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (value)
|
|
|
+ {
|
|
|
+ Permissions |= S_IRUSR;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Permissions &= ~S_IRUSR;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets or sets a value indicating whether the owner can write into this file.
|
|
|
@@ -233,7 +366,24 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if owner can write into this file; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool OwnerCanWrite { get; set; }
|
|
|
+ public bool OwnerCanWrite
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_IWUSR) == S_IWUSR;
|
|
|
+ }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (value)
|
|
|
+ {
|
|
|
+ Permissions |= S_IWUSR;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Permissions &= ~S_IWUSR;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets or sets a value indicating whether the owner can execute this file.
|
|
|
@@ -241,7 +391,24 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if owner can execute this file; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool OwnerCanExecute { get; set; }
|
|
|
+ public bool OwnerCanExecute
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_IXUSR) == S_IXUSR;
|
|
|
+ }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (value)
|
|
|
+ {
|
|
|
+ Permissions |= S_IXUSR;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Permissions &= ~S_IXUSR;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets or sets a value indicating whether the group members can read from this file.
|
|
|
@@ -249,7 +416,24 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if group members can read from this file; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool GroupCanRead { get; set; }
|
|
|
+ public bool GroupCanRead
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_IRGRP) == S_IRGRP;
|
|
|
+ }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (value)
|
|
|
+ {
|
|
|
+ Permissions |= S_IRGRP;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Permissions &= ~S_IRGRP;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets or sets a value indicating whether the group members can write into this file.
|
|
|
@@ -257,7 +441,24 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if group members can write into this file; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool GroupCanWrite { get; set; }
|
|
|
+ public bool GroupCanWrite
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_IWGRP) == S_IWGRP;
|
|
|
+ }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (value)
|
|
|
+ {
|
|
|
+ Permissions |= S_IWGRP;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Permissions &= ~S_IWGRP;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets or sets a value indicating whether the group members can execute this file.
|
|
|
@@ -265,7 +466,24 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if group members can execute this file; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool GroupCanExecute { get; set; }
|
|
|
+ public bool GroupCanExecute
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_IXGRP) == S_IXGRP;
|
|
|
+ }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (value)
|
|
|
+ {
|
|
|
+ Permissions |= S_IXGRP;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Permissions &= ~S_IXGRP;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets or sets a value indicating whether the others can read from this file.
|
|
|
@@ -273,7 +491,24 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if others can read from this file; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool OthersCanRead { get; set; }
|
|
|
+ public bool OthersCanRead
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_IROTH) == S_IROTH;
|
|
|
+ }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (value)
|
|
|
+ {
|
|
|
+ Permissions |= S_IROTH;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Permissions &= ~S_IROTH;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets or sets a value indicating whether the others can write into this file.
|
|
|
@@ -281,7 +516,24 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if others can write into this file; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool OthersCanWrite { get; set; }
|
|
|
+ public bool OthersCanWrite
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return (Permissions & S_IWOTH) == S_IWOTH;
|
|
|
+ }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (value)
|
|
|
+ {
|
|
|
+ Permissions |= S_IWOTH;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Permissions &= ~S_IWOTH;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets or sets a value indicating whether the others can execute this file.
|
|
|
@@ -289,209 +541,159 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>
|
|
|
/// <see langword="true"/> if others can execute this file; otherwise, <see langword="false"/>.
|
|
|
/// </value>
|
|
|
- public bool OthersCanExecute { get; set; }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the extensions.
|
|
|
- /// </summary>
|
|
|
- /// <value>
|
|
|
- /// The extensions.
|
|
|
- /// </value>
|
|
|
- public IDictionary<string, string> Extensions { get; private set; }
|
|
|
-
|
|
|
- internal uint Permissions
|
|
|
+ public bool OthersCanExecute
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
- uint permission = 0;
|
|
|
-
|
|
|
- if (_isBitFiledsBitSet)
|
|
|
- {
|
|
|
- permission |= S_IFMT;
|
|
|
- }
|
|
|
-
|
|
|
- if (IsSocket)
|
|
|
- {
|
|
|
- permission |= S_IFSOCK;
|
|
|
- }
|
|
|
-
|
|
|
- if (IsSymbolicLink)
|
|
|
- {
|
|
|
- permission |= S_IFLNK;
|
|
|
- }
|
|
|
-
|
|
|
- if (IsRegularFile)
|
|
|
+ return (Permissions & S_IXOTH) == S_IXOTH;
|
|
|
+ }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (value)
|
|
|
{
|
|
|
- permission |= S_IFREG;
|
|
|
+ Permissions |= S_IXOTH;
|
|
|
}
|
|
|
-
|
|
|
- if (IsBlockDevice)
|
|
|
+ else
|
|
|
{
|
|
|
- permission |= S_IFBLK;
|
|
|
+ Permissions &= ~S_IXOTH;
|
|
|
}
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if (IsDirectory)
|
|
|
- {
|
|
|
- permission |= S_IFDIR;
|
|
|
- }
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the extensions.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>
|
|
|
+ /// The extensions.
|
|
|
+ /// </value>
|
|
|
+ public IDictionary<string, string>? Extensions { get; }
|
|
|
|
|
|
- if (IsCharacterDevice)
|
|
|
- {
|
|
|
- permission |= S_IFCHR;
|
|
|
- }
|
|
|
+ internal uint Permissions { get; private set; }
|
|
|
|
|
|
- if (IsNamedPipe)
|
|
|
- {
|
|
|
- permission |= S_IFIFO;
|
|
|
- }
|
|
|
+ internal SftpFileAttributes(DateTime lastAccessTimeUtc, DateTime lastWriteTimeUtc, long size, int userId, int groupId, uint permissions, Dictionary<string, string>? extensions)
|
|
|
+ {
|
|
|
+ LastAccessTimeUtc = _originalLastAccessTimeUtc = lastAccessTimeUtc;
|
|
|
+ LastWriteTimeUtc = _originalLastWriteTimeUtc = lastWriteTimeUtc;
|
|
|
+ Size = _originalSize = size;
|
|
|
+ UserId = _originalUserId = userId;
|
|
|
+ GroupId = _originalGroupId = groupId;
|
|
|
+ Permissions = _originalPermissions = permissions;
|
|
|
+ Extensions = _originalExtensions = extensions;
|
|
|
+ }
|
|
|
|
|
|
- if (_isUIDBitSet)
|
|
|
- {
|
|
|
- permission |= S_ISUID;
|
|
|
- }
|
|
|
+ /// <summary>
|
|
|
+ /// Sets the POSIX permissions for this file.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="mode">
|
|
|
+ /// The permission mode as an octal number (e.g., <c>755</c>, <c>644</c>, <c>1777</c>).
|
|
|
+ /// </param>
|
|
|
+ /// <exception cref="ArgumentOutOfRangeException">
|
|
|
+ /// <paramref name="mode"/> has more than 4 digits or cannot be interpreted as an octal number.
|
|
|
+ /// </exception>
|
|
|
+ public void SetPermissions(short mode)
|
|
|
+ {
|
|
|
+ var special = (uint)Math.DivRem(mode, 1000, out var userGroupOther);
|
|
|
|
|
|
- if (_isGroupIDBitSet)
|
|
|
- {
|
|
|
- permission |= S_ISGID;
|
|
|
- }
|
|
|
+ var user = (uint)Math.DivRem(userGroupOther, 100, out var groupOther);
|
|
|
|
|
|
- if (_isStickyBitSet)
|
|
|
- {
|
|
|
- permission |= S_ISVTX;
|
|
|
- }
|
|
|
+ var group = (uint)Math.DivRem(groupOther, 10, out var iOther);
|
|
|
|
|
|
- if (OwnerCanRead)
|
|
|
- {
|
|
|
- permission |= S_IRUSR;
|
|
|
- }
|
|
|
+ var other = (uint)iOther;
|
|
|
|
|
|
- if (OwnerCanWrite)
|
|
|
- {
|
|
|
- permission |= S_IWUSR;
|
|
|
- }
|
|
|
+ if ((special & ~7u) != 0 || (user & ~7u) != 0 || (group & ~7u) != 0 || (other & ~7u) != 0)
|
|
|
+ {
|
|
|
+ throw new ArgumentOutOfRangeException(nameof(mode));
|
|
|
+ }
|
|
|
|
|
|
- if (OwnerCanExecute)
|
|
|
- {
|
|
|
- permission |= S_IXUSR;
|
|
|
- }
|
|
|
+ Permissions = (Permissions & ~0xFFFu) | (special << 9) | (user << 6) | (group << 3) | other;
|
|
|
+ }
|
|
|
|
|
|
- if (GroupCanRead)
|
|
|
- {
|
|
|
- permission |= S_IRGRP;
|
|
|
- }
|
|
|
+ /// <inheritdoc/>
|
|
|
+ public override string? ToString()
|
|
|
+ {
|
|
|
+ var sb = new StringBuilder();
|
|
|
|
|
|
- if (GroupCanWrite)
|
|
|
- {
|
|
|
- permission |= S_IWGRP;
|
|
|
- }
|
|
|
+ if (Permissions != default)
|
|
|
+ {
|
|
|
+ AppendPermissionsString(sb);
|
|
|
+ sb.Append(' ');
|
|
|
+ }
|
|
|
|
|
|
- if (GroupCanExecute)
|
|
|
- {
|
|
|
- permission |= S_IXGRP;
|
|
|
- }
|
|
|
+ if (Size != -1)
|
|
|
+ {
|
|
|
+ sb.AppendFormat("Size: {0} ", Size);
|
|
|
+ }
|
|
|
|
|
|
- if (OthersCanRead)
|
|
|
- {
|
|
|
- permission |= S_IROTH;
|
|
|
- }
|
|
|
+ if (LastWriteTime != default)
|
|
|
+ {
|
|
|
+ sb.AppendFormat("LastWriteTime: {0:s} ", LastWriteTime);
|
|
|
+ }
|
|
|
|
|
|
- if (OthersCanWrite)
|
|
|
+ if (sb.Length > 0)
|
|
|
+ {
|
|
|
+ if (sb[sb.Length - 1] == ' ')
|
|
|
{
|
|
|
- permission |= S_IWOTH;
|
|
|
+ sb.Length--;
|
|
|
}
|
|
|
|
|
|
- if (OthersCanExecute)
|
|
|
- {
|
|
|
- permission |= S_IXOTH;
|
|
|
- }
|
|
|
+ Debug.Assert(sb.Length > 0);
|
|
|
+ Debug.Assert(sb[^1] != ' ');
|
|
|
|
|
|
- return permission;
|
|
|
+ return sb.ToString();
|
|
|
}
|
|
|
- private set
|
|
|
- {
|
|
|
- _isBitFiledsBitSet = (value & S_IFMT) == S_IFMT;
|
|
|
-
|
|
|
- IsSocket = (value & S_IFSOCK) == S_IFSOCK;
|
|
|
-
|
|
|
- IsSymbolicLink = (value & S_IFLNK) == S_IFLNK;
|
|
|
-
|
|
|
- IsRegularFile = (value & S_IFREG) == S_IFREG;
|
|
|
-
|
|
|
- IsBlockDevice = (value & S_IFBLK) == S_IFBLK;
|
|
|
-
|
|
|
- IsDirectory = (value & S_IFDIR) == S_IFDIR;
|
|
|
-
|
|
|
- IsCharacterDevice = (value & S_IFCHR) == S_IFCHR;
|
|
|
-
|
|
|
- IsNamedPipe = (value & S_IFIFO) == S_IFIFO;
|
|
|
-
|
|
|
- _isUIDBitSet = (value & S_ISUID) == S_ISUID;
|
|
|
-
|
|
|
- _isGroupIDBitSet = (value & S_ISGID) == S_ISGID;
|
|
|
-
|
|
|
- _isStickyBitSet = (value & S_ISVTX) == S_ISVTX;
|
|
|
-
|
|
|
- OwnerCanRead = (value & S_IRUSR) == S_IRUSR;
|
|
|
-
|
|
|
- OwnerCanWrite = (value & S_IWUSR) == S_IWUSR;
|
|
|
-
|
|
|
- OwnerCanExecute = (value & S_IXUSR) == S_IXUSR;
|
|
|
|
|
|
- GroupCanRead = (value & S_IRGRP) == S_IRGRP;
|
|
|
-
|
|
|
- GroupCanWrite = (value & S_IWGRP) == S_IWGRP;
|
|
|
-
|
|
|
- GroupCanExecute = (value & S_IXGRP) == S_IXGRP;
|
|
|
-
|
|
|
- OthersCanRead = (value & S_IROTH) == S_IROTH;
|
|
|
-
|
|
|
- OthersCanWrite = (value & S_IWOTH) == S_IWOTH;
|
|
|
-
|
|
|
- OthersCanExecute = (value & S_IXOTH) == S_IXOTH;
|
|
|
- }
|
|
|
+ return base.ToString();
|
|
|
}
|
|
|
|
|
|
- private SftpFileAttributes()
|
|
|
+ private void AppendPermissionsString(StringBuilder sb)
|
|
|
{
|
|
|
- }
|
|
|
-
|
|
|
- internal SftpFileAttributes(DateTime lastAccessTimeUtc, DateTime lastWriteTimeUtc, long size, int userId, int groupId, uint permissions, IDictionary<string, string> extensions)
|
|
|
- {
|
|
|
- LastAccessTimeUtc = _originalLastAccessTimeUtc = lastAccessTimeUtc;
|
|
|
- LastWriteTimeUtc = _originalLastWriteTimeUtc = lastWriteTimeUtc;
|
|
|
- Size = _originalSize = size;
|
|
|
- UserId = _originalUserId = userId;
|
|
|
- GroupId = _originalGroupId = groupId;
|
|
|
- Permissions = _originalPermissions = permissions;
|
|
|
- Extensions = _originalExtensions = extensions;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Sets the permissions.
|
|
|
- /// </summary>
|
|
|
- /// <param name="mode">The mode.</param>
|
|
|
- public void SetPermissions(short mode)
|
|
|
- {
|
|
|
- if (mode is < 0 or > 999)
|
|
|
+ // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html
|
|
|
+
|
|
|
+ sb.Append(
|
|
|
+ IsRegularFile ? '-' :
|
|
|
+ IsDirectory ? 'd' :
|
|
|
+ IsSymbolicLink ? 'l' :
|
|
|
+ IsNamedPipe ? 'p' :
|
|
|
+ IsSocket ? 's' :
|
|
|
+ IsCharacterDevice ? 'c' :
|
|
|
+ IsBlockDevice ? 'b' :
|
|
|
+ '-');
|
|
|
+
|
|
|
+ sb.Append(OwnerCanRead ? 'r' : '-');
|
|
|
+ sb.Append(OwnerCanWrite ? 'w' : '-');
|
|
|
+
|
|
|
+ if (OwnerCanExecute)
|
|
|
{
|
|
|
- throw new ArgumentOutOfRangeException(nameof(mode));
|
|
|
+ sb.Append(IsUIDBitSet ? 's' : 'x');
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ sb.Append(IsUIDBitSet ? 'S' : '-');
|
|
|
}
|
|
|
|
|
|
- var modeBytes = mode.ToString(CultureInfo.InvariantCulture).PadLeft(3, '0').ToCharArray();
|
|
|
-
|
|
|
- var permission = ((modeBytes[0] & 0x0F) * 8 * 8) + ((modeBytes[1] & 0x0F) * 8) + (modeBytes[2] & 0x0F);
|
|
|
+ sb.Append(GroupCanRead ? 'r' : '-');
|
|
|
+ sb.Append(GroupCanWrite ? 'w' : '-');
|
|
|
|
|
|
- OwnerCanRead = (permission & S_IRUSR) == S_IRUSR;
|
|
|
- OwnerCanWrite = (permission & S_IWUSR) == S_IWUSR;
|
|
|
- OwnerCanExecute = (permission & S_IXUSR) == S_IXUSR;
|
|
|
+ if (GroupCanExecute)
|
|
|
+ {
|
|
|
+ sb.Append(IsGroupIDBitSet ? 's' : 'x');
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ sb.Append(IsGroupIDBitSet ? 'S' : '-');
|
|
|
+ }
|
|
|
|
|
|
- GroupCanRead = (permission & S_IRGRP) == S_IRGRP;
|
|
|
- GroupCanWrite = (permission & S_IWGRP) == S_IWGRP;
|
|
|
- GroupCanExecute = (permission & S_IXGRP) == S_IXGRP;
|
|
|
+ sb.Append(OthersCanRead ? 'r' : '-');
|
|
|
+ sb.Append(OthersCanWrite ? 'w' : '-');
|
|
|
|
|
|
- OthersCanRead = (permission & S_IROTH) == S_IROTH;
|
|
|
- OthersCanWrite = (permission & S_IWOTH) == S_IWOTH;
|
|
|
- OthersCanExecute = (permission & S_IXOTH) == S_IXOTH;
|
|
|
+ if (OthersCanExecute)
|
|
|
+ {
|
|
|
+ sb.Append(IsStickyBitSet ? 't' : 'x');
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ sb.Append(IsStickyBitSet ? 'T' : '-');
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -506,7 +708,7 @@ namespace Renci.SshNet.Sftp
|
|
|
{
|
|
|
uint flag = 0;
|
|
|
|
|
|
- if (IsSizeChanged && IsRegularFile)
|
|
|
+ if (IsSizeChanged)
|
|
|
{
|
|
|
flag |= 0x00000001;
|
|
|
}
|
|
|
@@ -533,7 +735,7 @@ namespace Renci.SshNet.Sftp
|
|
|
|
|
|
stream.Write(flag);
|
|
|
|
|
|
- if (IsSizeChanged && IsRegularFile)
|
|
|
+ if (IsSizeChanged)
|
|
|
{
|
|
|
stream.Write((ulong)Size);
|
|
|
}
|
|
|
@@ -551,9 +753,9 @@ namespace Renci.SshNet.Sftp
|
|
|
|
|
|
if (IsLastAccessTimeChanged || IsLastWriteTimeChanged)
|
|
|
{
|
|
|
- var time = (uint)((LastAccessTimeUtc.ToFileTimeUtc() / 10000000) - 11644473600);
|
|
|
+ var time = (uint)((DateTimeOffset)DateTime.SpecifyKind(LastAccessTimeUtc, DateTimeKind.Utc)).ToUnixTimeSeconds();
|
|
|
stream.Write(time);
|
|
|
- time = (uint)((LastWriteTimeUtc.ToFileTimeUtc() / 10000000) - 11644473600);
|
|
|
+ time = (uint)((DateTimeOffset)DateTime.SpecifyKind(LastWriteTimeUtc, DateTimeKind.Utc)).ToUnixTimeSeconds();
|
|
|
stream.Write(time);
|
|
|
}
|
|
|
|
|
|
@@ -561,12 +763,8 @@ namespace Renci.SshNet.Sftp
|
|
|
{
|
|
|
foreach (var item in Extensions)
|
|
|
{
|
|
|
- /*
|
|
|
- * TODO: we write as ASCII but read as UTF8 !!!
|
|
|
- */
|
|
|
-
|
|
|
- stream.Write(item.Key, SshData.Ascii);
|
|
|
- stream.Write(item.Value, SshData.Ascii);
|
|
|
+ stream.Write(item.Key, Encoding.UTF8);
|
|
|
+ stream.Write(item.Value, Encoding.UTF8);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -574,8 +772,6 @@ namespace Renci.SshNet.Sftp
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- internal static readonly SftpFileAttributes Empty = new SftpFileAttributes();
|
|
|
-
|
|
|
internal static SftpFileAttributes FromBytes(SshDataStream stream)
|
|
|
{
|
|
|
const uint SSH_FILEXFER_ATTR_SIZE = 0x00000001;
|
|
|
@@ -592,7 +788,7 @@ namespace Renci.SshNet.Sftp
|
|
|
uint permissions = 0;
|
|
|
DateTime accessTime;
|
|
|
DateTime modifyTime;
|
|
|
- Dictionary<string, string> extensions = null;
|
|
|
+ Dictionary<string, string>? extensions = null;
|
|
|
|
|
|
if ((flag & SSH_FILEXFER_ATTR_SIZE) == SSH_FILEXFER_ATTR_SIZE)
|
|
|
{
|
|
|
@@ -613,12 +809,8 @@ namespace Renci.SshNet.Sftp
|
|
|
|
|
|
if ((flag & SSH_FILEXFER_ATTR_ACMODTIME) == SSH_FILEXFER_ATTR_ACMODTIME)
|
|
|
{
|
|
|
- // The incoming times are "Unix times", so they're already in UTC. We need to preserve that
|
|
|
- // to avoid losing information in a local time conversion during the "fall back" hour in DST.
|
|
|
- var time = stream.ReadUInt32();
|
|
|
- accessTime = DateTime.FromFileTimeUtc((time + 11644473600) * 10000000);
|
|
|
- time = stream.ReadUInt32();
|
|
|
- modifyTime = DateTime.FromFileTimeUtc((time + 11644473600) * 10000000);
|
|
|
+ accessTime = DateTimeOffset.FromUnixTimeSeconds(stream.ReadUInt32()).UtcDateTime;
|
|
|
+ modifyTime = DateTimeOffset.FromUnixTimeSeconds(stream.ReadUInt32()).UtcDateTime;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
@@ -632,8 +824,8 @@ namespace Renci.SshNet.Sftp
|
|
|
extensions = new Dictionary<string, string>(extendedCount);
|
|
|
for (var i = 0; i < extendedCount; i++)
|
|
|
{
|
|
|
- var extensionName = stream.ReadString(SshData.Utf8);
|
|
|
- var extensionData = stream.ReadString(SshData.Utf8);
|
|
|
+ var extensionName = stream.ReadString(Encoding.UTF8);
|
|
|
+ var extensionData = stream.ReadString(Encoding.UTF8);
|
|
|
extensions.Add(extensionName, extensionData);
|
|
|
}
|
|
|
}
|