Annotation of cleanflash/CleanFlashCommon/HandleUtil.cs, revision 1.1.1.1

1.1       root        1: // Taken from: https://github.com/Walkman100/FileLocks
                      2: using System;
                      3: using System.Collections.Generic;
                      4: using System.IO;
                      5: using System.Runtime.CompilerServices;
                      6: using System.Runtime.ConstrainedExecution;
                      7: using System.Runtime.InteropServices;
                      8: using System.Security.Permissions;
                      9: using System.Text;
                     10: using System.Threading;
                     11: using Microsoft.Win32.SafeHandles;
                     12: using System.Diagnostics;
                     13: using System.Linq;
                     14: 
                     15: namespace CleanFlashCommon {
                     16:     public static class HandleUtil {
                     17: 
                     18:         private static Dictionary<string, string> deviceMap;
                     19:         private const string networkDevicePrefix = "\\Device\\LanmanRedirector\\";
                     20:         private const int MAX_PATH = 260;
                     21:         private const int handleTypeTokenCount = 27;
                     22:         private static readonly string[] handleTypeTokens = new string[] {
                     23:             "", "", "Directory", "SymbolicLink", "Token",
                     24:             "Process", "Thread", "Unknown7", "Event", "EventPair", "Mutant",
                     25:             "Unknown11", "Semaphore", "Timer", "Profile", "WindowStation",
                     26:             "Desktop", "Section", "Key", "Port", "WaitablePort",
                     27:             "Unknown21", "Unknown22", "Unknown23", "Unknown24",
                     28:             "IoCompletion", "File"
                     29:         };
                     30: 
                     31:         internal enum NT_STATUS {
                     32:             STATUS_SUCCESS = 0x00000000,
                     33:             STATUS_BUFFER_OVERFLOW = unchecked((int)0x80000005L),
                     34:             STATUS_INFO_LENGTH_MISMATCH = unchecked((int)0xC0000004L)
                     35:         }
                     36: 
                     37:         internal enum SYSTEM_INFORMATION_CLASS {
                     38:             SystemBasicInformation = 0,
                     39:             SystemPerformanceInformation = 2,
                     40:             SystemTimeOfDayInformation = 3,
                     41:             SystemProcessInformation = 5,
                     42:             SystemProcessorPerformanceInformation = 8,
                     43:             SystemHandleInformation = 16,
                     44:             SystemInterruptInformation = 23,
                     45:             SystemExceptionInformation = 33,
                     46:             SystemRegistryQuotaInformation = 37,
                     47:             SystemLookasideInformation = 45
                     48:         }
                     49: 
                     50:         internal enum OBJECT_INFORMATION_CLASS {
                     51:             ObjectBasicInformation = 0,
                     52:             ObjectNameInformation = 1,
                     53:             ObjectTypeInformation = 2,
                     54:             ObjectAllTypesInformation = 3,
                     55:             ObjectHandleInformation = 4
                     56:         }
                     57: 
                     58:         [Flags]
                     59:         internal enum ProcessAccessRights {
                     60:             PROCESS_DUP_HANDLE = 0x00000040
                     61:         }
                     62: 
                     63:         [Flags]
                     64:         internal enum DuplicateHandleOptions {
                     65:             DUPLICATE_CLOSE_SOURCE = 0x1,
                     66:             DUPLICATE_SAME_ACCESS = 0x2
                     67:         }
                     68: 
                     69:         private enum SystemHandleType {
                     70:             OB_TYPE_UNKNOWN = 0,
                     71:             OB_TYPE_TYPE = 1,
                     72:             OB_TYPE_DIRECTORY,
                     73:             OB_TYPE_SYMBOLIC_LINK,
                     74:             OB_TYPE_TOKEN,
                     75:             OB_TYPE_PROCESS,
                     76:             OB_TYPE_THREAD,
                     77:             OB_TYPE_UNKNOWN_7,
                     78:             OB_TYPE_EVENT,
                     79:             OB_TYPE_EVENT_PAIR,
                     80:             OB_TYPE_MUTANT,
                     81:             OB_TYPE_UNKNOWN_11,
                     82:             OB_TYPE_SEMAPHORE,
                     83:             OB_TYPE_TIMER,
                     84:             OB_TYPE_PROFILE,
                     85:             OB_TYPE_WINDOW_STATION,
                     86:             OB_TYPE_DESKTOP,
                     87:             OB_TYPE_SECTION,
                     88:             OB_TYPE_KEY,
                     89:             OB_TYPE_PORT,
                     90:             OB_TYPE_WAITABLE_PORT,
                     91:             OB_TYPE_UNKNOWN_21,
                     92:             OB_TYPE_UNKNOWN_22,
                     93:             OB_TYPE_UNKNOWN_23,
                     94:             OB_TYPE_UNKNOWN_24,
                     95:             OB_TYPE_IO_COMPLETION,
                     96:             OB_TYPE_FILE
                     97:         };
                     98: 
                     99:         [StructLayout(LayoutKind.Sequential)]
                    100:         private struct SYSTEM_HANDLE_ENTRY {
                    101:             public int OwnerPid;
                    102:             public byte ObjectType;
                    103:             public byte HandleFlags;
                    104:             public short HandleValue;
                    105:             public int ObjectPointer;
                    106:             public int AccessMask;
                    107:         }
                    108: 
                    109:         [DllImport("ntdll.dll")]
                    110:         internal static extern NT_STATUS NtQuerySystemInformation(
                    111:             [In] SYSTEM_INFORMATION_CLASS SystemInformationClass,
                    112:             [In] IntPtr SystemInformation,
                    113:             [In] int SystemInformationLength,
                    114:             [Out] out int ReturnLength);
                    115: 
                    116:         [DllImport("ntdll.dll")]
                    117:         internal static extern NT_STATUS NtQueryObject(
                    118:             [In] IntPtr Handle,
                    119:             [In] OBJECT_INFORMATION_CLASS ObjectInformationClass,
                    120:             [In] IntPtr ObjectInformation,
                    121:             [In] int ObjectInformationLength,
                    122:             [Out] out int ReturnLength);
                    123: 
                    124:         [DllImport("kernel32.dll", SetLastError = true)]
                    125:         internal static extern SafeProcessHandle OpenProcess(
                    126:             [In] ProcessAccessRights dwDesiredAccess,
                    127:             [In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
                    128:             [In] int dwProcessId);
                    129: 
                    130:         [DllImport("kernel32.dll", SetLastError = true)]
                    131:         [return: MarshalAs(UnmanagedType.Bool)]
                    132:         internal static extern bool DuplicateHandle(
                    133:             [In] IntPtr hSourceProcessHandle,
                    134:             [In] IntPtr hSourceHandle,
                    135:             [In] IntPtr hTargetProcessHandle,
                    136:             [Out] out SafeObjectHandle lpTargetHandle,
                    137:             [In] int dwDesiredAccess,
                    138:             [In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
                    139:             [In] DuplicateHandleOptions dwOptions);
                    140: 
                    141:         [DllImport("kernel32.dll")]
                    142:         internal static extern IntPtr GetCurrentProcess();
                    143: 
                    144:         [DllImport("kernel32.dll", SetLastError = true)]
                    145:         internal static extern int GetProcessId(
                    146:             [In] IntPtr Process);
                    147: 
                    148:         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
                    149:         [DllImport("kernel32.dll", SetLastError = true)]
                    150:         [return: MarshalAs(UnmanagedType.Bool)]
                    151:         internal static extern bool CloseHandle(
                    152:             [In] IntPtr hObject);
                    153: 
                    154:         [DllImport("kernel32.dll", SetLastError = true)]
                    155:         internal static extern int QueryDosDevice(
                    156:             [In] string lpDeviceName,
                    157:             [Out] StringBuilder lpTargetPath,
                    158:             [In] int ucchMax);
                    159: 
                    160:         [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
                    161:         internal sealed class SafeObjectHandle : SafeHandleZeroOrMinusOneIsInvalid {
                    162:             private SafeObjectHandle() : base(true) { }
                    163: 
                    164:             internal SafeObjectHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHandle) {
                    165:                 base.SetHandle(preexistingHandle);
                    166:             }
                    167: 
                    168:             protected override bool ReleaseHandle() {
                    169:                 return CloseHandle(base.handle);
                    170:             }
                    171:         }
                    172: 
                    173:         [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
                    174:         internal sealed class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid {
                    175:             private SafeProcessHandle()
                    176:                 : base(true) { }
                    177: 
                    178:             internal SafeProcessHandle(IntPtr preexistingHandle, bool ownsHandle)
                    179:                 : base(ownsHandle) {
                    180:                 base.SetHandle(preexistingHandle);
                    181:             }
                    182: 
                    183:             protected override bool ReleaseHandle() {
                    184:                 return CloseHandle(base.handle);
                    185:             }
                    186:         }
                    187: 
                    188:         private sealed class OpenFiles : IEnumerable<string> {
                    189:             private readonly int processId;
                    190: 
                    191:             internal OpenFiles(int processId) {
                    192:                 this.processId = processId;
                    193:             }
                    194: 
                    195:             public IEnumerator<string> GetEnumerator() {
                    196:                 NT_STATUS ret;
                    197:                 int length = 0x10000;
                    198:                 // Loop, probing for required memory.
                    199: 
                    200:                 do {
                    201:                     IntPtr ptr = IntPtr.Zero;
                    202:                     RuntimeHelpers.PrepareConstrainedRegions();
                    203: 
                    204:                     try {
                    205:                         RuntimeHelpers.PrepareConstrainedRegions();
                    206: 
                    207:                         try { } finally {
                    208:                             // CER guarantees that the address of the allocated 
                    209:                             // memory is actually assigned to ptr if an 
                    210:                             // asynchronous exception occurs.
                    211:                             ptr = Marshal.AllocHGlobal(length);
                    212:                         }
                    213: 
                    214:                         ret = NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS.SystemHandleInformation, ptr, length, out int returnLength);
                    215: 
                    216:                         if (ret == NT_STATUS.STATUS_INFO_LENGTH_MISMATCH) {
                    217:                             // Round required memory up to the nearest 64KB boundary.
                    218:                             length = (returnLength + 0xffff) & ~0xffff;
                    219:                         } else if (ret == NT_STATUS.STATUS_SUCCESS) {
                    220:                             int handleCount = Marshal.ReadInt32(ptr);
                    221:                             int offset = sizeof(int);
                    222:                             int size = Marshal.SizeOf(typeof(SYSTEM_HANDLE_ENTRY));
                    223: 
                    224:                             for (int i = 0; i < handleCount; i++) {
                    225:                                 SYSTEM_HANDLE_ENTRY handleEntry = (SYSTEM_HANDLE_ENTRY) Marshal.PtrToStructure((IntPtr)((int)ptr + offset), typeof(SYSTEM_HANDLE_ENTRY));
                    226:                                 
                    227:                                 if (handleEntry.OwnerPid == processId) {
                    228:                                     IntPtr handle = (IntPtr) handleEntry.HandleValue;
                    229:                                     SystemHandleType handleType;
                    230: 
                    231:                                     if (GetHandleType(handle, handleEntry.OwnerPid, out handleType) && handleType == SystemHandleType.OB_TYPE_FILE) {
                    232:                                         if (GetFileNameFromHandle(handle, handleEntry.OwnerPid, out string devicePath)) {
                    233:                                             if (ConvertDevicePathToDosPath(devicePath, out string dosPath)) {
                    234:                                                 if (File.Exists(dosPath)) {
                    235:                                                     yield return dosPath;
                    236:                                                 } else if (Directory.Exists(dosPath)) {
                    237:                                                     yield return dosPath;
                    238:                                                 }
                    239:                                             }
                    240:                                         }
                    241:                                     }
                    242:                                 }
                    243: 
                    244:                                 offset += size;
                    245:                             }
                    246:                         }
                    247:                     } finally {
                    248:                         // CER guarantees that the allocated memory is freed, 
                    249:                         // if an asynchronous exception occurs. 
                    250:                         Marshal.FreeHGlobal(ptr);
                    251:                     }
                    252:                 } while (ret == NT_STATUS.STATUS_INFO_LENGTH_MISMATCH);
                    253:             }
                    254: 
                    255:             System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
                    256:                 return GetEnumerator();
                    257:             }
                    258:         }
                    259: 
                    260:         private class FileNameFromHandleState : IDisposable {
                    261:             private readonly ManualResetEvent _mr;
                    262:             public IntPtr Handle { get; }
                    263:             public string FileName { get; set; }
                    264:             public bool RetValue { get; set; }
                    265: 
                    266:             public FileNameFromHandleState(IntPtr handle) {
                    267:                 _mr = new ManualResetEvent(false);
                    268:                 this.Handle = handle;
                    269:             }
                    270: 
                    271:             public bool WaitOne(int wait) {
                    272:                 return _mr.WaitOne(wait, false);
                    273:             }
                    274: 
                    275:             public void Set() {
                    276:                 try {
                    277:                     _mr.Set();
                    278:                 } catch { }
                    279:             }
                    280: 
                    281:             public void Dispose() {
                    282:                 if (_mr != null) {
                    283:                     _mr.Close();
                    284:                 }
                    285:             }
                    286:         }
                    287: 
                    288:         private static bool GetFileNameFromHandle(IntPtr handle, out string fileName) {
                    289:             IntPtr ptr = IntPtr.Zero;
                    290:             RuntimeHelpers.PrepareConstrainedRegions();
                    291: 
                    292:             try {
                    293:                 int length = 0x200;  // 512 bytes
                    294:                 RuntimeHelpers.PrepareConstrainedRegions();
                    295: 
                    296:                 try { } finally {
                    297:                     // CER guarantees the assignment of the allocated 
                    298:                     // memory address to ptr, if an ansynchronous exception 
                    299:                     // occurs.
                    300:                     ptr = Marshal.AllocHGlobal(length);
                    301:                 }
                    302: 
                    303:                 NT_STATUS ret = NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, ptr, length, out length);
                    304: 
                    305:                 if (ret == NT_STATUS.STATUS_BUFFER_OVERFLOW) {
                    306:                     RuntimeHelpers.PrepareConstrainedRegions();
                    307:                     try { } finally {
                    308:                         // CER guarantees that the previous allocation is freed,
                    309:                         // and that the newly allocated memory address is 
                    310:                         // assigned to ptr if an asynchronous exception occurs.
                    311:                         Marshal.FreeHGlobal(ptr);
                    312:                         ptr = Marshal.AllocHGlobal(length);
                    313:                     }
                    314:                     ret = NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, ptr, length, out length);
                    315:                 }
                    316:                 if (ret == NT_STATUS.STATUS_SUCCESS) {
                    317:                     fileName = Marshal.PtrToStringUni((IntPtr)((int)ptr + 8), (length - 9) / 2);
                    318:                     return fileName.Length != 0;
                    319:                 }
                    320:             } finally {
                    321:                 // CER guarantees that the allocated memory is freed, 
                    322:                 // if an asynchronous exception occurs.
                    323:                 Marshal.FreeHGlobal(ptr);
                    324:             }
                    325: 
                    326:             fileName = string.Empty;
                    327:             return false;
                    328:         }
                    329:         private static void GetFileNameFromHandle(object state) {
                    330:             FileNameFromHandleState s = (FileNameFromHandleState)state;
                    331: 
                    332:             s.RetValue = GetFileNameFromHandle(s.Handle, out string fileName);
                    333:             s.FileName = fileName;
                    334:             s.Set();
                    335:         }
                    336: 
                    337:         private static bool GetFileNameFromHandle(IntPtr handle, out string fileName, int wait) {
                    338:             using (FileNameFromHandleState f = new FileNameFromHandleState(handle)) {
                    339:                 ThreadPool.QueueUserWorkItem(new WaitCallback(GetFileNameFromHandle), f);
                    340: 
                    341:                 if (f.WaitOne(wait)) {
                    342:                     fileName = f.FileName;
                    343:                     return f.RetValue;
                    344:                 } else {
                    345:                     fileName = string.Empty;
                    346:                     return false;
                    347:                 }
                    348:             }
                    349:         }
                    350: 
                    351:         private static bool GetFileNameFromHandle(IntPtr handle, int processId, out string fileName) {
                    352:             IntPtr currentProcess = GetCurrentProcess();
                    353:             bool remote = processId != GetProcessId(currentProcess);
                    354:             SafeProcessHandle processHandle = null;
                    355:             SafeObjectHandle objectHandle = null;
                    356: 
                    357:             try {
                    358:                 if (remote) {
                    359:                     processHandle = OpenProcess(ProcessAccessRights.PROCESS_DUP_HANDLE, true, processId);
                    360:                     if (DuplicateHandle(processHandle.DangerousGetHandle(), handle, currentProcess, out objectHandle, 0, false, DuplicateHandleOptions.DUPLICATE_SAME_ACCESS)) {
                    361:                         handle = objectHandle.DangerousGetHandle();
                    362:                     }
                    363:                 }
                    364: 
                    365:                 return GetFileNameFromHandle(handle, out fileName, 200);
                    366:             } finally {
                    367:                 if (remote) {
                    368:                     if (processHandle != null) {
                    369:                         processHandle.Close();
                    370:                     }
                    371: 
                    372:                     if (objectHandle != null) {
                    373:                         objectHandle.Close();
                    374:                     }
                    375:                 }
                    376:             }
                    377:         }
                    378: 
                    379:         private static string GetHandleTypeToken(IntPtr handle) {
                    380:             NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, IntPtr.Zero, 0, out int length);
                    381:             IntPtr ptr = IntPtr.Zero;
                    382:             RuntimeHelpers.PrepareConstrainedRegions();
                    383: 
                    384:             try {
                    385:                 RuntimeHelpers.PrepareConstrainedRegions();
                    386: 
                    387:                 try { } finally {
                    388:                     if (length >= 0) {
                    389:                         ptr = Marshal.AllocHGlobal(length);
                    390:                     }
                    391:                 }
                    392: 
                    393:                 if (NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, ptr, length, out length) == NT_STATUS.STATUS_SUCCESS) {
                    394:                     return Marshal.PtrToStringUni((IntPtr)((int)ptr + 0x60));
                    395:                 }
                    396: 
                    397:             } finally {
                    398:                 Marshal.FreeHGlobal(ptr);
                    399:             }
                    400: 
                    401:             return string.Empty;
                    402:         }
                    403: 
                    404:         private static string GetHandleTypeToken(IntPtr handle, int processId) {
                    405:             IntPtr currentProcess = GetCurrentProcess();
                    406:             bool remote = processId != GetProcessId(currentProcess);
                    407:             SafeProcessHandle processHandle = null;
                    408:             SafeObjectHandle objectHandle = null;
                    409: 
                    410:             try {
                    411:                 if (remote) {
                    412:                     processHandle = OpenProcess(ProcessAccessRights.PROCESS_DUP_HANDLE, true, processId);
                    413:                     if (DuplicateHandle(processHandle.DangerousGetHandle(), handle, currentProcess, out objectHandle, 0, false, DuplicateHandleOptions.DUPLICATE_SAME_ACCESS)) {
                    414:                         handle = objectHandle.DangerousGetHandle();
                    415:                     }
                    416:                 }
                    417: 
                    418:                 return GetHandleTypeToken(handle);
                    419:             } finally {
                    420:                 if (remote) {
                    421:                     if (processHandle != null) {
                    422:                         processHandle.Close();
                    423:                     }
                    424: 
                    425:                     if (objectHandle != null) {
                    426:                         objectHandle.Close();
                    427:                     }
                    428:                 }
                    429:             }
                    430:         }
                    431: 
                    432:         private static bool GetHandleTypeFromToken(string token, out SystemHandleType handleType) {
                    433:             for (int i = 1; i < handleTypeTokenCount; i++) {
                    434:                 if (handleTypeTokens[i] == token) {
                    435:                     handleType = (SystemHandleType) i;
                    436:                     return true;
                    437:                 }
                    438:             }
                    439: 
                    440:             handleType = SystemHandleType.OB_TYPE_UNKNOWN;
                    441:             return false;
                    442:         }
                    443: 
                    444:         private static bool GetHandleType(IntPtr handle, int processId, out SystemHandleType handleType) {
                    445:             string token = GetHandleTypeToken(handle, processId);
                    446:             return GetHandleTypeFromToken(token, out handleType);
                    447:         }
                    448: 
                    449:         private static bool ConvertDevicePathToDosPath(string devicePath, out string dosPath) {
                    450:             EnsureDeviceMap();
                    451:             int i = devicePath.Length;
                    452: 
                    453:             while (i > 0 && (i = devicePath.LastIndexOf('\\', i - 1)) != -1) {
                    454:                 if (deviceMap.TryGetValue(devicePath.Substring(0, i), out string drive)) {
                    455:                     dosPath = string.Concat(drive, devicePath.Substring(i));
                    456:                     return dosPath.Length != 0;
                    457:                 }
                    458:             }
                    459: 
                    460:             dosPath = string.Empty;
                    461:             return false;
                    462:         }
                    463: 
                    464:         private static void EnsureDeviceMap() {
                    465:             if (deviceMap == null) {
                    466:                 Dictionary<string, string> localDeviceMap = BuildDeviceMap();
                    467:                 Interlocked.CompareExchange(ref deviceMap, localDeviceMap, null);
                    468:             }
                    469:         }
                    470: 
                    471:         private static Dictionary<string, string> BuildDeviceMap() {
                    472:             string[] logicalDrives = Environment.GetLogicalDrives();
                    473:             Dictionary<string, string> localDeviceMap = new Dictionary<string, string>(logicalDrives.Length);
                    474:             StringBuilder lpTargetPath = new StringBuilder(MAX_PATH);
                    475: 
                    476:             foreach (string drive in logicalDrives) {
                    477:                 string lpDeviceName = drive.Substring(0, 2);
                    478:                 QueryDosDevice(lpDeviceName, lpTargetPath, MAX_PATH);
                    479:                 localDeviceMap.Add(NormalizeDeviceName(lpTargetPath.ToString()), lpDeviceName);
                    480:             }
                    481: 
                    482:             localDeviceMap.Add(networkDevicePrefix.Substring(0, networkDevicePrefix.Length - 1), "\\");
                    483:             return localDeviceMap;
                    484:         }
                    485: 
                    486:         private static string NormalizeDeviceName(string deviceName) {
                    487:             if (string.Compare(deviceName, 0, networkDevicePrefix, 0, networkDevicePrefix.Length, StringComparison.InvariantCulture) == 0) {
                    488:                 string shareName = deviceName.Substring(deviceName.IndexOf('\\', networkDevicePrefix.Length) + 1);
                    489:                 return string.Concat(networkDevicePrefix, shareName);
                    490:             }
                    491: 
                    492:             return deviceName;
                    493:         }
                    494: 
                    495:         /// <summary>
                    496:         /// Gets the open files enumerator.
                    497:         /// </summary>
                    498:         /// <param name="processId">The process id.</param>
                    499:         /// <returns></returns>
                    500:         public static IEnumerable<string> GetOpenFilesEnumerator(int processId) {
                    501:             return new OpenFiles(processId);
                    502:         }
                    503: 
                    504:         public static List<Process> GetProcessesUsingFile(string fName) {
                    505:             List<Process> result = new List<Process>();
                    506:             foreach (Process p in Process.GetProcesses()) {
                    507:                 try {
                    508:                     if (GetOpenFilesEnumerator(p.Id).Contains(fName)) {
                    509:                         result.Add(p);
                    510:                     }
                    511:                 } catch { } // Some processes will fail.
                    512:             }
                    513:             return result;
                    514:         }
                    515: 
                    516:         public static void KillProcessesUsingFile(string fName) {
                    517:             foreach (Process process in GetProcessesUsingFile(fName).OrderBy(o => o.StartTime)) {
                    518:                 try {
                    519:                     process.Kill();
                    520:                     process.WaitForExit();
                    521:                 } catch {
                    522:                     // Oh well...
                    523:                 }
                    524:             }
                    525:         }
                    526:     }
                    527: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.