tgstation-server  4.4.0
The /tg/station 13 server suite
PosixProcessFeatures.cs
Go to the documentation of this file.
1 using Microsoft.Extensions.Logging;
2 using Mono.Unix;
3 using Mono.Unix.Native;
4 using System;
5 using System.Globalization;
6 using System.Linq;
7 using System.Text;
8 using System.Threading;
9 using System.Threading.Tasks;
11 using Tgstation.Server.Host.IO;
13 
14 namespace Tgstation.Server.Host.System
15 {
18  {
22  readonly Lazy<IProcessExecutor> lazyLoadedProcessExecutor;
23 
28 
32  readonly ILogger<PosixProcessFeatures> logger;
33 
40  public PosixProcessFeatures(Lazy<IProcessExecutor> lazyLoadedProcessExecutor, IIOManager ioManager, ILogger<PosixProcessFeatures> logger)
41  {
42  this.lazyLoadedProcessExecutor = lazyLoadedProcessExecutor ?? throw new ArgumentNullException(nameof(lazyLoadedProcessExecutor));
43  this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
44  this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
45  }
46 
48  public void ResumeProcess(global::System.Diagnostics.Process process)
49  {
50  try
51  {
52  var result = Syscall.kill(process.Id, Signum.SIGCONT);
53  if (result != 0)
54  throw new UnixIOException(result);
55  logger.LogTrace("Resumed PID {0}", process.Id);
56  }
57  catch (Exception e)
58  {
59  logger.LogError(e, "Failed to resume PID {0}!", process.Id);
60  throw;
61  }
62  }
63 
65  public void SuspendProcess(global::System.Diagnostics.Process process)
66  {
67  try
68  {
69  var result = Syscall.kill(process.Id, Signum.SIGSTOP);
70  if (result != 0)
71  throw new UnixIOException(result);
72  logger.LogTrace("Resumed PID {0}", process.Id);
73  }
74  catch (Exception e)
75  {
76  logger.LogError(e, "Failed to suspend PID {0}!", process.Id);
77  throw;
78  }
79  }
80 
82  public async Task<string> GetExecutingUsername(global::System.Diagnostics.Process process, CancellationToken cancellationToken)
83  {
84  if (process == null)
85  throw new ArgumentNullException(nameof(process));
86 
87  // Need to read /proc/[pid]/status
88  // http://man7.org/linux/man-pages/man5/proc.5.html
89  // https://unix.stackexchange.com/questions/102676/why-is-uid-information-not-in-proc-x-stat
90  var pid = process.Id;
91  var statusFile = ioManager.ConcatPath("/proc", pid.ToString(CultureInfo.InvariantCulture), "status");
92  var statusBytes = await ioManager.ReadAllBytes(statusFile, cancellationToken).ConfigureAwait(false);
93  var statusText = Encoding.UTF8.GetString(statusBytes);
94  var splits = statusText.Split('\n', StringSplitOptions.RemoveEmptyEntries);
95  var entry = splits.FirstOrDefault(x => x.Trim().StartsWith("Uid:", StringComparison.Ordinal));
96  if (entry == default)
97  return "UNKNOWN";
98 
99  return entry
100  .Substring(4)
101  .Split(' ', StringSplitOptions.RemoveEmptyEntries)
102  .FirstOrDefault(x => !String.IsNullOrWhiteSpace(x))
103  ?? "UNPARSABLE";
104  }
105 
107  public async Task CreateDump(global::System.Diagnostics.Process process, string outputFile, CancellationToken cancellationToken)
108  {
109  if (process == null)
110  throw new ArgumentNullException(nameof(process));
111  if (outputFile == null)
112  throw new ArgumentNullException(nameof(outputFile));
113 
114  const string GCorePath = "/usr/bin/gcore";
115  if (!await ioManager.FileExists(GCorePath, cancellationToken).ConfigureAwait(false))
116  throw new JobException(ErrorCode.MissingGCore);
117 
118  var pid = process.Id;
119  string output;
120  int exitCode;
121  using (var gcoreProc = lazyLoadedProcessExecutor.Value.LaunchProcess(
122  GCorePath,
123  Environment.CurrentDirectory,
124  $"-o {outputFile} {process.Id}",
125  true,
126  true,
127  true))
128  {
129  using (cancellationToken.Register(() => gcoreProc.Terminate()))
130  exitCode = await gcoreProc.Lifetime.ConfigureAwait(false);
131 
132  output = gcoreProc.GetCombinedOutput();
133  logger.LogDebug("gcore output:{0}{1}", Environment.NewLine, output);
134  }
135 
136  if (exitCode != 0)
137  throw new JobException(
138  ErrorCode.GCoreFailure,
139  new JobException(
140  $"Exit Code: {exitCode}{Environment.NewLine}Output:{Environment.NewLine}{output}"));
141 
142  // gcore outputs name.pid so remove the pid part
143  var generatedGCoreFile = $"{outputFile}.{pid}";
144  await ioManager.MoveFile(generatedGCoreFile, outputFile, cancellationToken).ConfigureAwait(false);
145  }
146  }
147 }
readonly IIOManager ioManager
The IIOManager for the PosixProcessFeatures.
readonly Lazy< IProcessExecutor > lazyLoadedProcessExecutor
Lazy<T> loaded IProcessExecutor.
ErrorCode
Types of ErrorMessages that the API may return.
Definition: ErrorCode.cs:10
PosixProcessFeatures(Lazy< IProcessExecutor > lazyLoadedProcessExecutor, IIOManager ioManager, ILogger< PosixProcessFeatures > logger)
Initializes a new instance of the PosixProcessFeatures .
async Task CreateDump(global::System.Diagnostics.Process process, string outputFile, CancellationToken cancellationToken)
Create a dump file for a given process .
readonly ILogger< PosixProcessFeatures > logger
The ILogger<TCategoryName> for the PosixProcessFeatures.
Operation exceptions thrown from the context of a Models.Job
Definition: JobException.cs:9
void SuspendProcess(global::System.Diagnostics.Process process)
Suspend a given process .
async Task< string > GetExecutingUsername(global::System.Diagnostics.Process process, CancellationToken cancellationToken)
Get the name of the user executing a given process .
Abstraction for suspending and resuming processes.
Interface for using filesystems
Definition: IIOManager.cs:11
void ResumeProcess(global::System.Diagnostics.Process process)
Resume a given suspended Process.