I am writing a software update process on Linux. Application is .NET 5 RC1 (Sept 15 2020 release). When a certain packet is received by my application, it downloads the software update to a sub-folder then spawns off the executable to perform the software update.
Unfortunately, using Process.Start and ProcessStartInfo seems to create a process that is attached to the main process. Since the software update must stop the process in order to update it, it also gets stopped because it is a child of the process, having been spawned via Process.Start.
How do I create a detached process on Linux? On Windows I am using PInvoke and the CreateProcess API with the DETACHED_PROCESS flag, see the following:
var processInformation = new ProcessUtility.PROCESS_INFORMATION();
var startupInfo = new ProcessUtility.STARTUPINFO();
var sa = new ProcessUtility.SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
CreateProcess(null, "\"" + fileName + "\" " + arguments, ref sa, ref sa, false, DETACHED_PROCESS, IntPtr.Zero, Path.GetDirectoryName(fileName), ref startupInfo, out processInformation);
Here is my code for Linux. I had read that appending & to a process on Linux creates it detached, but that does not appear to be the case.
ProcessStartInfo info = new ProcessStartInfo
{
// Linux uses " &" to detach the process
Arguments = arguments + " &",
CreateNoWindow = true,
FileName = fileName,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = Path.GetDirectoryName(fileName)
};
Process.Start(info);
I was unable to get nohup or disown to work from C#. Killing the parent process always resulted in the child process being terminated as well.
I ended up using at, which can be installed via sudo apt install at. The atd service is installed and will stay running even when rebooted.
Here is the C# code that I used:
// the following assumes `sudo apt install at` has been run.
string fileName = "[your process to execute]";
string arguments = "[your command line arguments for fileName]";
string argumentsEscaped = arguments.Replace("\"", "\\\"");
string fullArgs = $"-c \"echo sudo \\\"{fileName}\\\" {argumentsEscaped} | at now\"";
ProcessStartInfo info = new()
{
Arguments = fullArgs,
CreateNoWindow = true,
FileName = "/bin/bash",
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = Path.GetDirectoryName(fileName)
};
using var process = Process.Start(info);
process.WaitForExit();
// make sure to check process.ExitCode == 0
Slightly refactored version of @jjxtra solution, so it's easier to understand what's going on in the arguments.
Btw, the echo is not an example, but the way of executing at command.
string command = $"actual command to run";
string atdCommand = $@"echo \""{command}\"" | at now";
string bashCommand = $@"-c ""{atdCommand}"" ";
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = bashCommand,
...
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With