How to shutdown computer from a .NET Core application running on Linux
Asked Answered
T

3

6

I have a .net core 2.0 program running on Linux (Ubuntu Server 16.04 LTS).

I'm trying to shutdown the computer by invoking a process with the following command: sudo shutdown -h now, though when the program is running in the background as daemon service, the shutdown process does not work.

Here is the code:

var process = new Process
{
    StartInfo =
    {
        CreateNoWindow = true,
        RedirectStandardError = true,
        RedirectStandardInput = true,
        RedirectStandardOutput = true,
        UseShellExecute = false,
        FileName = Environment.GetEnvironmentVariable("SHELL"),
        Arguments = "-s"
    },
    EnableRaisingEvents = true
};

if (process.Start())
{
    process.BeginErrorReadLine();
    process.BeginOutputReadLine();
    process.StandardInput.WriteLine("sudo shutdown -h now");
}

My assumption is that the service runs as a separate session so it doesn't have any control. How can I get the application to shutdown the computer when it is running as a Linux daemon?

Tonsil answered 2/5, 2018 at 18:28 Comment(1)
When running as a deamon your process probably lacks privileges to call sudo - or sudo starts but it immediately prompts for a password, so it sits idle.Wundt
W
3

I recommend changing your code to use P/Invoke to call Linux's reboot function directly, this will also give you more details if it fails.

While invoking other executables to perform tasks is the convention on Unix/Linux (especially from shell scripts), .NET programs really don't fit in well and the code required is very brittle (e.g. as you're seeing with sudo), especially as in the .NET world processing Standard IO (stdin, stdout, stderr) from other processes is very difficult.

internal static class NativeMethods
{
    [DllImport( "libc.so", SetLastError = true)] // You may need to change this to "libc.so.6" or "libc.so.7" depending on your platform)
    public static extern Int32 reboot(Int32 magic, Int32 magic2, Int32 cmd, IntPtr arg);

    public const Int32 LINUX_REBOOT_MAGIC1 = unchecked((int)0xfee1dead);
    public const Int32 LINUX_REBOOT_MAGIC2 = 672274793;
    public const Int32 LINUX_REBOOT_MAGIC2A = 85072278;
    public const Int32 LINUX_REBOOT_MAGIC2B = 369367448;
    public const Int32 LINUX_REBOOT_MAGIC2C = 537993216;


    public const Int32 LINUX_REBOOT_CMD_RESTART = 0x01234567;
    public const Int32 LINUX_REBOOT_CMD_HALT = unchecked((int)0xCDEF0123);
    public const Int32 LINUX_REBOOT_CMD_CAD_ON = unchecked((int)0x89ABCDEF);
    public const Int32 LINUX_REBOOT_CMD_CAD_OFF = 0x00000000;
    public const Int32 LINUX_REBOOT_CMD_POWER_OFF = 0x4321FEDC;
    public const Int32 LINUX_REBOOT_CMD_RESTART2 = unchecked((int)0xA1B2C3D4);
    public const Int32 LINUX_REBOOT_CMD_SW_SUSPEND = unchecked((int)0xD000FCE2);
    public const Int32 LINUX_REBOOT_CMD_KEXEC = 0x45584543;

    public const Int32 EPERM  =  1;
    public const Int32 EFAULT = 14;
    public const Int32 EINVAL = 22;
}

Usage:

using static NativeMethods;

public static void Shutdown()
{
    Int32 ret = reboot( LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_POWER_OFF, IntPtr.Zero );

    // `reboot(LINUX_REBOOT_CMD_POWER_OFF)` never returns if it's successful, so if it returns 0 then that's weird, we should treat it as an error condition instead of success:
    if( ret == 0 ) throw new InvalidOperationException( "reboot(LINUX_REBOOT_CMD_POWER_OFF) returned 0.");

    // ..otherwise we expect it to return -1 in the event of failure, so any other value is exceptional:
    if( ret != -1 ) throw new InvalidOperationException( "Unexpected reboot() return value: " + ret );

    // At this point, ret == -1, which means check `errno`!
    // `errno` is accessed via Marshal.GetLastWin32Error(), even on non-Win32 platforms and especially even on Linux

    Int32 errno = Marshal.GetLastWin32Error();
    switch( errno )
    {
    case EPERM:
        throw new UnauthorizedAccessException( "You do not have permission to call reboot()" );

    case EINVAL:
        throw new ArgumentException( "Bad magic numbers (stray cosmic-ray?)" );

    case EFAULT:
    default:
        throw new InvalidOperationException( "Could not call reboot():" + errno.ToString() );
    }
}

Note that a successful call to reboot() will never return.

Wundt answered 3/5, 2018 at 1:11 Comment(8)
Thanks for the reply. I tried to implement it in this way but got the error as -1 from reboot(). Do you know what could be the reason?Tonsil
@Tonsil My apologies. I misread the documentation. I've updated my answer. If it returns -1 then you get the error number via errno (Marshal.GetLastWin32Error() - even on Linux). The error-number is not the return value.Wundt
It appears several of the values may need to be marked unchecked since they can't be held by a signed integer in C#. For example, public const int LINUX_REBOOT_CMD_CAD_ON = unchecked((int)0x89ABCDEF).Damocles
@TravisIllig The C# compiler defaults to unchecked. Does your project have checked arithmetic enabled by default?Wundt
This potentially could be something that changed. dotnet new console to get a new console app, then add public const Int32 LINUX_REBOOT_MAGIC1 = 0xfee1dead; to Program.cs yields the compile error "Cannot implicitly convert type 'uint' to 'int'. An explicit conversion exists (are you missing a cast?)"Damocles
@Wundt got always EINVAL (Bad magic numbers) as error code. Using libc.so.6 on an ARM processor.Fauces
I was getting the same as @MaxR. Using strace, I could see something strange was going on: reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, 0xfee1dead /* LINUX_REBOOT_CMD_??? */) = -1 EINVAL (Invalid argument) - It appears the first two magic numbers are not required for this invocation method. Changing the DllImport to public static extern Int32 reboot(Int32 cmd, IntPtr arg); and invoking via reboot(LINUX_REBOOT_CMD_POWER_OFF, IntPtr.Zero); works fine.Ash
When I run dotnet run <MyProg> I get unauthorized. When I run sudo dotnet run <MyProg> it says dotnet is not installedCorsage
J
2

Adding that, for .net core running on the Raspberry Pi, we need to use the other answer, but follow user Tom's comment and change the DllImport to:

[DllImport( "libc.so.6", SetLastError = true)]
public static extern Int32 reboot(Int32 cmd, IntPtr arg);

And then to power off we can call:

reboot(LINUX_REBOOT_CMD_POWER_OFF, IntPtr.Zero);

Or to reboot:

reboot(LINUX_REBOOT_CMD_RESTART, IntPtr.Zero);
Jerroldjerroll answered 17/10, 2021 at 20:6 Comment(0)
M
2

This works on AWS EC2 with Ubuntu Server, used in daemon. Tested only there, may work also elsewhere.

Process process = new Process();
process.StartInfo.FileName = "/usr/bin/sudo";
process.StartInfo.Arguments = "/sbin/shutdown -h now";
process.Start();
Moralez answered 3/2, 2023 at 17:24 Comment(1)
I am using this code on RapberryPi (ubuntu) from net7.0 (also 8.0) application, compiled target runtime: linux-arm.Hawkes

© 2022 - 2025 — McMap. All rights reserved.