Reboot 命令实际是 systemctl?
Lapin Gris Lv3

背景

在一次查问题的过程中,偶然发现 reboot 命令是直接链接到 systemctl 的(划重点:直接链接到),看下面的演示,

# which reboot
/sbin/reboot

# file /sbin/reboot
/sbin/reboot: symbolic link to ../bin/systemctl

第一直觉就是这是不是不合理,直接把 reboot 软链到 systemctl ,不就相当于直接执行 systemctl 命令,这怎么会起到 reboot 的作用的呢?

继续看,

发现 /sbin 目录下很多命令都是直接软链到 systemctl,没有参数来区分,而且这些命令是 100% 工作的,当数量多起来的时候,事情逐渐合理起来了。

# file /sbin/* | grep symbolic | grep systemctl
/sbin/halt: symbolic link to ../bin/systemctl
/sbin/poweroff: symbolic link to ../bin/systemctl
/sbin/reboot: symbolic link to ../bin/systemctl
/sbin/runlevel: symbolic link to ../bin/systemctl
/sbin/shutdown: symbolic link to ../bin/systemctl
/sbin/telinit: symbolic link to ../bin/systemctl

那 systemctl 是如何区分出这些命令的?

熟悉 C 编程的同学大概率可以猜到了,systemctl 可能是通过进程名来区分的,也就是 argv[0] 这个参数,C 语言里 argv[0] 保存的是程序的名称(其他语言也有类似的参数)。

那拿 systemctl 测试一下?

[root@cs8 ~]# ln -sf /usr/bin/systemctl testcmd
[root@cs8 ~]# ./testcmd
UNIT LOAD ACTIVE SUB DESCRIPTION
proc-sys-fs-binfmt_misc.automount loaded active waiting Arbitrary Executable File Formats File System Automount Point
sys-devices-pci0000:00-0000:00:05.0-virtio0-net-eth0.device loaded active plugged Virtio network device
sys-devices-pci0000:00-0000:00:06.0-virtio1-block-vda-vda1.device loaded active plugged /sys/devices/pci0000:00/0000:00:06.0/virtio1/block/vda/vda1
sys-devices-pci0000:00-0000:00:06.0-virtio1-block-vda.device loaded active plugged /sys/devices/pci0000:00/0000:00:06.0/virtio1/block/vda

可以看到 systemctl 并不识别 testcmd 命令,而是打印了所有 uints,等价 systemctl 直接执行。

看代码

和我们预想的一样,systemctl 内部通过 argv 来判断由谁调用的

// systemd/src/systemctl/systemctl.c
int systemctl_dispatch_parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);

if (invoked_as(argv, "halt")) {
arg_action = ACTION_HALT;
return halt_parse_argv(argc, argv);

} else if (invoked_as(argv, "poweroff")) {
arg_action = ACTION_POWEROFF;
return halt_parse_argv(argc, argv);

} else if (invoked_as(argv, "reboot")) {
if (kexec_loaded())
arg_action = ACTION_KEXEC;
else
arg_action = ACTION_REBOOT;
return halt_parse_argv(argc, argv);

} else if (invoked_as(argv, "shutdown")) {
arg_action = ACTION_POWEROFF;
return shutdown_parse_argv(argc, argv);

} else if (invoked_as(argv, "init")) {
if (sd_booted() > 0) {
arg_action = _ACTION_INVALID;
return telinit_parse_argv(argc, argv);
} else {
arg_action = ACTION_TELINIT;
return 1;
}
} else if (invoked_as(argv, "runlevel")) {
arg_action = ACTION_RUNLEVEL;
return runlevel_parse_argv(argc, argv);
}

arg_action = ACTION_SYSTEMCTL;
return systemctl_parse_argv(argc, argv);
}

自己实现一个

实现也很简单,几行代码就行。只需要实现通过 argv[0] 识别调用方是谁,然后根据不同的调用方执行不同的逻辑即可。

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
// 提取程序名(最后一个斜杠后的内容)
char *programName = strrchr(argv[0], '/');
if (programName != NULL) {
programName++; // 移动到斜杠后的第一个字符
} else {
programName = argv[0]; // 如果没有斜杠,直接使用整个参数
}

// 根据程序名执行不同的逻辑
if (strcmp(programName, "programA") == 0) {
printf("执行程序 A 的逻辑\n");
} else if (strcmp(programName, "programB") == 0) {
printf("执行程序 B 的逻辑\n");
} else {
printf("未知程序,执行默认逻辑\n");
}

return 0;
}

总结

完结撒花