如何从在Linux上运行的.NET Core 应用程序关机计算机

6

我有一个在Linux上(Ubuntu Server 16.04 LTS)运行的.net core 2.0程序。

我试图通过调用以下命令的进程关闭计算机:sudo shutdown -h now,但当程序以守护进程服务方式在后台运行时,关闭进程无法正常工作。

以下是代码:

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");
}

我的假设是该服务作为单独的会话运行,因此它没有任何控制权。当应用程序作为Linux守护进程运行时,如何让计算机关机?


2
当作为守护进程运行时,您的进程可能缺少调用sudo的特权 - 或者sudo启动但立即提示输入密码,因此它处于空闲状态。 - Dai
3个回答

3
我建议更改你的代码,使用P/Invoke直接调用Linux的reboot函数,这也将为你提供更多详细信息,如果失败的话。
虽然在Unix/Linux上调用其他可执行文件来执行任务是惯例(特别是从shell脚本中),但.NET程序确实不太适合,并且所需的代码非常脆弱(例如,正如你正在使用sudo看到的那样),特别是在.NET世界中,处理来自其他进程的标准IO(stdinstdoutstderr)非常困难。
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;
}

使用方法:

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() );
    }
}

请注意,成功调用 reboot() 将永远不会返回。

看起来有几个值可能需要标记为 unchecked,因为它们不能由 C# 中的有符号整数保存。例如,public const int LINUX_REBOOT_CMD_CAD_ON = unchecked((int)0x89ABCDEF) - Travis Illig
@TravisIllig C#编译器默认为“unchecked”。您的项目默认启用了已检查的算术运算吗? - Dai
这可能是一个变化的东西。使用 dotnet new console 命令创建一个新的控制台应用程序,然后将 public const Int32 LINUX_REBOOT_MAGIC1 = 0xfee1dead; 添加到 Program.cs 中会导致编译错误:“无法隐式转换类型 'uint' 为 'int'。存在显式转换(是否缺少强制转换?)” - Travis Illig
1
@Dai 在 ARM 处理器上使用 libc.so.6 时,始终收到 EINVAL(错误的魔数)作为错误代码。 - Max R.
2
我得到了与@MaxR相同的结果。使用strace,我可以看到发生了一些奇怪的事情:reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,0xfee1dead /* LINUX_REBOOT_CMD_??? */)= -1 EINVAL(无效参数)-似乎对于此调用方法不需要前两个魔术数字。 将DllImport更改为public static extern Int32 reboot(Int32 cmd,IntPtr arg);并通过reboot(LINUX_REBOOT_CMD_POWER_OFF,IntPtr.Zero);调用可以正常工作。 - Tom
显示剩余3条评论

2
此外,对于在树莓派上运行的 .net core,我们需要使用另一个答案,但要遵循用户Tom的评论并将 DllImport 更改为:
[DllImport( "libc.so.6", SetLastError = true)]
public static extern Int32 reboot(Int32 cmd, IntPtr arg);

然后,要关闭电源,我们可以调用:

reboot(LINUX_REBOOT_CMD_POWER_OFF, IntPtr.Zero);

或者重新启动:

reboot(LINUX_REBOOT_CMD_RESTART, IntPtr.Zero);

0

这在 AWS EC2 上使用 Ubuntu Server 可以工作,在守护程序中使用。只在那里测试过,也可能在其他地方工作。

Process process = new Process();
process.StartInfo.FileName = "/usr/bin/sudo";
process.StartInfo.Arguments = "/sbin/shutdown -h now";
process.Start();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接