| | 1 | | using System; |
| | 2 | | using System.Collections.Generic; |
| | 3 | | using System.ComponentModel; |
| | 4 | | using System.Diagnostics; |
| | 5 | | using System.Diagnostics.CodeAnalysis; |
| | 6 | | using System.IO; |
| | 7 | | using System.Linq; |
| | 8 | | using System.Runtime.CompilerServices; |
| | 9 | | using System.Runtime.InteropServices; |
| | 10 | | using System.Security.Principal; |
| | 11 | | using System.Threading; |
| | 12 | |
|
| | 13 | | using static LockCheck.Windows.NativeMethods; |
| | 14 | |
|
| | 15 | | namespace LockCheck.Windows; |
| | 16 | |
|
| | 17 | | internal static class NtDll |
| | 18 | | { |
| | 19 | | internal class PseudoPeb |
| | 20 | | { |
| 2 | 21 | | public PseudoPeb(SYSTEM_PROCESS_INFORMATION pi, string? executable, string? systemAccount, string? processName = |
| | 22 | | { |
| 2 | 23 | | ProcessId = pi.UniqueProcessId.ToInt32(); |
| 2 | 24 | | ParentProcessId = pi.InheritedFromUniqueProcessId.ToInt32(); |
| 2 | 25 | | ExecutableFullPath = executable; |
| 2 | 26 | | ProcessName = pi.NamePtr != IntPtr.Zero ? Marshal.PtrToStringUni(pi.NamePtr) : processName; |
| 2 | 27 | | Owner = systemAccount; |
| 2 | 28 | | ProcessSequenceNumber = processSequenceNumber; |
| 2 | 29 | | StartTime = DateTime.FromFileTime(pi.CreateTime); |
| 2 | 30 | | } |
| | 31 | |
|
| 2 | 32 | | public int ProcessId { get; private set; } |
| 2 | 33 | | public int? ParentProcessId { get; private set; } |
| 2 | 34 | | public string? ProcessName { get; private set; } |
| 2 | 35 | | public string? ExecutableFullPath { get; private set; } |
| 2 | 36 | | public string? Owner { get; private set; } |
| 2 | 37 | | public ulong? ProcessSequenceNumber { get; } |
| 2 | 38 | | public DateTime StartTime { get; private set; } |
| 2 | 39 | | public int SessionId => 0; |
| 2 | 40 | | public bool? IsCritical => true; |
| 2 | 41 | | public bool IsPseudoProcess => true; |
| | 42 | | } |
| | 43 | |
|
| | 44 | | // Pseudo processes never change during w/o rebooting. So we can cache them up front. |
| 2 | 45 | | private static readonly Lazy<Dictionary<int, PseudoPeb>> s_systemPseudoProcesses = new(() => |
| 2 | 46 | | { |
| 2 | 47 | | string? systemAccount = GetSystemAccountName(); |
| 2 | 48 | | var result = new Dictionary<int, PseudoPeb>(); |
| 2 | 49 | |
|
| 2 | 50 | | EnumerateSystemProcesses(null, result, (res, _, pi, processSequenceNumber) => |
| 2 | 51 | | { |
| 2 | 52 | | if ((int)pi.UniqueProcessId == 0) |
| 2 | 53 | | { |
| 2 | 54 | | // "System Idle Process" always PID 0, does not have a name, even in SYSTEM_PROCESS_INFORMATION.NamePtr |
| 2 | 55 | | res![0] = new PseudoPeb(pi, null, systemAccount, "System Idle Process", processSequenceNumber); |
| 2 | 56 | | } |
| 2 | 57 | | else if (pi.NamePtr != IntPtr.Zero) |
| 2 | 58 | | { |
| 2 | 59 | | string? name = Marshal.PtrToStringUni(pi.NamePtr); |
| 2 | 60 | | if (name != null && IsSystemPseudoProcessByName(name, out var executable)) |
| 2 | 61 | | { |
| 2 | 62 | | var pseudoPeb = new PseudoPeb(pi, executable, systemAccount, processSequenceNumber: processSequenceN |
| 2 | 63 | | res![pseudoPeb.ProcessId] = pseudoPeb; |
| 2 | 64 | | } |
| 2 | 65 | | } |
| 2 | 66 | | return 0; |
| 2 | 67 | | }); |
| 2 | 68 | |
|
| 2 | 69 | | return result; |
| 2 | 70 | | }, LazyThreadSafetyMode.ExecutionAndPublication); |
| | 71 | |
|
| | 72 | | private static bool IsSystemPseudoProcessByName(string? processName, out string? executablePath) |
| | 73 | | { |
| 2 | 74 | | executablePath = null; |
| 2 | 75 | | if (processName != null) |
| | 76 | | { |
| | 77 | | switch (processName) |
| | 78 | | { |
| | 79 | | case "System Idle Process": |
| | 80 | | // Process Explorer and others also return no executable name here, |
| | 81 | | // even though this *is* a pseudo process. Technically, we should |
| | 82 | | // never get here, because this process (PID 0) doesn't have a name |
| | 83 | | // set in SYSTEM_PROCESS_INFORMATION.NamePtr, but we're playing it |
| | 84 | | // safe. |
| 0 | 85 | | return true; |
| | 86 | | case "System": |
| | 87 | | case "Secure System": |
| | 88 | | case "Registry": |
| | 89 | | case "Memory Compression": |
| | 90 | | // Regardless of whether the current process is WOW64, 64 bit or 32 bit, always return |
| | 91 | | // the "actual" system directory here. This is compatible to what NativeMethods.GetProcessImagePath( |
| | 92 | | // does. |
| 2 | 93 | | executablePath = Environment.ExpandEnvironmentVariables("%windir%\\System32\\ntoskrnl.exe"); |
| 2 | 94 | | return true; |
| | 95 | | } |
| | 96 | | } |
| | 97 | |
|
| 2 | 98 | | return false; |
| | 99 | | } |
| | 100 | |
|
| | 101 | | private static string? GetSystemAccountName() |
| | 102 | | { |
| | 103 | | try |
| | 104 | | { |
| 2 | 105 | | var sid = new SecurityIdentifier("S-1-5-18"); |
| 2 | 106 | | return sid.Translate(typeof(NTAccount)).Value; |
| | 107 | | } |
| 0 | 108 | | catch |
| | 109 | | { |
| 0 | 110 | | } |
| | 111 | |
|
| 0 | 112 | | return null; |
| 2 | 113 | | } |
| | 114 | |
|
| | 115 | | internal static bool TryGetSystemPseudoProcess(int processId, [NotNullWhen(true)] out PseudoPeb? info) |
| 2 | 116 | | => s_systemPseudoProcesses.Value.TryGetValue(processId, out info); |
| | 117 | |
|
| | 118 | | public static HashSet<IWin32ProcessDetails> GetAllProcesses() |
| | 119 | | { |
| 0 | 120 | | return EnumerateSystemProcesses(null, (object?)null, |
| 0 | 121 | | static (_, idx, pi, seq) => (IWin32ProcessDetails)new Peb(pi, seq)) |
| 0 | 122 | | .Select(v => v.Value) |
| 0 | 123 | | .ToHashSet(); |
| | 124 | | } |
| | 125 | |
|
| | 126 | | public static HashSet<ProcessInfo> GetLockingProcessInfos(string[] paths, [NotNullIfNotNull(nameof(directories))] re |
| | 127 | | { |
| 2 | 128 | | if (paths == null) |
| | 129 | | { |
| 2 | 130 | | throw new ArgumentNullException(nameof(paths)); |
| | 131 | | } |
| | 132 | |
|
| 2 | 133 | | var result = new HashSet<ProcessInfo>(); |
| | 134 | |
|
| 2 | 135 | | foreach (string path in paths) |
| | 136 | | { |
| 2 | 137 | | if (Directory.Exists(path)) |
| | 138 | | { |
| 2 | 139 | | directories?.Add(path); |
| 2 | 140 | | continue; |
| | 141 | | } |
| | 142 | |
|
| 2 | 143 | | GetLockingProcessInfo(path, result); |
| | 144 | | } |
| | 145 | |
|
| 2 | 146 | | return result; |
| | 147 | | } |
| | 148 | |
|
| | 149 | | private static void GetLockingProcessInfo(string path, HashSet<ProcessInfo> result) |
| | 150 | | { |
| 2 | 151 | | if (path == null) |
| | 152 | | { |
| 2 | 153 | | throw new ArgumentNullException(nameof(path)); |
| | 154 | | } |
| | 155 | |
|
| 2 | 156 | | if (result == null) |
| | 157 | | { |
| 0 | 158 | | throw new ArgumentNullException(nameof(result)); |
| | 159 | | } |
| | 160 | |
|
| 2 | 161 | | var statusBlock = new IO_STATUS_BLOCK(); |
| | 162 | |
|
| 2 | 163 | | using (var handle = GetFileHandle(path)) |
| | 164 | | { |
| 2 | 165 | | if (handle.IsInvalid) |
| | 166 | | { |
| | 167 | | // The file does not exist or is gone already. Could be a race condition. There is nothing we can contri |
| | 168 | | // Doing this, exhibits the same behavior as the RestartManager implementation. |
| 0 | 169 | | return; |
| | 170 | | } |
| | 171 | |
|
| | 172 | | // The resulting FILE_PROCESS_IDS_USING_FILE_INFORMATION structure is rather small (on 64 bit Windows, |
| | 173 | | // 12 byte + padding). Additionally, we assume that there aren't a whole lot of processes locking the |
| | 174 | | // same path. Thus choosing our initial buffer size rather conservative for about 8 processes. |
| 2 | 175 | | int bufferSize = (IntPtr.Size + sizeof(int)) * 8; |
| | 176 | | #if NET |
| | 177 | | using (var mem = new ScopedNativeMemory(stackalloc byte[bufferSize])) |
| | 178 | | #else |
| 2 | 179 | | using (var mem = new ScopedNativeMemory(bufferSize)) |
| | 180 | | #endif |
| | 181 | | { |
| | 182 | | uint status; |
| 2 | 183 | | while ((status = NtQueryInformationFile(handle, ref statusBlock, (IntPtr)mem, (uint)mem.Size, |
| 2 | 184 | | FILE_INFORMATION_CLASS.FileProcessIdsUsingFileInformation)) == STATUS_INFO_LENGTH_MISMATCH) |
| | 185 | | { |
| 0 | 186 | | mem.Resize(mem.Size * 2); |
| | 187 | | } |
| | 188 | |
|
| 2 | 189 | | if (status != STATUS_SUCCESS) |
| | 190 | | { |
| 0 | 191 | | throw GetException(status); |
| | 192 | | } |
| | 193 | |
|
| | 194 | | // Buffer contains: |
| | 195 | | // struct FILE_PROCESS_IDS_USING_FILE_INFORMATION |
| | 196 | | // { |
| | 197 | | // ULONG NumberOfProcessIdsInList; |
| | 198 | | // ULONG_PTR ProcessIdList[1]; |
| | 199 | | // } |
| | 200 | |
|
| 2 | 201 | | IntPtr readBuffer = (IntPtr)mem; |
| 2 | 202 | | int numEntries = Marshal.ReadInt32(readBuffer); // NumberOfProcessIdsInList |
| 2 | 203 | | readBuffer += IntPtr.Size; |
| | 204 | |
|
| 2 | 205 | | for (int i = 0; i < numEntries; i++) |
| | 206 | | { |
| 2 | 207 | | int processId = Marshal.ReadIntPtr(readBuffer).ToInt32(); // A single ProcessIdList[] element |
| 2 | 208 | | var entry = ProcessInfoWindows.Create(processId); |
| 2 | 209 | | if (entry != null) |
| | 210 | | { |
| 2 | 211 | | result.Add(entry); |
| | 212 | | } |
| 2 | 213 | | readBuffer += IntPtr.Size; |
| | 214 | | } |
| 2 | 215 | | } |
| | 216 | | } |
| 2 | 217 | | } |
| | 218 | |
|
| | 219 | | internal static Win32Exception GetException(uint status) |
| | 220 | | { |
| 0 | 221 | | int res = RtlNtStatusToDosError(status); |
| 0 | 222 | | return new Win32Exception(res, GetMessage(res)); |
| | 223 | | } |
| | 224 | |
|
| | 225 | | internal static Dictionary<(int, DateTime), ProcessInfo> GetProcessesByWorkingDirectory(List<string> directories) |
| | 226 | | { |
| 2 | 227 | | if (directories == null) |
| | 228 | | { |
| 0 | 229 | | throw new ArgumentNullException(nameof(directories)); |
| | 230 | | } |
| | 231 | |
|
| 2 | 232 | | return EnumerateSystemProcesses(null, directories, |
| 2 | 233 | | static (dirs, idx, pi, seq) => |
| 2 | 234 | | { |
| 2 | 235 | | var peb = new Peb(pi, seq); |
| 2 | 236 | |
|
| 2 | 237 | | if (!peb.HasError && !string.IsNullOrEmpty(peb.CurrentDirectory)) |
| 2 | 238 | | { |
| 2 | 239 | | // If the process' current directory is the search path itself, or it is somewhere nested below it, |
| 2 | 240 | | // we have to take it into account. This will also account for differences in the two when the |
| 2 | 241 | | // search path does not end with a '\', but the PEB's current directory does (which is always the |
| 2 | 242 | | // case). |
| 2 | 243 | | if (dirs!.FindIndex(d => peb.CurrentDirectory.StartsWith(d, StringComparison.OrdinalIgnoreCase)) != |
| 2 | 244 | | { |
| 2 | 245 | | return (ProcessInfo)new ProcessInfoWindows(peb); |
| 2 | 246 | | } |
| 2 | 247 | | } |
| 2 | 248 | |
|
| 2 | 249 | | return null; |
| 2 | 250 | | }); |
| | 251 | | } |
| | 252 | |
|
| | 253 | | // Use a smaller buffer size on debug to ensure we hit the retry path. |
| 2 | 254 | | private static uint GetDefaultCachedBufferSize() => 1024 * |
| 2 | 255 | | #if DEBUG |
| 2 | 256 | | 8; |
| 2 | 257 | | #else |
| 2 | 258 | | 1024; |
| | 259 | | #endif |
| | 260 | |
|
| | 261 | | #if NET |
| | 262 | | // |
| | 263 | | // This implementation with based on dotnet/runtime ProcessManager.Win32.cs does. |
| | 264 | | // Basically, it doesn't hold on to a "cached buffer" and uses more modern constructs |
| | 265 | | // which results in "simpler" code. Especially, it does not use GCHandle and also |
| | 266 | | // doesn't have workarounds for "older" versions of Windows anymore. |
| | 267 | | // |
| | 268 | |
|
| | 269 | | private static uint s_mostRecentSize = GetDefaultCachedBufferSize(); |
| | 270 | |
|
| | 271 | | internal static unsafe Dictionary<(int, DateTime), T> EnumerateSystemProcesses<T, TData>( |
| | 272 | | HashSet<int>? processIds, |
| | 273 | | TData? data, |
| | 274 | | Func<TData?, int, SYSTEM_PROCESS_INFORMATION, ulong?, T?> newEntry) |
| | 275 | | { |
| | 276 | | // Start with the default buffer size. |
| | 277 | | uint bufferSize = s_mostRecentSize; |
| | 278 | | var infoClass = SYSTEM_INFORMATION_CLASS.SystemProcessInformation; |
| | 279 | |
|
| | 280 | | while (true) |
| | 281 | | { |
| | 282 | | // Some platforms require the buffer to be 64-bit aligned. ScopedNativeMemory guarantees sufficient alignmen |
| | 283 | | using var mem = new ScopedNativeMemory((int)bufferSize); |
| | 284 | |
|
| | 285 | | uint actualSize = 0; |
| | 286 | | uint status = NtQuerySystemInformation(infoClass, (void*)mem, bufferSize, &actualSize); |
| | 287 | |
|
| | 288 | | if (status != STATUS_INFO_LENGTH_MISMATCH) |
| | 289 | | { |
| | 290 | | // see definition of NT_SUCCESS(Status) in SDK |
| | 291 | | if ((int)status < 0) |
| | 292 | | { |
| | 293 | | throw GetException(status); |
| | 294 | | } |
| | 295 | |
|
| | 296 | | // Remember last buffer size for next attempt. Note that this may also result in smaller |
| | 297 | | // buffer sizes for further attempts, as the live processes can also decrease in comparison |
| | 298 | | // to a previous call. |
| | 299 | | Debug.Assert(actualSize > 0 && actualSize <= bufferSize, $"actualSize={actualSize} bufferSize={bufferSiz |
| | 300 | | s_mostRecentSize = GetEstimatedBufferSize(actualSize); |
| | 301 | |
|
| | 302 | | return HandleProcesses(new ReadOnlySpan<byte>((void*)mem, (int)actualSize), processIds, data, newEntry); |
| | 303 | | } |
| | 304 | | else |
| | 305 | | { |
| | 306 | | // Buffer was too small; retry with a larger buffer. |
| | 307 | | Debug.Assert(actualSize > bufferSize, $"actualSize={actualSize} bufferSize={bufferSize} (0x{status:x8}). |
| | 308 | | bufferSize = GetEstimatedBufferSize(actualSize); |
| | 309 | | } |
| | 310 | | } |
| | 311 | |
|
| | 312 | | // allocating a few more kilo bytes just in case there are some new processes since the last call |
| | 313 | | static uint GetEstimatedBufferSize(uint actualSize) => actualSize + 1024 * 10; |
| | 314 | | } |
| | 315 | |
|
| | 316 | | private static unsafe Dictionary<(int, DateTime), T> HandleProcesses<T, TData>( |
| | 317 | | ReadOnlySpan<byte> current, |
| | 318 | | HashSet<int>? processIds, |
| | 319 | | TData? data, |
| | 320 | | Func<TData?, int, SYSTEM_PROCESS_INFORMATION, ulong?, T?> newEntry) |
| | 321 | | { |
| | 322 | | var processInfos = new Dictionary<(int, DateTime), T>(); |
| | 323 | | int processInformationOffset = 0; |
| | 324 | | int count = 0; |
| | 325 | |
|
| | 326 | | while (true) |
| | 327 | | { |
| | 328 | | ref readonly var pi = ref MemoryMarshal.AsRef<SYSTEM_PROCESS_INFORMATION>(current.Slice(processInformationOf |
| | 329 | | int pid = pi.UniqueProcessId.ToInt32(); |
| | 330 | |
|
| | 331 | | ulong? seq = null; |
| | 332 | | if (SupportsProcessSequenceNumber) |
| | 333 | | { |
| | 334 | | ref readonly var pix = ref MemoryMarshal.AsRef<SYSTEM_PROCESS_INFORMATION_EXTENSION>(current.Slice(proce |
| | 335 | | seq = pix.ProcessSequenceNumber; |
| | 336 | | } |
| | 337 | |
|
| | 338 | | if (processIds == null || processIds.Contains(pid)) |
| | 339 | | { |
| | 340 | | var entry = newEntry(data, count, pi, seq); |
| | 341 | | if (entry != null) |
| | 342 | | { |
| | 343 | | processInfos.Add((pid, DateTime.FromFileTime(pi.CreateTime)), entry); |
| | 344 | | } |
| | 345 | | } |
| | 346 | |
|
| | 347 | | if (pi.NextEntryOffset == 0) |
| | 348 | | { |
| | 349 | | break; |
| | 350 | | } |
| | 351 | | processInformationOffset += (int)pi.NextEntryOffset; |
| | 352 | | count++; |
| | 353 | | } |
| | 354 | |
|
| | 355 | | return processInfos; |
| | 356 | | } |
| | 357 | |
|
| | 358 | | #else |
| | 359 | | // |
| | 360 | | // This implementation with based on .NET Frameworks Process class. It does not provide all the features as the |
| | 361 | | // .NET version. For example, it does not access SYSTEM_PROCESS_INFORMATION_EXTENSION to get the ProcessSequenceNumb |
| | 362 | | // |
| | 363 | |
|
| | 364 | | private static long[]? s_cachedBuffer; |
| | 365 | |
|
| | 366 | | internal static Dictionary<(int, DateTime), T> EnumerateSystemProcesses<T, TData>( |
| | 367 | | HashSet<int>? processIds, |
| | 368 | | TData? data, |
| | 369 | | Func<TData?, int, SYSTEM_PROCESS_INFORMATION, ulong?, T?> newEntry) |
| | 370 | | { |
| 2 | 371 | | var processInfos = new Dictionary<(int, DateTime), T>(); |
| 2 | 372 | | var bufferHandle = new GCHandle(); |
| | 373 | |
|
| | 374 | | // Start with the default buffer size (smaller in DEBUG to make sure retry path is hit) |
| 2 | 375 | | int bufferSize = (int)GetDefaultCachedBufferSize(); |
| | 376 | |
|
| | 377 | | // Get the cached buffer. |
| 2 | 378 | | long[]? buffer = Interlocked.Exchange(ref s_cachedBuffer, null); |
| | 379 | |
|
| | 380 | | try |
| | 381 | | { |
| | 382 | | // Retry until we get all the data |
| | 383 | | int status; |
| | 384 | | do |
| | 385 | | { |
| 2 | 386 | | if (buffer == null) |
| | 387 | | { |
| | 388 | | // Allocate buffer of longs since some platforms require the buffer to be 64-bit aligned. |
| 2 | 389 | | buffer = new long[(bufferSize + 7) / 8]; |
| | 390 | | } |
| | 391 | | else |
| | 392 | | { |
| | 393 | | // If we have cached buffer, set the size properly. |
| 2 | 394 | | bufferSize = buffer.Length * sizeof(long); |
| | 395 | | } |
| 2 | 396 | | bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); |
| | 397 | |
|
| 2 | 398 | | status = NtQuerySystemInformation( |
| 2 | 399 | | SYSTEM_INFORMATION_CLASS.SystemProcessInformation, |
| 2 | 400 | | bufferHandle.AddrOfPinnedObject(), |
| 2 | 401 | | bufferSize, |
| 2 | 402 | | out int requiredSize); |
| | 403 | |
|
| 2 | 404 | | if ((uint)status == STATUS_INFO_LENGTH_MISMATCH) |
| | 405 | | { |
| 0 | 406 | | if (bufferHandle.IsAllocated) |
| | 407 | | { |
| 0 | 408 | | bufferHandle.Free(); |
| | 409 | | } |
| | 410 | |
|
| 0 | 411 | | buffer = null; |
| 0 | 412 | | bufferSize = GetNewBufferSize(bufferSize, requiredSize); |
| | 413 | | } |
| 2 | 414 | | } while ((uint)status == STATUS_INFO_LENGTH_MISMATCH); |
| | 415 | |
|
| 2 | 416 | | if (status < 0) |
| | 417 | | { |
| 0 | 418 | | throw GetException((uint)status); |
| | 419 | | } |
| | 420 | |
|
| | 421 | | // Parse the data block to get process information |
| 2 | 422 | | IntPtr dataPtr = bufferHandle.AddrOfPinnedObject(); |
| | 423 | |
|
| 2 | 424 | | long totalOffset = 0; |
| 2 | 425 | | int count = 0; |
| | 426 | |
|
| 2 | 427 | | while (true) |
| | 428 | | { |
| 2 | 429 | | nint currentPtr = checked((IntPtr)(dataPtr.ToInt64() + totalOffset)); |
| 2 | 430 | | var pi = Marshal.PtrToStructure<SYSTEM_PROCESS_INFORMATION>(currentPtr); |
| | 431 | |
|
| 2 | 432 | | int pid = pi.UniqueProcessId.ToInt32(); |
| 2 | 433 | | if (processIds == null || processIds.Contains(pid)) |
| | 434 | | { |
| 2 | 435 | | var startTime = DateTime.FromFileTime(pi.CreateTime); |
| 2 | 436 | | var entry = newEntry(data, count, pi, null); |
| 2 | 437 | | if (entry != null) |
| | 438 | | { |
| 2 | 439 | | processInfos.Add((pid, startTime), entry); |
| | 440 | | } |
| | 441 | | } |
| | 442 | |
|
| 2 | 443 | | if (pi.NextEntryOffset == 0) |
| | 444 | | { |
| 2 | 445 | | break; |
| | 446 | | } |
| 2 | 447 | | totalOffset += pi.NextEntryOffset; |
| 2 | 448 | | count++; |
| | 449 | | } |
| | 450 | | } |
| | 451 | | finally |
| | 452 | | { |
| | 453 | | // Cache the final buffer for use on the next call. |
| 2 | 454 | | Interlocked.Exchange(ref s_cachedBuffer, buffer); |
| | 455 | |
|
| 2 | 456 | | if (bufferHandle.IsAllocated) |
| | 457 | | { |
| 2 | 458 | | bufferHandle.Free(); |
| | 459 | | } |
| 2 | 460 | | } |
| | 461 | |
|
| 2 | 462 | | return processInfos; |
| | 463 | |
|
| | 464 | | static int GetNewBufferSize(int existingBufferSize, int requiredSize) |
| | 465 | | { |
| 0 | 466 | | if (requiredSize == 0) |
| | 467 | | { |
| | 468 | | // |
| | 469 | | // On some old OS like win2000, requiredSize will not be set if the buffer |
| | 470 | | // passed to NtQuerySystemInformation is not enough. |
| | 471 | | // |
| 0 | 472 | | int newSize = existingBufferSize * 2; |
| 0 | 473 | | if (newSize < existingBufferSize) |
| | 474 | | { |
| 0 | 475 | | throw new OutOfMemoryException($"Existing buffer size {existingBufferSize:N0} bytes, attempting to a |
| | 476 | | } |
| 0 | 477 | | return newSize; |
| | 478 | | } |
| | 479 | | else |
| | 480 | | { |
| | 481 | | // allocating a few more kilo bytes just in case there are some new process |
| | 482 | | // kicked in since new call to NtQuerySystemInformation |
| 0 | 483 | | int newSize = requiredSize + (1024 * 10); |
| 0 | 484 | | if (newSize < requiredSize) |
| | 485 | | { |
| 0 | 486 | | throw new OutOfMemoryException($"Required buffer size {requiredSize:N0} bytes, attempting to allocat |
| | 487 | | } |
| 0 | 488 | | return newSize; |
| | 489 | | } |
| | 490 | | } |
| | 491 | | } |
| | 492 | | #endif |
| | 493 | | } |