Skip to content

Commit a4f6aad

Browse files
committed
Added support for extended change notifications.
1 parent 655c3bb commit a4f6aad

4 files changed

Lines changed: 252 additions & 21 deletions

File tree

NtApiDotNet/NtFile.cs

Lines changed: 162 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,29 @@ private static IEnumerable<DirectoryChangeNotification> ReadNotifications(SafeHG
370370
{
371371
var info = buffer.GetStructAtOffset<FileNotifyInformation>(offset);
372372
var result = info.Result;
373-
ns.Add(new DirectoryChangeNotification(result.Action, info.Data.ReadUnicodeString(result.FileNameLength / 2)));
373+
ns.Add(new DirectoryChangeNotification(info));
374+
if (result.NextEntryOffset == 0)
375+
{
376+
break;
377+
}
378+
offset += result.NextEntryOffset;
379+
}
380+
return ns.AsReadOnly();
381+
}
382+
383+
private static IEnumerable<DirectoryChangeNotificationExtended> ReadExtendedNotifications(SafeHGlobalBuffer buffer, IoStatus status)
384+
{
385+
List<DirectoryChangeNotificationExtended> ns = new List<DirectoryChangeNotificationExtended>();
386+
387+
// Change buffer size to reflect what's in the buffer.
388+
buffer.Initialize((uint)status.Information32);
389+
390+
int offset = 0;
391+
while (offset < buffer.Length)
392+
{
393+
var info = buffer.GetStructAtOffset<FileNotifyExtendedInformation>(offset);
394+
var result = info.Result;
395+
ns.Add(new DirectoryChangeNotificationExtended(info));
374396
if (result.NextEntryOffset == 0)
375397
{
376398
break;
@@ -2788,8 +2810,9 @@ public void CancelIo()
27882810
/// <summary>
27892811
/// Get the extended attributes of a file.
27902812
/// </summary>
2813+
/// <param name="throw_on_error">True to throw on error.</param>
27912814
/// <returns>The extended attributes, empty if no extended attributes.</returns>
2792-
public EaBuffer GetEa()
2815+
public NtResult<EaBuffer> GetEa(bool throw_on_error)
27932816
{
27942817
int ea_size = 1024;
27952818
while (true)
@@ -2804,30 +2827,51 @@ public EaBuffer GetEa()
28042827
}
28052828
else if (status.IsSuccess())
28062829
{
2807-
return new EaBuffer(buffer);
2830+
return new EaBuffer(buffer).CreateResult();
28082831
}
28092832
else if (status == NtStatus.STATUS_NO_EAS_ON_FILE)
28102833
{
2811-
return new EaBuffer();
2834+
return new EaBuffer().CreateResult();
28122835
}
28132836
else
28142837
{
2815-
throw new NtException(status);
2838+
return status.CreateResultFromError<EaBuffer>(throw_on_error);
28162839
}
28172840
}
28182841
}
28192842

2843+
/// <summary>
2844+
/// Get the extended attributes of a file.
2845+
/// </summary>
2846+
/// <returns>The extended attributes, empty if no extended attributes.</returns>
2847+
public EaBuffer GetEa()
2848+
{
2849+
return GetEa(true).Result;
2850+
}
2851+
28202852
/// <summary>
28212853
/// Set the extended attributes for a file.
28222854
/// </summary>
28232855
/// <param name="ea">The EA buffer to set.</param>
2856+
/// <param name="throw_on_error">True to throw on error.</param>
28242857
/// <remarks>This will add entries if they no longer exist,
28252858
/// remove entries if the data is empty or update existing entires.</remarks>
2826-
public void SetEa(EaBuffer ea)
2859+
public NtStatus SetEa(EaBuffer ea, bool throw_on_error)
28272860
{
28282861
byte[] ea_buffer = ea.ToByteArray();
28292862
IoStatus io_status = new IoStatus();
2830-
NtSystemCalls.NtSetEaFile(Handle, io_status, ea_buffer, ea_buffer.Length).ToNtException();
2863+
return NtSystemCalls.NtSetEaFile(Handle, io_status, ea_buffer, ea_buffer.Length).ToNtException(throw_on_error);
2864+
}
2865+
2866+
/// <summary>
2867+
/// Set the extended attributes for a file.
2868+
/// </summary>
2869+
/// <param name="ea">The EA buffer to set.</param>
2870+
/// <remarks>This will add entries if they no longer exist,
2871+
/// remove entries if the data is empty or update existing entires.</remarks>
2872+
public void SetEa(EaBuffer ea)
2873+
{
2874+
SetEa(ea, true);
28312875
}
28322876

28332877
/// <summary>
@@ -2900,13 +2944,22 @@ public bool IsAccessGranted(FileDirectoryAccessRights access)
29002944
return IsAccessMaskGranted(access);
29012945
}
29022946

2947+
/// <summary>
2948+
/// Get the cached signing level for a file.
2949+
/// </summary>
2950+
/// <returns>The cached signing level.</returns>
2951+
public NtResult<CachedSigningLevel> GetCachedSigningLevel(bool throw_on_error)
2952+
{
2953+
return NtSecurity.GetCachedSigningLevel(Handle, throw_on_error);
2954+
}
2955+
29032956
/// <summary>
29042957
/// Get the cached signing level for a file.
29052958
/// </summary>
29062959
/// <returns>The cached signing level.</returns>
29072960
public CachedSigningLevel GetCachedSigningLevel()
29082961
{
2909-
return NtSecurity.GetCachedSigningLevel(Handle);
2962+
return GetCachedSigningLevel(true).Result;
29102963
}
29112964

29122965
/// <summary>
@@ -3356,14 +3409,114 @@ public async Task<IEnumerable<DirectoryChangeNotification>> GetChangeNotificatio
33563409
/// </summary>
33573410
/// <param name="completion_filter">The filter of events to watch for.</param>
33583411
/// <param name="watch_subtree">True to watch all sub directories.</param>
3359-
/// <param name="token">Cancellation token.</param>
33603412
/// <returns>The list of changes.</returns>
33613413
public Task<IEnumerable<DirectoryChangeNotification>> GetChangeNotificationAsync(
33623414
DirectoryChangeNotifyFilter completion_filter, bool watch_subtree)
33633415
{
33643416
return GetChangeNotificationAsync(completion_filter, watch_subtree, CancellationToken.None);
33653417
}
33663418

3419+
3420+
/// <summary>
3421+
/// Get extended change notifications.
3422+
/// </summary>
3423+
/// <param name="completion_filter">The filter of events to watch for.</param>
3424+
/// <param name="watch_subtree">True to watch all sub directories.</param>
3425+
/// <param name="throw_on_error">True to throw on error.</param>
3426+
/// <returns>The list of changes.</returns>
3427+
[SupportedVersion(SupportedVersion.Windows10_RS3)]
3428+
public NtResult<IEnumerable<DirectoryChangeNotificationExtended>> GetChangeNotificationEx(
3429+
DirectoryChangeNotifyFilter completion_filter, bool watch_subtree, bool throw_on_error)
3430+
{
3431+
using (NtAsyncResult result = new NtAsyncResult(this))
3432+
{
3433+
using (var buffer = new SafeHGlobalBuffer(8192))
3434+
{
3435+
return result.CompleteCall(NtSystemCalls.NtNotifyChangeDirectoryFileEx(
3436+
Handle, result.EventHandle, IntPtr.Zero, IntPtr.Zero, result.IoStatusBuffer,
3437+
buffer, buffer.Length, completion_filter, watch_subtree,
3438+
DirectoryNotifyInformationClass.DirectoryNotifyExtendedInformation))
3439+
.CreateResult(throw_on_error, () => ReadExtendedNotifications(buffer, result.IoStatusBuffer.Result));
3440+
}
3441+
}
3442+
}
3443+
3444+
/// <summary>
3445+
/// Get change notifications.
3446+
/// </summary>
3447+
/// <param name="completion_filter">The filter of events to watch for.</param>
3448+
/// <param name="watch_subtree">True to watch all sub directories.</param>
3449+
/// <returns>The list of changes.</returns>
3450+
[SupportedVersion(SupportedVersion.Windows10_RS3)]
3451+
public IEnumerable<DirectoryChangeNotificationExtended> GetChangeNotificationEx(DirectoryChangeNotifyFilter completion_filter, bool watch_subtree)
3452+
{
3453+
return GetChangeNotificationEx(completion_filter, watch_subtree, true).Result;
3454+
}
3455+
3456+
/// <summary>
3457+
/// Get change notifications.
3458+
/// </summary>
3459+
/// <param name="completion_filter">The filter of events to watch for.</param>
3460+
/// <param name="watch_subtree">True to watch all sub directories.</param>
3461+
/// <param name="throw_on_error">True to throw on error.</param>
3462+
/// <param name="token">Cancellation token.</param>
3463+
/// <returns>The list of changes.</returns>
3464+
[SupportedVersion(SupportedVersion.Windows10_RS3)]
3465+
public async Task<NtResult<IEnumerable<DirectoryChangeNotificationExtended>>> GetChangeNotificationExAsync(
3466+
DirectoryChangeNotifyFilter completion_filter, bool watch_subtree, CancellationToken token, bool throw_on_error)
3467+
{
3468+
using (var buffer = new SafeHGlobalBuffer(8192))
3469+
{
3470+
var status = await RunFileCallAsync(result => NtSystemCalls.NtNotifyChangeDirectoryFileEx(
3471+
Handle, result.EventHandle, IntPtr.Zero, IntPtr.Zero, result.IoStatusBuffer,
3472+
buffer, buffer.Length, completion_filter, watch_subtree,
3473+
DirectoryNotifyInformationClass.DirectoryNotifyExtendedInformation), token, throw_on_error);
3474+
return status.Map(r => ReadExtendedNotifications(buffer, r));
3475+
}
3476+
}
3477+
3478+
/// <summary>
3479+
/// Get change notifications.
3480+
/// </summary>
3481+
/// <param name="completion_filter">The filter of events to watch for.</param>
3482+
/// <param name="watch_subtree">True to watch all sub directories.</param>
3483+
/// <param name="throw_on_error">True to throw on error.</param>
3484+
/// <returns>The list of changes.</returns>
3485+
[SupportedVersion(SupportedVersion.Windows10_RS3)]
3486+
public Task<NtResult<IEnumerable<DirectoryChangeNotificationExtended>>> GetChangeNotificationExAsync(
3487+
DirectoryChangeNotifyFilter completion_filter, bool watch_subtree, bool throw_on_error)
3488+
{
3489+
return GetChangeNotificationExAsync(completion_filter, watch_subtree, CancellationToken.None, throw_on_error);
3490+
}
3491+
3492+
/// <summary>
3493+
/// Get change notifications.
3494+
/// </summary>
3495+
/// <param name="completion_filter">The filter of events to watch for.</param>
3496+
/// <param name="watch_subtree">True to watch all sub directories.</param>
3497+
/// <param name="token">Cancellation token.</param>
3498+
/// <returns>The list of changes.</returns>
3499+
[SupportedVersion(SupportedVersion.Windows10_RS3)]
3500+
public async Task<IEnumerable<DirectoryChangeNotificationExtended>> GetChangeNotificationExAsync(
3501+
DirectoryChangeNotifyFilter completion_filter, bool watch_subtree, CancellationToken token)
3502+
{
3503+
var result = await GetChangeNotificationExAsync(completion_filter, watch_subtree, token, true);
3504+
return result.Result;
3505+
}
3506+
3507+
/// <summary>
3508+
/// Get change notifications.
3509+
/// </summary>
3510+
/// <param name="completion_filter">The filter of events to watch for.</param>
3511+
/// <param name="watch_subtree">True to watch all sub directories.</param>
3512+
/// <returns>The list of changes.</returns>
3513+
[SupportedVersion(SupportedVersion.Windows10_RS3)]
3514+
public Task<IEnumerable<DirectoryChangeNotificationExtended>> GetChangeNotificationAsyncEx(
3515+
DirectoryChangeNotifyFilter completion_filter, bool watch_subtree)
3516+
{
3517+
return GetChangeNotificationExAsync(completion_filter, watch_subtree, CancellationToken.None);
3518+
}
3519+
33673520
/// <summary>
33683521
/// Method to query information for this object type.
33693522
/// </summary>

NtApiDotNet/NtFileNative.cs

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,20 @@ public static extern NtStatus NtNotifyChangeDirectoryFile(
267267
DirectoryChangeNotifyFilter CompletionFilter,
268268
bool WatchTree
269269
);
270+
271+
[DllImport("ntdll.dll")]
272+
public static extern NtStatus NtNotifyChangeDirectoryFileEx(
273+
SafeKernelObjectHandle FileHandle,
274+
SafeKernelObjectHandle Event,
275+
IntPtr ApcRoutine,
276+
IntPtr ApcContext,
277+
SafeIoStatusBuffer IoStatusBlock,
278+
SafeBuffer Buffer,
279+
int BufferSize,
280+
DirectoryChangeNotifyFilter CompletionFilter,
281+
bool WatchTree,
282+
DirectoryNotifyInformationClass DirectoryNotifyInformationClass
283+
);
270284
}
271285

272286
public static partial class NtRtl
@@ -1450,7 +1464,7 @@ public enum FileNotificationAction
14501464
RenamedNewName = 5,
14511465
}
14521466

1453-
[StructLayout(LayoutKind.Sequential), DataStart("FileName")]
1467+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode), DataStart("FileName")]
14541468
public struct FileNotifyInformation
14551469
{
14561470
public int NextEntryOffset;
@@ -1459,17 +1473,76 @@ public struct FileNotifyInformation
14591473
public char FileName;
14601474
}
14611475

1462-
public class DirectoryChangeNotification
1476+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode), DataStart("FileName")]
1477+
public struct FileNotifyExtendedInformation
1478+
{
1479+
public int NextEntryOffset;
1480+
public FileNotificationAction Action;
1481+
public LargeIntegerStruct CreationTime;
1482+
public LargeIntegerStruct LastModificationTime;
1483+
public LargeIntegerStruct LastChangeTime;
1484+
public LargeIntegerStruct LastAccessTime;
1485+
public LargeIntegerStruct AllocatedLength;
1486+
public LargeIntegerStruct FileSize;
1487+
public FileAttributes FileAttributes;
1488+
public int ReparsePointTag;
1489+
public LargeIntegerStruct FileId;
1490+
public LargeIntegerStruct ParentFileId;
1491+
public int FileNameLength;
1492+
public char FileName;
1493+
}
1494+
1495+
public sealed class DirectoryChangeNotification
14631496
{
14641497
public FileNotificationAction Action { get; }
14651498
public string FileName { get; }
14661499

1467-
internal DirectoryChangeNotification(FileNotificationAction action, string file_name)
1500+
internal DirectoryChangeNotification(SafeStructureInOutBuffer<FileNotifyInformation> buffer)
14681501
{
1469-
Action = action;
1470-
FileName = file_name;
1502+
var info = buffer.Result;
1503+
Action = info.Action;
1504+
FileName = buffer.Data.ReadUnicodeString(info.FileNameLength / 2);
14711505
}
14721506
}
14731507

1508+
public sealed class DirectoryChangeNotificationExtended
1509+
{
1510+
public FileNotificationAction Action { get; }
1511+
public string FileName { get; }
1512+
public DateTime CreationTime { get; }
1513+
public DateTime LastModificationTime { get; }
1514+
public DateTime LastChangeTime { get; }
1515+
public DateTime LastAccessTime { get; }
1516+
public long AllocatedLength { get; }
1517+
public long FileSize { get; }
1518+
public FileAttributes FileAttributes { get; }
1519+
public ReparseTag ReparsePointTag { get; }
1520+
public long FileId { get; }
1521+
public long ParentFileId { get; }
1522+
1523+
internal DirectoryChangeNotificationExtended(SafeStructureInOutBuffer<FileNotifyExtendedInformation> buffer)
1524+
{
1525+
var info = buffer.Result;
1526+
Action = info.Action;
1527+
CreationTime = info.CreationTime.ToDateTime();
1528+
LastModificationTime = info.LastModificationTime.ToDateTime();
1529+
LastChangeTime = info.LastChangeTime.ToDateTime();
1530+
LastAccessTime = info.LastAccessTime.ToDateTime();
1531+
AllocatedLength = info.AllocatedLength.QuadPart;
1532+
FileSize = info.FileSize.QuadPart;
1533+
FileAttributes = info.FileAttributes;
1534+
ReparsePointTag = (ReparseTag)info.ReparsePointTag;
1535+
FileId = info.FileId.QuadPart;
1536+
ParentFileId = info.ParentFileId.QuadPart;
1537+
FileName = buffer.Data.ReadUnicodeString(info.FileNameLength / 2);
1538+
}
1539+
}
1540+
1541+
public enum DirectoryNotifyInformationClass
1542+
{
1543+
DirectoryNotifyInformation = 1,
1544+
DirectoryNotifyExtendedInformation = 2
1545+
}
1546+
14741547
#pragma warning restore 1591
14751548
}

NtApiDotNet/NtSecurity.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -893,13 +893,7 @@ public static byte[] StringToConditionalAce(string condition_sddl)
893893
/// <returns>The cached signing level.</returns>
894894
public static CachedSigningLevel GetCachedSigningLevel(SafeKernelObjectHandle handle)
895895
{
896-
byte[] thumb_print = new byte[0x68];
897-
int thumb_print_size = thumb_print.Length;
898-
899-
NtSystemCalls.NtGetCachedSigningLevel(handle, out int flags,
900-
out SigningLevel signing_level, thumb_print, ref thumb_print_size, out HashAlgorithm thumb_print_algo).ToNtException();
901-
Array.Resize(ref thumb_print, thumb_print_size);
902-
return new CachedSigningLevel(flags, signing_level, thumb_print, thumb_print_algo);
896+
return GetCachedSigningLevel(handle, true).Result;
903897
}
904898

905899
/// <summary>

NtApiDotNet/NtStructures.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
using System;
1516
using System.Runtime.InteropServices;
1617

1718
namespace NtApiDotNet
@@ -35,6 +36,11 @@ public LargeInteger(long value)
3536
{
3637
QuadPart = value;
3738
}
39+
40+
internal DateTime ToDateTime()
41+
{
42+
return DateTime.FromFileTime(QuadPart);
43+
}
3844
}
3945

4046
[StructLayout(LayoutKind.Explicit)]
@@ -46,6 +52,11 @@ public struct LargeIntegerStruct
4652
public int HighPart;
4753
[FieldOffset(0)]
4854
public long QuadPart;
55+
56+
internal DateTime ToDateTime()
57+
{
58+
return DateTime.FromFileTime(QuadPart);
59+
}
4960
}
5061
#pragma warning restore 1591
5162
}

0 commit comments

Comments
 (0)