mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-23 04:57:17 +01:00
Merge branch 'dev' into 979-Postgres
This commit is contained in:
+3
-3
@@ -2,9 +2,9 @@
|
||||
<PropertyGroup>
|
||||
<!-- This is the authorative version list -->
|
||||
<!-- Integration tests will ensure they match across the board -->
|
||||
<TgsCoreVersion>4.2.1</TgsCoreVersion>
|
||||
<TgsApiVersion>6.2.0</TgsApiVersion>
|
||||
<TgsClientVersion>6.1.0</TgsClientVersion>
|
||||
<TgsCoreVersion>4.2.2</TgsCoreVersion>
|
||||
<TgsApiVersion>6.3.0</TgsApiVersion>
|
||||
<TgsClientVersion>6.2.0</TgsClientVersion>
|
||||
<TgsDmapiVersion>5.1.1</TgsDmapiVersion>
|
||||
<TgsControlPanelVersion>0.4.0</TgsControlPanelVersion>
|
||||
<TgsHostWatchdogVersion>1.1.0</TgsHostWatchdogVersion>
|
||||
|
||||
@@ -471,5 +471,17 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
[Description("Cannot set both softShutdown and softReboot at once!")]
|
||||
DreamDaemonDoubleSoft,
|
||||
|
||||
/// <summary>
|
||||
/// Attempted to launch DreamDaemon on a user account that had the BYOND pager running.
|
||||
/// </summary>
|
||||
[Description("Cannot start DreamDaemon headless with the BYOND pager running!")]
|
||||
DeploymentPagerRunning,
|
||||
|
||||
/// <summary>
|
||||
/// Could not bind to port we wanted to launch DreamDaemon on.
|
||||
/// </summary>
|
||||
[Description("Could not bind to requested DreamDaemon port! Is there another service running on that port?")]
|
||||
DreamDaemonPortInUse,
|
||||
}
|
||||
}
|
||||
@@ -225,7 +225,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
return channelModel;
|
||||
}
|
||||
|
||||
Logger.LogWarning("Cound not map channel {0}! Incorrect type: {1}", channelId, discordChannel.GetType());
|
||||
Logger.LogWarning("Cound not map channel {0}! Incorrect type: {1}", channelId, discordChannel?.GetType());
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,11 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
|
||||
/// <summary>
|
||||
/// The text to reply with as the result of a <see cref="TopicCommandType.ChatCommand"/> request, if any.
|
||||
/// </summary>
|
||||
public string CommandResponseMessage { get; private set; }
|
||||
public string CommandResponseMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ChatMessage"/>s to send as the result of a <see cref="TopicCommandType.EventNotification"/> request, if any.
|
||||
/// </summary>
|
||||
public ICollection<ChatMessage> ChatResponses { get; private set; }
|
||||
public ICollection<ChatMessage> ChatResponses { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
@@ -110,6 +112,24 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a given <paramref name="port"/> can be bound to.
|
||||
/// </summary>
|
||||
/// <param name="port">The port number to test.</param>
|
||||
static void PortBindTest(ushort port)
|
||||
{
|
||||
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
|
||||
try
|
||||
{
|
||||
socket.Bind(new IPEndPoint(IPAddress.Loopback, port));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new JobException(ErrorCode.DreamDaemonPortInUse, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="SessionControllerFactory"/>
|
||||
/// </summary>
|
||||
@@ -201,7 +221,8 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
if (launchParameters.SecurityLevel == DreamDaemonSecurity.Trusted)
|
||||
await byondLock.TrustDmbPath(ioManager.ConcatPath(basePath, dmbProvider.DmbName), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
CheckPagerIsNotRunning();
|
||||
PortBindTest(portToUse.Value);
|
||||
await CheckPagerIsNotRunning(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var accessIdentifier = cryptographySuite.GetSecureString();
|
||||
|
||||
@@ -214,7 +235,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
// important to run on all ports to allow port changing
|
||||
var arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} -ports 1-65535 {2}-close -{3} -{4} -public -params \"{5}\"",
|
||||
dmbProvider.DmbName,
|
||||
primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort,
|
||||
portToUse,
|
||||
launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty,
|
||||
SecurityWord(launchParameters.SecurityLevel.Value),
|
||||
visibility,
|
||||
@@ -403,10 +424,24 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
/// <summary>
|
||||
/// Make sure the BYOND pager is not running.
|
||||
/// </summary>
|
||||
void CheckPagerIsNotRunning()
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
async Task CheckPagerIsNotRunning(CancellationToken cancellationToken)
|
||||
{
|
||||
if (platformIdentifier.IsWindows && processExecutor.IsProcessWithNameRunning("byond"))
|
||||
throw new JobException("Cannot start DreamDaemon headless with the BYOND pager running!");
|
||||
if (!platformIdentifier.IsWindows)
|
||||
return;
|
||||
|
||||
using var otherProcess = processExecutor.GetProcessByName("byond");
|
||||
if (otherProcess == null)
|
||||
return;
|
||||
|
||||
var otherUsernameTask = otherProcess.GetExecutingUsername(cancellationToken);
|
||||
using var ourProcess = processExecutor.GetCurrentProcess();
|
||||
var ourUserName = await ourProcess.GetExecutingUsername(cancellationToken).ConfigureAwait(false);
|
||||
var otherUserName = await otherUsernameTask.ConfigureAwait(false);
|
||||
|
||||
if(otherUserName.Equals(ourUserName, StringComparison.Ordinal))
|
||||
throw new JobException(ErrorCode.DeploymentPagerRunning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,6 +471,8 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
var scriptOutput = script.GetCombinedOutput();
|
||||
if (exitCode != 0)
|
||||
throw new JobException($"Script {I} exited with code {exitCode}:{Environment.NewLine}{scriptOutput}");
|
||||
else
|
||||
logger.LogDebug("Script output:{0}{1}", Environment.NewLine, scriptOutput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
PrimaryPort = 1337,
|
||||
SecondaryPort = 1338,
|
||||
SecurityLevel = DreamDaemonSecurity.Safe,
|
||||
StartupTimeout = 20,
|
||||
StartupTimeout = 60,
|
||||
HeartbeatSeconds = 60
|
||||
},
|
||||
DreamMakerSettings = new DreamMakerSettings
|
||||
|
||||
@@ -254,7 +254,7 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddSingleton<ISymlinkFactory, WindowsSymlinkFactory>();
|
||||
services.AddSingleton<IByondInstaller, WindowsByondInstaller>();
|
||||
services.AddSingleton<IPostWriteHandler, WindowsPostWriteHandler>();
|
||||
services.AddSingleton<IProcessSuspender, WindowsProcessSuspender>();
|
||||
services.AddSingleton<IProcessFeatures, WindowsProcessFeatures>();
|
||||
|
||||
services.AddSingleton<WindowsNetworkPromptReaper>();
|
||||
services.AddSingleton<INetworkPromptReaper>(x => x.GetRequiredService<WindowsNetworkPromptReaper>());
|
||||
@@ -267,7 +267,7 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddSingleton<ISymlinkFactory, PosixSymlinkFactory>();
|
||||
services.AddSingleton<IByondInstaller, PosixByondInstaller>();
|
||||
services.AddSingleton<IPostWriteHandler, PosixPostWriteHandler>();
|
||||
services.AddSingleton<IProcessSuspender, PosixProcessSuspender>();
|
||||
services.AddSingleton<IProcessFeatures, PosixProcessFeatures>();
|
||||
services.AddSingleton<INetworkPromptReaper, PosixNetworkPromptReaper>();
|
||||
}
|
||||
|
||||
@@ -369,6 +369,7 @@ namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
applicationBuilder.UseSwagger();
|
||||
applicationBuilder.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TGS API V4"));
|
||||
logger.LogTrace("Swagger API generation enabled");
|
||||
}
|
||||
|
||||
// Set up CORS based on configuration if necessary
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.System
|
||||
{
|
||||
@@ -39,5 +40,12 @@ namespace Tgstation.Server.Host.System
|
||||
/// Terminates the process
|
||||
/// </summary>
|
||||
void Terminate();
|
||||
|
||||
/// <summary>
|
||||
/// Get the name of the account executing the <see cref="IProcess"/>.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the name of the account executing the <see cref="IProcess"/>.</returns>
|
||||
Task<string> GetExecutingUsername(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -18,17 +18,23 @@
|
||||
IProcess LaunchProcess(string fileName, string workingDirectory, string arguments = null, bool readOutput = false, bool readError = false, bool noShellExecute = false);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="IProcess"/> by <paramref name="id"/>
|
||||
/// Get a <see cref="IProcess"/> representing the running executable.
|
||||
/// </summary>
|
||||
/// <param name="id">The <see cref="IProcess.Id"/></param>
|
||||
/// <returns>The <see cref="IProcess"/> represented by <paramref name="id"/> on success, <see langword="null"/> on failure</returns>
|
||||
/// <returns>The current <see cref="IProcess"/>.</returns>
|
||||
IProcess GetCurrentProcess();
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="IProcess"/> by <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The <see cref="IProcess.Id"/>.</param>
|
||||
/// <returns>The <see cref="IProcess"/> represented by <paramref name="id"/> on success, <see langword="null"/> on failure.</returns>
|
||||
IProcess GetProcess(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Check if a <see cref="IProcess"/> with a given <paramref name="name"/> is running.
|
||||
/// Get a <see cref="IProcess"/> with a given <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the process without the extension.</param>
|
||||
/// <returns><see langword="true"/> if the process is running, <see langword="false"/> otherwise.</returns>
|
||||
bool IsProcessWithNameRunning(string name);
|
||||
/// <param name="name">The name of the process executable without the extension.</param>
|
||||
/// <returns>The <see cref="IProcess"/> represented by <paramref name="name"/> on success, <see langword="null"/> on failure.</returns>
|
||||
IProcess GetProcessByName(string name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.System
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstraction for suspending and resuming processes.
|
||||
/// </summary>
|
||||
interface IProcessFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the name of the user executing a given <paramref name="process"/>.
|
||||
/// </summary>
|
||||
/// <param name="process">The <see cref="global::System.Diagnostics.Process"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>The name of the user executing <paramref name="process"/>.</returns>
|
||||
Task<string> GetExecutingUsername(global::System.Diagnostics.Process process, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Suspend a given <see cref="Process"/>.
|
||||
/// </summary>
|
||||
/// <param name="process">The <see cref="Process"/> to suspend.</param>
|
||||
void SuspendProcess(global::System.Diagnostics.Process process);
|
||||
|
||||
/// <summary>
|
||||
/// Resume a given suspended <see cref="Process"/>.
|
||||
/// </summary>
|
||||
/// <param name="process">The <see cref="Process"/> to susperesumend.</param>
|
||||
void ResumeProcess(global::System.Diagnostics.Process process);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
namespace Tgstation.Server.Host.System
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstraction for suspending and resuming processes.
|
||||
/// </summary>
|
||||
interface IProcessSuspender
|
||||
{
|
||||
/// <summary>
|
||||
/// Suspend a given <see cref="Process"/>.
|
||||
/// </summary>
|
||||
/// <param name="process">The <see cref="Process"/> to suspend.</param>
|
||||
void SuspendProcess(global::System.Diagnostics.Process process);
|
||||
|
||||
/// <summary>
|
||||
/// Resume a given suspended <see cref="Process"/>.
|
||||
/// </summary>
|
||||
/// <param name="process">The <see cref="Process"/> to susperesumend.</param>
|
||||
void ResumeProcess(global::System.Diagnostics.Process process);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Mono.Unix;
|
||||
using Mono.Unix.Native;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
namespace Tgstation.Server.Host.System
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class PosixProcessFeatures : IProcessFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="PosixProcessFeatures"/>.
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger{TCategoryName}"/> for the <see cref="PosixProcessFeatures"/>.
|
||||
/// </summary>
|
||||
readonly ILogger<PosixProcessFeatures> logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PosixProcessFeatures"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
public PosixProcessFeatures(IIOManager ioManager, ILogger<PosixProcessFeatures> logger)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ResumeProcess(global::System.Diagnostics.Process process)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = Syscall.kill(process.Id, Signum.SIGCONT);
|
||||
if (result != 0)
|
||||
throw new UnixIOException(result);
|
||||
logger.LogTrace("Resumed PID {0}", process.Id);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, "Failed to resume PID {0}!", process.Id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SuspendProcess(global::System.Diagnostics.Process process)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = Syscall.kill(process.Id, Signum.SIGSTOP);
|
||||
if (result != 0)
|
||||
throw new UnixIOException(result);
|
||||
logger.LogTrace("Resumed PID {0}", process.Id);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, "Failed to suspend PID {0}!", process.Id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> GetExecutingUsername(global::System.Diagnostics.Process process, CancellationToken cancellationToken)
|
||||
{
|
||||
if (process == null)
|
||||
throw new ArgumentNullException(nameof(process));
|
||||
|
||||
// Need to read /proc/[pid]/status
|
||||
// http://man7.org/linux/man-pages/man5/proc.5.html
|
||||
// https://unix.stackexchange.com/questions/102676/why-is-uid-information-not-in-proc-x-stat
|
||||
var pid = process.Id;
|
||||
var statusFile = ioManager.ConcatPath("/proc", pid.ToString(CultureInfo.InvariantCulture), "status");
|
||||
var statusBytes = await ioManager.ReadAllBytes(statusFile, cancellationToken).ConfigureAwait(false);
|
||||
var statusText = Encoding.UTF8.GetString(statusBytes);
|
||||
var splits = statusText.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
var entry = splits.FirstOrDefault(x => x.Trim().StartsWith("Uid:", StringComparison.Ordinal));
|
||||
if (entry == default)
|
||||
return "UNKNOWN";
|
||||
|
||||
return entry
|
||||
.Substring(4)
|
||||
.Split(' ', StringSplitOptions.RemoveEmptyEntries)
|
||||
.FirstOrDefault(x => !String.IsNullOrWhiteSpace(x))
|
||||
?? "UNPARSABLE";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Mono.Unix;
|
||||
using Mono.Unix.Native;
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Host.System
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class PosixProcessSuspender : IProcessSuspender
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger{TCategoryName}"/> for the <see cref="PosixProcessSuspender"/>.
|
||||
/// </summary>
|
||||
readonly ILogger<PosixProcessSuspender> logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PosixProcessSuspender"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
public PosixProcessSuspender(ILogger<PosixProcessSuspender> logger)
|
||||
{
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ResumeProcess(global::System.Diagnostics.Process process)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = Syscall.kill(process.Id, Signum.SIGCONT);
|
||||
if (result != 0)
|
||||
throw new UnixIOException(result);
|
||||
logger.LogTrace("Resumed PID {0}", process.Id);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, "Failed to resume PID {0}!", process.Id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SuspendProcess(global::System.Diagnostics.Process process)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = Syscall.kill(process.Id, Signum.SIGSTOP);
|
||||
if (result != 0)
|
||||
throw new UnixIOException(result);
|
||||
logger.LogTrace("Resumed PID {0}", process.Id);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, "Failed to suspend PID {0}!", process.Id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.System
|
||||
@@ -19,9 +20,9 @@ namespace Tgstation.Server.Host.System
|
||||
public Task<int> Lifetime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IProcessSuspender"/> for the <see cref="Process"/>.
|
||||
/// The <see cref="IProcessFeatures"/> for the <see cref="Process"/>.
|
||||
/// </summary>
|
||||
readonly IProcessSuspender processSuspender;
|
||||
readonly IProcessFeatures processFeatures;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="Process"/>
|
||||
@@ -37,7 +38,7 @@ namespace Tgstation.Server.Host.System
|
||||
/// <summary>
|
||||
/// Construct a <see cref="Process"/>
|
||||
/// </summary>
|
||||
/// <param name="processSuspender">The value of <see cref="processSuspender"/></param>
|
||||
/// <param name="processFeatures">The value of <see cref="processFeatures"/></param>
|
||||
/// <param name="handle">The value of <see cref="handle"/></param>
|
||||
/// <param name="lifetime">The value of <see cref="Lifetime"/></param>
|
||||
/// <param name="outputStringBuilder">The value of <see cref="outputStringBuilder"/></param>
|
||||
@@ -46,7 +47,7 @@ namespace Tgstation.Server.Host.System
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="preExisting">If <paramref name="handle"/> was NOT just created</param>
|
||||
public Process(
|
||||
IProcessSuspender processSuspender,
|
||||
IProcessFeatures processFeatures,
|
||||
global::System.Diagnostics.Process handle,
|
||||
Task<int> lifetime,
|
||||
StringBuilder outputStringBuilder,
|
||||
@@ -55,7 +56,7 @@ namespace Tgstation.Server.Host.System
|
||||
ILogger<Process> logger,
|
||||
bool preExisting)
|
||||
{
|
||||
this.processSuspender = processSuspender ?? throw new ArgumentNullException(nameof(processSuspender));
|
||||
this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures));
|
||||
this.handle = handle ?? throw new ArgumentNullException(nameof(handle));
|
||||
|
||||
this.outputStringBuilder = outputStringBuilder;
|
||||
@@ -152,9 +153,17 @@ namespace Tgstation.Server.Host.System
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Suspend() => processSuspender.SuspendProcess(handle);
|
||||
public void Suspend() => processFeatures.SuspendProcess(handle);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Resume() => processSuspender.ResumeProcess(handle);
|
||||
public void Resume() => processFeatures.ResumeProcess(handle);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> GetExecutingUsername(CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await processFeatures.GetExecutingUsername(handle, cancellationToken).ConfigureAwait(false);
|
||||
logger.LogTrace("PID {0} Username: {1}", Id, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -10,9 +9,9 @@ namespace Tgstation.Server.Host.System
|
||||
sealed class ProcessExecutor : IProcessExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IProcessSuspender"/> for the <see cref="ProcessExecutor"/>.
|
||||
/// The <see cref="IProcessFeatures"/> for the <see cref="ProcessExecutor"/>.
|
||||
/// </summary>
|
||||
readonly IProcessSuspender processSuspender;
|
||||
readonly IProcessFeatures processFeatures;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="ProcessExecutor"/>
|
||||
@@ -55,15 +54,15 @@ namespace Tgstation.Server.Host.System
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ProcessExecutor"/>
|
||||
/// </summary>
|
||||
/// <param name="processSuspender">The value of <see cref="processSuspender"/>.</param>
|
||||
/// <param name="processFeatures">The value of <see cref="processFeatures"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
|
||||
public ProcessExecutor(
|
||||
IProcessSuspender processSuspender,
|
||||
IProcessFeatures processFeatures,
|
||||
ILogger<ProcessExecutor> logger,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
this.processSuspender = processSuspender ?? throw new ArgumentNullException(nameof(processSuspender));
|
||||
this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
}
|
||||
@@ -83,23 +82,15 @@ namespace Tgstation.Server.Host.System
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return new Process(
|
||||
processSuspender,
|
||||
handle,
|
||||
AttachExitHandler(handle),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
loggerFactory.CreateLogger<Process>(),
|
||||
true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
handle.Dispose();
|
||||
throw;
|
||||
}
|
||||
return CreateFromExistingHandle(handle);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IProcess GetCurrentProcess()
|
||||
{
|
||||
logger.LogTrace("Getting current process...");
|
||||
var handle = global::System.Diagnostics.Process.GetCurrentProcess();
|
||||
return CreateFromExistingHandle(handle);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -215,7 +206,7 @@ namespace Tgstation.Server.Host.System
|
||||
catch (InvalidOperationException) { }
|
||||
|
||||
return new Process(
|
||||
processSuspender,
|
||||
processFeatures,
|
||||
handle,
|
||||
lifetimeTask,
|
||||
outputStringBuilder,
|
||||
@@ -231,13 +222,50 @@ namespace Tgstation.Server.Host.System
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsProcessWithNameRunning(string name)
|
||||
public IProcess GetProcessByName(string name)
|
||||
{
|
||||
logger.LogTrace("GetProcessByName: {0}...", name ?? throw new ArgumentNullException(nameof(name)));
|
||||
var procs = global::System.Diagnostics.Process.GetProcessesByName(name);
|
||||
global::System.Diagnostics.Process handle = null;
|
||||
foreach (var proc in procs)
|
||||
proc.Dispose();
|
||||
if (handle == null)
|
||||
handle = proc;
|
||||
else
|
||||
{
|
||||
logger.LogTrace("Disposing extra found PID: {0}", proc.Id);
|
||||
proc.Dispose();
|
||||
}
|
||||
|
||||
return procs.Any();
|
||||
if (handle == null)
|
||||
return null;
|
||||
|
||||
return CreateFromExistingHandle(handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="IProcess"/> given an existing <paramref name="handle"/>.
|
||||
/// </summary>
|
||||
/// <param name="handle">The <see cref="global::System.Diagnostics.Process"/> to create a <see cref="IProcess"/> from.</param>
|
||||
/// <returns>The <see cref="IProcess"/> based on <paramref name="handle"/>.</returns>
|
||||
private IProcess CreateFromExistingHandle(global::System.Diagnostics.Process handle)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new Process(
|
||||
processFeatures,
|
||||
handle,
|
||||
AttachExitHandler(handle),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
loggerFactory.CreateLogger<Process>(),
|
||||
true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
handle.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+42
-5
@@ -2,22 +2,26 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.System
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class WindowsProcessSuspender : IProcessSuspender
|
||||
sealed class WindowsProcessFeatures : IProcessFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger{TCategoryName}"/> for the <see cref="WindowsProcessSuspender"/>.
|
||||
/// The <see cref="ILogger{TCategoryName}"/> for the <see cref="WindowsProcessFeatures"/>.
|
||||
/// </summary>
|
||||
readonly ILogger<WindowsProcessSuspender> logger;
|
||||
readonly ILogger<WindowsProcessFeatures> logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WindowsProcessSuspender"/> <see langword="class"/>.
|
||||
/// Initializes a new instance of the <see cref="WindowsProcessFeatures"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
public WindowsProcessSuspender(ILogger<WindowsProcessSuspender> logger)
|
||||
public WindowsProcessFeatures(ILogger<WindowsProcessFeatures> logger)
|
||||
{
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
@@ -25,6 +29,9 @@ namespace Tgstation.Server.Host.System
|
||||
/// <inheritdoc />
|
||||
public void ResumeProcess(global::System.Diagnostics.Process process)
|
||||
{
|
||||
if (process == null)
|
||||
throw new ArgumentNullException(nameof(process));
|
||||
|
||||
try
|
||||
{
|
||||
foreach (ProcessThread thread in process.Threads)
|
||||
@@ -51,6 +58,9 @@ namespace Tgstation.Server.Host.System
|
||||
/// <inheritdoc />
|
||||
public void SuspendProcess(global::System.Diagnostics.Process process)
|
||||
{
|
||||
if (process == null)
|
||||
throw new ArgumentNullException(nameof(process));
|
||||
|
||||
try
|
||||
{
|
||||
foreach (ProcessThread thread in process.Threads)
|
||||
@@ -73,5 +83,32 @@ namespace Tgstation.Server.Host.System
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<string> GetExecutingUsername(global::System.Diagnostics.Process process, CancellationToken cancellationToken)
|
||||
{
|
||||
string query = $"SELECT * FROM Win32_Process WHERE ProcessId = {process?.Id ?? throw new ArgumentNullException(nameof(process))}";
|
||||
using var searcher = new ManagementObjectSearcher(query);
|
||||
foreach (ManagementObject obj in searcher.Get())
|
||||
{
|
||||
var argList = new string[] { String.Empty, String.Empty };
|
||||
var returnString = obj.InvokeMethod(
|
||||
"GetOwner",
|
||||
argList)
|
||||
?.ToString();
|
||||
|
||||
if (!Int32.TryParse(returnString, out var returnVal))
|
||||
return Task.FromResult($"BAD RETURN PARSE: {returnString}");
|
||||
|
||||
if (returnVal == 0)
|
||||
{
|
||||
// return DOMAIN\user
|
||||
string owner = argList.Last() + "\\" + argList.First();
|
||||
return Task.FromResult(owner);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult("NO OWNER");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,7 @@
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.1" />
|
||||
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="4.7.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.5.1" />
|
||||
<PackageReference Include="System.Management" Version="4.7.0" />
|
||||
<PackageReference Include="Wangkanai.Detection.Browser" Version="2.0.0" />
|
||||
<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="3.0.50" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using Castle.Core.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
namespace Tgstation.Server.Host.System.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for <see cref="IProcessFeatures"/>.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public sealed class TestProcessFeatures
|
||||
{
|
||||
IProcessFeatures features;
|
||||
|
||||
[TestInitialize]
|
||||
public void Init()
|
||||
{
|
||||
features = new PlatformIdentifier().IsWindows
|
||||
? (IProcessFeatures)new WindowsProcessFeatures(Mock.Of<ILogger<WindowsProcessFeatures>>())
|
||||
: new PosixProcessFeatures(new DefaultIOManager(), Mock.Of<ILogger<PosixProcessFeatures>>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestGetUsername()
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TRAVIS")))
|
||||
Assert.Inconclusive("This test doesn't work on TRAVIS CI!");
|
||||
|
||||
var username = await features.GetExecutingUsername(global::System.Diagnostics.Process.GetCurrentProcess(), default);
|
||||
Assert.IsTrue(username.Contains(Environment.UserName), $"Exepcted a string containing \"{Environment.UserName}\", got \"{username}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Client;
|
||||
using Tgstation.Server.Client.Components;
|
||||
using Tgstation.Server.Host.Components.Interop;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Tests.Instance
|
||||
@@ -101,8 +102,8 @@ namespace Tgstation.Server.Tests.Instance
|
||||
using var ddProc = ddProcs.Single();
|
||||
using var ourProcessHandler = new ProcessExecutor(
|
||||
new PlatformIdentifier().IsWindows
|
||||
? (IProcessSuspender)new WindowsProcessSuspender(Mock.Of<ILogger<WindowsProcessSuspender>>())
|
||||
: new PosixProcessSuspender(Mock.Of<ILogger<PosixProcessSuspender>>()),
|
||||
? (IProcessFeatures)new WindowsProcessFeatures(Mock.Of<ILogger<WindowsProcessFeatures>>())
|
||||
: new PosixProcessFeatures(Mock.Of<IIOManager>(), Mock.Of<ILogger<PosixProcessFeatures>>()),
|
||||
Mock.Of<ILogger<ProcessExecutor>>(),
|
||||
LoggerFactory.Create(x => { }))
|
||||
.GetProcess(ddProc.Id);
|
||||
|
||||
@@ -301,7 +301,7 @@ namespace Tgstation.Server.Tests
|
||||
{
|
||||
var platformIdentifier = new PlatformIdentifier();
|
||||
var processExecutor = new ProcessExecutor(
|
||||
Mock.Of<IProcessSuspender>(),
|
||||
Mock.Of<IProcessFeatures>(),
|
||||
Mock.Of<ILogger<ProcessExecutor>>(),
|
||||
LoggerFactory.Create(x => { }));
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Tgstation.Server.Tests
|
||||
}
|
||||
|
||||
System.IO.Directory.CreateDirectory(Directory);
|
||||
const string UrlString = "http://localhost:5001";
|
||||
const string UrlString = "http://localhost:5010";
|
||||
Url = new Uri(UrlString);
|
||||
|
||||
//so we need a db
|
||||
|
||||
Reference in New Issue
Block a user