Add `dev ps` command

pull/440/head
NextTurn 2020-08-02 00:00:00 +08:00 committed by Next Turn
parent fcbc087b4b
commit 9573a78668
4 changed files with 170 additions and 50 deletions

View File

@ -139,6 +139,24 @@ namespace WinSW.Native
internal Service(IntPtr handle) => this.handle = handle;
internal unsafe int ProcessId
{
get
{
if (!QueryServiceStatusEx(
this.handle,
ServiceStatusType.ProcessInfo,
out SERVICE_STATUS_PROCESS status,
sizeof(SERVICE_STATUS_PROCESS),
out _))
{
Throw.Command.Win32Exception("Failed to query service status.");
}
return status.CurrentState == ServiceControllerStatus.Running ? status.ProcessId : -1;
}
}
/// <exception cref="CommandException" />
internal ServiceControllerStatus Status
{

View File

@ -65,6 +65,14 @@ namespace WinSW.Native
[DllImport(Libraries.Advapi32, SetLastError = true)]
internal static extern bool QueryServiceStatus(IntPtr serviceHandle, out SERVICE_STATUS serviceStatus);
[DllImport(Libraries.Advapi32, SetLastError = true)]
internal static extern bool QueryServiceStatusEx(
IntPtr serviceHandle,
ServiceStatusType infoLevel,
out SERVICE_STATUS_PROCESS buffer,
int bufferSize,
out int bytesNeeded);
[DllImport(Libraries.Advapi32, SetLastError = true)]
internal static extern bool SetServiceObjectSecurity(IntPtr serviceHandle, SecurityInfos securityInformation, byte[] securityDescriptor);
@ -145,6 +153,12 @@ namespace WinSW.Native
MODIFY_BOOT_CONFIG,
}
// SC_STATUS_
internal enum ServiceStatusType
{
ProcessInfo = 0,
}
internal struct SERVICE_DELAYED_AUTO_START_INFO
{
public bool DelayedAutostart;
@ -182,5 +196,18 @@ namespace WinSW.Native
public int CheckPoint;
public int WaitHint;
}
internal struct SERVICE_STATUS_PROCESS
{
public ServiceType ServiceType;
public ServiceControllerStatus CurrentState;
public int ControlsAccepted;
public int Win32ExitCode;
public int ServiceSpecificExitCode;
public int CheckPoint;
public int WaitHint;
public int ProcessId;
public int ServiceFlags;
}
}
}

View File

@ -15,20 +15,72 @@ namespace WinSW.Util
{
Stop(process, stopTimeout);
foreach (Process child in GetDescendants(process))
foreach (Process child in GetChildren(process))
{
StopTree(child, stopTimeout);
using (child)
{
StopTree(child, stopTimeout);
}
}
}
internal static void StopDescendants(this Process process, TimeSpan stopTimeout)
{
foreach (Process child in GetDescendants(process))
foreach (Process child in GetChildren(process))
{
StopTree(child, stopTimeout);
using (child)
{
StopTree(child, stopTimeout);
}
}
}
internal static unsafe List<Process> GetChildren(this Process process)
{
DateTime startTime = process.StartTime;
int processId = process.Id;
var children = new List<Process>();
foreach (Process other in Process.GetProcesses())
{
try
{
if (other.StartTime <= startTime)
{
goto Next;
}
IntPtr handle = other.Handle;
if (NtQueryInformationProcess(
handle,
PROCESSINFOCLASS.ProcessBasicInformation,
out PROCESS_BASIC_INFORMATION information,
sizeof(PROCESS_BASIC_INFORMATION)) != 0)
{
goto Next;
}
if ((int)information.InheritedFromUniqueProcessId == processId)
{
Logger.Info($"Found child process '{other.Format()}'.");
children.Add(other);
continue;
}
Next:
other.Dispose();
}
catch (Exception e) when (e is InvalidOperationException || e is Win32Exception)
{
other.Dispose();
}
}
return children;
}
private static void Stop(Process process, TimeSpan stopTimeout)
{
Logger.Info("Stopping process " + process.Id);
@ -61,51 +113,5 @@ namespace WinSW.Util
// TODO: Propagate error if process kill fails? Currently we use the legacy behavior
}
private static unsafe List<Process> GetDescendants(Process root)
{
DateTime startTime = root.StartTime;
int processId = root.Id;
var children = new List<Process>();
foreach (Process process in Process.GetProcesses())
{
try
{
if (process.StartTime <= startTime)
{
goto Next;
}
IntPtr handle = process.Handle;
if (NtQueryInformationProcess(
handle,
PROCESSINFOCLASS.ProcessBasicInformation,
out PROCESS_BASIC_INFORMATION information,
sizeof(PROCESS_BASIC_INFORMATION)) != 0)
{
goto Next;
}
if ((int)information.InheritedFromUniqueProcessId == processId)
{
Logger.Info($"Found child process '{process.Format()}'.");
children.Add(process);
continue;
}
Next:
process.Dispose();
}
catch (Exception e) when (e is InvalidOperationException || e is Win32Exception)
{
process.Dispose();
}
}
return children;
}
}
}

View File

@ -26,6 +26,7 @@ using log4net.Core;
using log4net.Layout;
using WinSW.Logging;
using WinSW.Native;
using WinSW.Util;
using Process = System.Diagnostics.Process;
using TimeoutException = System.ServiceProcess.TimeoutException;
@ -238,6 +239,22 @@ namespace WinSW
root.Add(refresh);
}
{
var dev = new Command("dev");
dev.Add(config);
dev.Add(noElevate);
root.Add(dev);
var ps = new Command("ps")
{
Handler = CommandHandler.Create<string?, bool>(DevPs),
};
dev.Add(ps);
}
return new CommandLineBuilder(root)
// see UseDefaults
@ -774,6 +791,58 @@ namespace WinSW
}
}
void DevPs(string? pathToConfig, bool noElevate)
{
XmlServiceConfig config = XmlServiceConfig.Create(pathToConfig);
if (!elevated)
{
Elevate(noElevate);
return;
}
using ServiceManager scm = ServiceManager.Open();
using Service sc = scm.OpenService(config.Id);
int processId = sc.ProcessId;
if (processId >= 0)
{
const string Vertical = " \u2502 ";
const string Corner = " \u2514\u2500";
const string Cross = " \u251c\u2500";
const string Space = " ";
using Process process = Process.GetProcessById(processId);
Draw(process, string.Empty, true);
static void Draw(Process process, string indentation, bool isLastChild)
{
Console.Write(indentation);
if (isLastChild)
{
Console.Write(Corner);
indentation += Space;
}
else
{
Console.Write(Cross);
indentation += Vertical;
}
Console.WriteLine(process.Format());
List<Process> children = process.GetChildren();
int count = children.Count;
for (int i = 0; i < count; i++)
{
using Process child = children[i];
Draw(child, indentation, i == count - 1);
}
}
}
}
// [DoesNotReturn]
static void Elevate(bool noElevate)
{