Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f3887fd
Implement SunOS FileSystemWatcher using portfs event ports
gwr Jan 23, 2026
1a096a1
Fix Copilot feedback
gwr Mar 13, 2026
7538ce6
In Cleanup clear watcher._cancellation
gwr Mar 13, 2026
63fcbc7
Selective exception handling in HandleDirectoryEvent
gwr Mar 13, 2026
378566b
Fix inotify comments
gwr Mar 14, 2026
e6d2e86
Selective exception handling in HandleFileEvent
gwr Mar 14, 2026
43b475c
Revert the change to Cleanup for now -- not quite right
gwr Mar 14, 2026
4c7a642
Fix relative paths for rename
gwr Mar 14, 2026
d6a569e
Do PortDissociate before deleting elements from the cookieMap
gwr Mar 14, 2026
00d55c7
Run ProcessEvents with captured execution context
gwr Mar 14, 2026
d1f6886
If HandleDirectoryEvent finds the watched directory gone, terminate
gwr Mar 14, 2026
fe230ca
Do PortAssociate before map insertion etc. in case that throws
gwr Mar 14, 2026
be37d3f
Don't need s_enableDebugLog condition for Debug.WriteLine
gwr Mar 14, 2026
03a3292
Enable leak test helper for SunOS
gwr Mar 14, 2026
a353dad
Avoid duplicate OnError in HandleDirectoryEvent
gwr Mar 14, 2026
5e8b062
Make CancellationCallback handle PortSend error
gwr Mar 14, 2026
bfe13e3
Improve efficiency in AssociateDirectoryContents
gwr Mar 14, 2026
3291f02
Add FILE_NOFOLLOW to PortAssociate calls
gwr Mar 14, 2026
1da2f50
Factor out AssociateNode
gwr Mar 16, 2026
1884b5c
Use try/catch some more places
gwr Mar 16, 2026
f68a021
Silence EBADF in ProcessEvents, and fix some comments
gwr Mar 16, 2026
078a3b7
More efficient CompareSnapshotsAndNotify
gwr Mar 17, 2026
927aa87
fix record-syle updats
gwr Mar 17, 2026
493bd15
Do (re)AssociateNode in finally block for relability
gwr Mar 17, 2026
c3307ba
Add design comments to CompareSnapshotsAndNotify
gwr Mar 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions src/libraries/Common/src/Interop/SunOS/portfs/Interop.portfs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// This implementation uses SunOS portfs (event ports) to watch directories.
// portfs can detect when a directory's modification time changes via port_associate,
// but cannot tell us WHAT changed in the directory. This makes it different from
// Linux inotify or Windows ReadDirectoryChangesW.
//
// The FileSystemWatcher implementation must:
// 1. Use port_associate to watch for FILE_MODIFIED events on directories
// 2. When an event occurs, re-read the directory contents
// 3. Compare with cached state to determine what actually changed
//

using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

internal static partial class Interop
{
internal static partial class PortFs
{
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PortCreate",
SetLastError = true)]
internal static partial SafeFileHandle PortCreate();

// pFileObj must point to pinned memory with size >= sizeof(file_obj)
// because the address is used as an identifier in the kernel.
// dirPath string is used while making the association but is
// never referenced again after PortAssociate returns.
// mtime is the directory modification time before reading the directory
// cookie is returned by PortGet to identify which directory changed
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PortAssociate",
SetLastError = true, StringMarshalling = StringMarshalling.Utf8)]
internal static unsafe partial int PortAssociate(SafeFileHandle fd, IntPtr pFileObj, string dirPath, Interop.Sys.TimeSpec* mtime, int evmask, nuint cookie);

// Returns the cookie value from PortAssociate in the cookie parameter
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PortGet",
SetLastError = true)]
internal static unsafe partial int PortGet(SafeFileHandle fd, int* events, nuint* cookie, Interop.Sys.TimeSpec* tmo);

[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PortDissociate",
SetLastError = true)]
internal static partial int PortDissociate(SafeFileHandle fd, IntPtr pFileObj);

// Send a synthetic event to wake up a blocked PortGet call
// evflags can be any PortEvent value (e.g., FILE_NOFOLLOW for cancellation)
// cookie is returned to PortGet to identify the event
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PortSend",
SetLastError = true)]
internal static partial int PortSend(SafeFileHandle fd, int evflags, nuint cookie);

[Flags]
internal enum PortEvent
{
FILE_ACCESS = 0x00000001,
FILE_MODIFIED = 0x00000002,
FILE_ATTRIB = 0x00000004,
FILE_TRUNC = 0x00100000,
FILE_NOFOLLOW = 0x10000000,
FILE_EXCEPTION = 0x20000000,

// Exception events
FILE_DELETE = 0x00000010,
FILE_RENAME_TO = 0x00000020,
FILE_RENAME_FROM = 0x00000040,
// These are unused, and the duplicate value causes warnings.
// UNMOUNTED = 0x20000000,
// MOUNTEDOVER = 0x40000000,
}

// sizeof(file_obj) = 3*sizeof(timespec) + 3*sizeof(uintptr_t) + sizeof(char*)
// On 64-bit: 3*16 + 3*8 + 8 = 80 bytes
internal const int FileObjSize = 80;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)</TargetFrameworks>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-illumos;$(NetCoreAppCurrent)-solaris;$(NetCoreAppCurrent)</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseCompilerGeneratedDocXmlFile>false</UseCompilerGeneratedDocXmlFile>
</PropertyGroup>
Expand Down Expand Up @@ -89,6 +89,22 @@
Link="Common\Interop\Unix\Interop.Stat.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'illumos' or '$(TargetPlatformIdentifier)' == 'solaris'">
<Compile Include="System\IO\FileSystemWatcher.SunOS.cs" />
<Compile Include="$(CommonPath)Interop\SunOS\portfs\Interop.portfs.cs"
Link="Common\Interop\SunOS\portfs\Interop.portfs.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.RealPath.cs"
Link="Common\Interop\Unix\Interop.RealPath.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.UTimensat.cs"
Link="Common\Interop\Unix\System.Native\Interop.UTimensat.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Poll.cs"
Link="Common\Interop\Unix\Interop.Poll.cs" />
<Compile Include="$(CommonPath)Interop\Unix\Interop.Poll.Structs.cs"
Link="Common\Interop\Unix\Interop.Poll.Structs.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs"
Link="Common\Interop\Unix\Interop.Stat.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'osx' or '$(TargetPlatformIdentifier)' == 'maccatalyst'">
<Compile Include="System\IO\FileSystemWatcher.OSX.cs" />
<Compile Include="$(CoreLibSharedDir)System\IO\FileSystem.Exists.Unix.cs"
Expand Down
Loading
Loading