Files
TeslaRel410/emulator/pod-launch/JobObject.cs
T
CydandClaude Opus 4.8 9df87f2c01 Deploy: pod install scaffold -- supervisor entry-point, postinstall, packaging
Network-agnostic, air-gapped pod install for the two DOSBox titles (BT/RP 4.10),
fitting the existing TeslaConsole/TeslaLauncher pod-bay architecture.

emulator/DEPLOYMENT-PLAN.md
  Full design: bridge on the one NIC (pods form a source-proven P2P TCP mesh, so
  NAT/slirp is out); launcher keeps the bay IP, the DOSBox guest bridges at
  bayIP+100 (egg/mesh/game endpoint); two-edit +100 convention (postinstall +
  console DOSBox flag); static, air-gapped, <=32 pods.

emulator/pod-launch/  (C# net8 supervising entry-point)
  Creates a Job Object (KILL_ON_JOB_CLOSE), launches DOSBox-X + the render bridge
  into it and blocks -- kill this process and both die (kernel-enforced, even on
  a hard TerminateProcess of a hung session; verified). Mode dispatch: bt/rp
  wired (also serve camera + live mission-review via egg hostType); review +
  diagnostics recognized but fail clean until their DOS launch args are known.

emulator/deploy/  (install side)
  postinstall.bat (thin elevated wrapper) + configure.ps1 (NIC detect, realnic
  bind as a contiguous letter-leading GUID fragment, game IP = bayIP+100, stable
  MAC, render net_*.conf templates, stamp WATTCP.CFG my_ip) + tokenized conf
  templates + package.ps1 (assemble the zip). Render/bind/stamp + packaging
  verified against a scratch tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 10:36:30 -05:00

110 lines
4.2 KiB
C#

// A Windows Job Object configured to KILL every process in it the instant the
// job's last handle closes. This process holds that handle for the whole
// session, so when it dies -- gracefully, on Ctrl-C, or on a hard
// TerminateProcess by TeslaLauncher -- DOSBox and the render bridge die too.
// Kernel-enforced; no cooperative shutdown required (the hung-DOSBox case).
using System;
using System.Runtime.InteropServices;
namespace VwePod
{
internal sealed class JobObject : IDisposable
{
private IntPtr _handle;
private bool _disposed;
public JobObject()
{
_handle = CreateJobObject(IntPtr.Zero, null);
if (_handle == IntPtr.Zero)
throw new InvalidOperationException("CreateJobObject failed: " + Marshal.GetLastWin32Error());
var ext = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
BasicLimitInformation = new JOBOBJECT_BASIC_LIMIT_INFORMATION
{
LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
}
};
int len = Marshal.SizeOf(ext);
IntPtr p = Marshal.AllocHGlobal(len);
try
{
Marshal.StructureToPtr(ext, p, false);
if (!SetInformationJobObject(_handle, JobObjectExtendedLimitInformation, p, (uint)len))
throw new InvalidOperationException("SetInformationJobObject failed: " + Marshal.GetLastWin32Error());
}
finally { Marshal.FreeHGlobal(p); }
}
// Assign a process to the job. Any process the job member later spawns is
// automatically in the job too, so a whole tree is captured.
public void Add(IntPtr processHandle)
{
if (!AssignProcessToJobObject(_handle, processHandle))
throw new InvalidOperationException("AssignProcessToJobObject failed: " + Marshal.GetLastWin32Error());
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
if (_handle != IntPtr.Zero)
{
CloseHandle(_handle); // last handle -> KILL_ON_JOB_CLOSE fires
_handle = IntPtr.Zero;
}
}
// ---- native ----
private const int JobObjectExtendedLimitInformation = 9;
private const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000;
[StructLayout(LayoutKind.Sequential)]
private struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public long PerProcessUserTimeLimit;
public long PerJobUserTimeLimit;
public uint LimitFlags;
public UIntPtr MinimumWorkingSetSize;
public UIntPtr MaximumWorkingSetSize;
public uint ActiveProcessLimit;
public UIntPtr Affinity;
public uint PriorityClass;
public uint SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
private struct IO_COUNTERS
{
public ulong ReadOperationCount, WriteOperationCount, OtherOperationCount;
public ulong ReadTransferCount, WriteTransferCount, OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
private struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UIntPtr ProcessMemoryLimit;
public UIntPtr JobMemoryLimit;
public UIntPtr PeakProcessMemoryUsed;
public UIntPtr PeakJobMemoryUsed;
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string lpName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetInformationJobObject(IntPtr hJob, int infoClass, IntPtr lpInfo, uint cbInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
}
}