| | | 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. |
| | | 45 | | private static readonly Lazy<Dictionary<int, PseudoPeb>> s_systemPseudoProcesses = new(() => |
| | | 46 | | { |
| | | 47 | | string? systemAccount = GetSystemAccountName(); |
| | | 48 | | var result = new Dictionary<int, PseudoPeb>(); |
| | | 49 | | |
| | | 50 | | EnumerateSystemProcesses(null, result, (res, _, pi, processSequenceNumber) => |
| | | 51 | | { |
| | | 52 | | if ((int)pi.UniqueProcessId == 0) |
| | | 53 | | { |
| | | 54 | | // "System Idle Process" always PID 0, does not have a name, even in SYSTEM_PROCESS_INFORMATION.NamePtr |
| | | 55 | | res![0] = new PseudoPeb(pi, null, systemAccount, "System Idle Process", processSequenceNumber); |
| | | 56 | | } |
| | | 57 | | else if (pi.NamePtr != IntPtr.Zero) |
| | | 58 | | { |
| | | 59 | | string? name = Marshal.PtrToStringUni(pi.NamePtr); |
| | | 60 | | if (name != null && IsSystemPseudoProcessByName(name, out var executable)) |
| | | 61 | | { |
| | | 62 | | var pseudoPeb = new PseudoPeb(pi, executable, systemAccount, processSequenceNumber: processSequenceN |
| | | 63 | | res![pseudoPeb.ProcessId] = pseudoPeb; |
| | | 64 | | } |
| | | 65 | | } |
| | | 66 | | return 0; |
| | | 67 | | }); |
| | | 68 | | |
| | | 69 | | return result; |
| | | 70 | | }, LazyThreadSafetyMode.ExecutionAndPublication); |
| | | 71 | | |
| | | 72 | | private static bool IsSystemPseudoProcessByName(string? processName, out string? executablePath) |
| | | 73 | | { |
| | | 74 | | executablePath = null; |
| | | 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. |
| | | 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. |
| | | 93 | | executablePath = Environment.ExpandEnvironmentVariables("%windir%\\System32\\ntoskrnl.exe"); |
| | | 94 | | return true; |
| | | 95 | | } |
| | | 96 | | } |
| | | 97 | | |
| | | 98 | | return false; |
| | | 99 | | } |
| | | 100 | | |
| | | 101 | | private static string? GetSystemAccountName() |
| | | 102 | | { |
| | | 103 | | try |
| | | 104 | | { |
| | | 105 | | var sid = new SecurityIdentifier("S-1-5-18"); |
| | | 106 | | return sid.Translate(typeof(NTAccount)).Value; |
| | | 107 | | } |
| | | 108 | | catch |
| | | 109 | | { |
| | | 110 | | } |
| | | 111 | | |
| | | 112 | | return null; |
| | | 113 | | } |
| | | 114 | | |
| | | 115 | | internal static bool TryGetSystemPseudoProcess(int processId, [NotNullWhen(true)] out PseudoPeb? info) |
| | | 116 | | => s_systemPseudoProcesses.Value.TryGetValue(processId, out info); |
| | | 117 | | |
| | | 118 | | public static HashSet<IWin32ProcessDetails> GetAllProcesses() |
| | | 119 | | { |
| | | 120 | | return EnumerateSystemProcesses(null, (object?)null, |
| | | 121 | | static (_, idx, pi, seq) => (IWin32ProcessDetails)new Peb(pi, seq)) |
| | | 122 | | .Select(v => v.Value) |
| | | 123 | | .ToHashSet(); |
| | | 124 | | } |
| | | 125 | | |
| | | 126 | | public static HashSet<ProcessInfo> GetLockingProcessInfos(string[] paths, [NotNullIfNotNull(nameof(directories))] re |
| | | 127 | | { |
| | | 128 | | if (paths == null) |
| | | 129 | | { |
| | | 130 | | throw new ArgumentNullException(nameof(paths)); |
| | | 131 | | } |
| | | 132 | | |
| | | 133 | | var result = new HashSet<ProcessInfo>(); |
| | | 134 | | |
| | | 135 | | foreach (string path in paths) |
| | | 136 | | { |
| | | 137 | | if (Directory.Exists(path)) |
| | | 138 | | { |
| | | 139 | | directories?.Add(path); |
| | | 140 | | continue; |
| | | 141 | | } |
| | | 142 | | |
| | | 143 | | GetLockingProcessInfo(path, result); |
| | | 144 | | } |
| | | 145 | | |
| | | 146 | | return result; |
| | | 147 | | } |
| | | 148 | | |
| | | 149 | | private static void GetLockingProcessInfo(string path, HashSet<ProcessInfo> result) |
| | | 150 | | { |
| | | 151 | | if (path == null) |
| | | 152 | | { |
| | | 153 | | throw new ArgumentNullException(nameof(path)); |
| | | 154 | | } |
| | | 155 | | |
| | | 156 | | if (result == null) |
| | | 157 | | { |
| | | 158 | | throw new ArgumentNullException(nameof(result)); |
| | | 159 | | } |
| | | 160 | | |
| | | 161 | | var statusBlock = new IO_STATUS_BLOCK(); |
| | | 162 | | |
| | | 163 | | using (var handle = GetFileHandle(path)) |
| | | 164 | | { |
| | | 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. |
| | | 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. |
| | | 175 | | int bufferSize = (IntPtr.Size + sizeof(int)) * 8; |
| | | 176 | | #if NET |
| | | 177 | | using (var mem = new ScopedNativeMemory(stackalloc byte[bufferSize])) |
| | | 178 | | #else |
| | | 179 | | using (var mem = new ScopedNativeMemory(bufferSize)) |
| | | 180 | | #endif |
| | | 181 | | { |
| | | 182 | | uint status; |
| | | 183 | | while ((status = NtQueryInformationFile(handle, ref statusBlock, (IntPtr)mem, (uint)mem.Size, |
| | | 184 | | FILE_INFORMATION_CLASS.FileProcessIdsUsingFileInformation)) == STATUS_INFO_LENGTH_MISMATCH) |
| | | 185 | | { |
| | | 186 | | mem.Resize(mem.Size * 2); |
| | | 187 | | } |
| | | 188 | | |
| | | 189 | | if (status != STATUS_SUCCESS) |
| | | 190 | | { |
| | | 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 | | |
| | | 201 | | IntPtr readBuffer = (IntPtr)mem; |
| | | 202 | | int numEntries = Marshal.ReadInt32(readBuffer); // NumberOfProcessIdsInList |
| | | 203 | | readBuffer += IntPtr.Size; |
| | | 204 | | |
| | | 205 | | for (int i = 0; i < numEntries; i++) |
| | | 206 | | { |
| | | 207 | | int processId = Marshal.ReadIntPtr(readBuffer).ToInt32(); // A single ProcessIdList[] element |
| | | 208 | | var entry = ProcessInfoWindows.Create(processId); |
| | | 209 | | if (entry != null) |
| | | 210 | | { |
| | | 211 | | result.Add(entry); |
| | | 212 | | } |
| | | 213 | | readBuffer += IntPtr.Size; |
| | | 214 | | } |
| | | 215 | | } |
| | | 216 | | } |
| | | 217 | | } |
| | | 218 | | |
| | | 219 | | internal static Win32Exception GetException(uint status) |
| | | 220 | | { |
| | | 221 | | int res = RtlNtStatusToDosError(status); |
| | | 222 | | return new Win32Exception(res, GetMessage(res)); |
| | | 223 | | } |
| | | 224 | | |
| | | 225 | | internal static Dictionary<(int, DateTime), ProcessInfo> GetProcessesByWorkingDirectory(List<string> directories) |
| | | 226 | | { |
| | | 227 | | if (directories == null) |
| | | 228 | | { |
| | | 229 | | throw new ArgumentNullException(nameof(directories)); |
| | | 230 | | } |
| | | 231 | | |
| | | 232 | | return EnumerateSystemProcesses(null, directories, |
| | | 233 | | static (dirs, idx, pi, seq) => |
| | | 234 | | { |
| | | 235 | | var peb = new Peb(pi, seq); |
| | | 236 | | |
| | | 237 | | if (!peb.HasError && !string.IsNullOrEmpty(peb.CurrentDirectory)) |
| | | 238 | | { |
| | | 239 | | // If the process' current directory is the search path itself, or it is somewhere nested below it, |
| | | 240 | | // we have to take it into account. This will also account for differences in the two when the |
| | | 241 | | // search path does not end with a '\', but the PEB's current directory does (which is always the |
| | | 242 | | // case). |
| | | 243 | | if (dirs!.FindIndex(d => peb.CurrentDirectory.StartsWith(d, StringComparison.OrdinalIgnoreCase)) != |
| | | 244 | | { |
| | | 245 | | return (ProcessInfo)new ProcessInfoWindows(peb); |
| | | 246 | | } |
| | | 247 | | } |
| | | 248 | | |
| | | 249 | | return null; |
| | | 250 | | }); |
| | | 251 | | } |
| | | 252 | | |
| | | 253 | | // Use a smaller buffer size on debug to ensure we hit the retry path. |
| | | 254 | | private static uint GetDefaultCachedBufferSize() => 1024 * |
| | | 255 | | #if DEBUG |
| | | 256 | | 8; |
| | | 257 | | #else |
| | | 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 | | { |
| | | 371 | | var processInfos = new Dictionary<(int, DateTime), T>(); |
| | | 372 | | var bufferHandle = new GCHandle(); |
| | | 373 | | |
| | | 374 | | // Start with the default buffer size (smaller in DEBUG to make sure retry path is hit) |
| | | 375 | | int bufferSize = (int)GetDefaultCachedBufferSize(); |
| | | 376 | | |
| | | 377 | | // Get the cached buffer. |
| | | 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 | | { |
| | | 386 | | if (buffer == null) |
| | | 387 | | { |
| | | 388 | | // Allocate buffer of longs since some platforms require the buffer to be 64-bit aligned. |
| | | 389 | | buffer = new long[(bufferSize + 7) / 8]; |
| | | 390 | | } |
| | | 391 | | else |
| | | 392 | | { |
| | | 393 | | // If we have cached buffer, set the size properly. |
| | | 394 | | bufferSize = buffer.Length * sizeof(long); |
| | | 395 | | } |
| | | 396 | | bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); |
| | | 397 | | |
| | | 398 | | status = NtQuerySystemInformation( |
| | | 399 | | SYSTEM_INFORMATION_CLASS.SystemProcessInformation, |
| | | 400 | | bufferHandle.AddrOfPinnedObject(), |
| | | 401 | | bufferSize, |
| | | 402 | | out int requiredSize); |
| | | 403 | | |
| | | 404 | | if ((uint)status == STATUS_INFO_LENGTH_MISMATCH) |
| | | 405 | | { |
| | | 406 | | if (bufferHandle.IsAllocated) |
| | | 407 | | { |
| | | 408 | | bufferHandle.Free(); |
| | | 409 | | } |
| | | 410 | | |
| | | 411 | | buffer = null; |
| | | 412 | | bufferSize = GetNewBufferSize(bufferSize, requiredSize); |
| | | 413 | | } |
| | | 414 | | } while ((uint)status == STATUS_INFO_LENGTH_MISMATCH); |
| | | 415 | | |
| | | 416 | | if (status < 0) |
| | | 417 | | { |
| | | 418 | | throw GetException((uint)status); |
| | | 419 | | } |
| | | 420 | | |
| | | 421 | | // Parse the data block to get process information |
| | | 422 | | IntPtr dataPtr = bufferHandle.AddrOfPinnedObject(); |
| | | 423 | | |
| | | 424 | | long totalOffset = 0; |
| | | 425 | | int count = 0; |
| | | 426 | | |
| | | 427 | | while (true) |
| | | 428 | | { |
| | | 429 | | nint currentPtr = checked((IntPtr)(dataPtr.ToInt64() + totalOffset)); |
| | | 430 | | var pi = Marshal.PtrToStructure<SYSTEM_PROCESS_INFORMATION>(currentPtr); |
| | | 431 | | |
| | | 432 | | int pid = pi.UniqueProcessId.ToInt32(); |
| | | 433 | | if (processIds == null || processIds.Contains(pid)) |
| | | 434 | | { |
| | | 435 | | var startTime = DateTime.FromFileTime(pi.CreateTime); |
| | | 436 | | var entry = newEntry(data, count, pi, null); |
| | | 437 | | if (entry != null) |
| | | 438 | | { |
| | | 439 | | processInfos.Add((pid, startTime), entry); |
| | | 440 | | } |
| | | 441 | | } |
| | | 442 | | |
| | | 443 | | if (pi.NextEntryOffset == 0) |
| | | 444 | | { |
| | | 445 | | break; |
| | | 446 | | } |
| | | 447 | | totalOffset += pi.NextEntryOffset; |
| | | 448 | | count++; |
| | | 449 | | } |
| | | 450 | | } |
| | | 451 | | finally |
| | | 452 | | { |
| | | 453 | | // Cache the final buffer for use on the next call. |
| | | 454 | | Interlocked.Exchange(ref s_cachedBuffer, buffer); |
| | | 455 | | |
| | | 456 | | if (bufferHandle.IsAllocated) |
| | | 457 | | { |
| | | 458 | | bufferHandle.Free(); |
| | | 459 | | } |
| | | 460 | | } |
| | | 461 | | |
| | | 462 | | return processInfos; |
| | | 463 | | |
| | | 464 | | static int GetNewBufferSize(int existingBufferSize, int requiredSize) |
| | | 465 | | { |
| | | 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 | | // |
| | | 472 | | int newSize = existingBufferSize * 2; |
| | | 473 | | if (newSize < existingBufferSize) |
| | | 474 | | { |
| | | 475 | | throw new OutOfMemoryException($"Existing buffer size {existingBufferSize:N0} bytes, attempting to a |
| | | 476 | | } |
| | | 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 |
| | | 483 | | int newSize = requiredSize + (1024 * 10); |
| | | 484 | | if (newSize < requiredSize) |
| | | 485 | | { |
| | | 486 | | throw new OutOfMemoryException($"Required buffer size {requiredSize:N0} bytes, attempting to allocat |
| | | 487 | | } |
| | | 488 | | return newSize; |
| | | 489 | | } |
| | | 490 | | } |
| | | 491 | | } |
| | | 492 | | #endif |
| | | 493 | | } |