control protocol. Includes 'halt' and 'reboot' alias scripts.
Implement a dinit-reboot helper program to be called by the main
Dinit process to actually perform shutdown.
dinit
dinit-start
test
+shutdown
+dinit-reboot
-include mconfig
-objects = dinit.o load_service.o service.o control.o dinit-log.o dinit-start.o
+objects = dinit.o load_service.o service.o control.o dinit-log.o dinit-start.o shutdown.o dinit-reboot.o
dinit_objects = dinit.o load_service.o service.o control.o dinit-log.o
all: dinit dinit-start
+shutdown-utils: shutdown dinit-reboot
+
dinit: $(dinit_objects)
$(CXX) -o dinit $(dinit_objects) -lev $(EXTRA_LIBS)
dinit-start: dinit-start.o
$(CXX) -o dinit-start dinit-start.o $(EXTRA_LIBS)
+
+shutdown: shutdown.o
+ $(CXX) -o shutdown shutdown.o
+
+dinit-reboot: dinit-reboot.o
+ $(CXX) -o dinit-reboot dinit-reboot.o
$(objects): %.o: %.cc service.h dinit-log.h control.h control-cmds.h
$(CXX) $(CXXOPTS) -c $< -o $@
--- /dev/null
+// #include <netinet/in.h>
+#include <cstddef>
+#include <cstdio>
+#include <csignal>
+#include <unistd.h>
+#include <cstring>
+#include <string>
+#include <iostream>
+
+#include <sys/reboot.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+
+#include "control-cmds.h"
+
+// shutdown: shut down the system
+// This utility communicates with the dinit daemon via a unix socket (/dev/initctl).
+
+static void unmount_disks();
+static void swap_off();
+
+int main(int argc, char **argv)
+{
+ using namespace std;
+
+ int sd_type = 0;
+
+ //bool show_help = argc < 2;
+ bool show_help = false;
+
+ for (int i = 1; i < argc; i++) {
+ if (argv[i][0] == '-') {
+ if (strcmp(argv[i], "--help") == 0) {
+ show_help = true;
+ break;
+ }
+ else if (strcmp(argv[i], "-r") == 0) {
+ // Reboot
+ sd_type = 1;
+ }
+ else if (strcmp(argv[i], "-p") == 0) {
+ // Power down
+ sd_type = 2;
+ }
+ else if (strcmp(argv[i], "-h") == 0) {
+ // Halt
+ sd_type = 3;
+ }
+ else if (strcmp(argv[i], "-l") == 0) {
+ // Loop
+ sd_type = 0;
+ }
+ else {
+ cerr << "Unrecognized command-line parameter: " << argv[i] << endl;
+ return 1;
+ }
+ }
+ else {
+ // time argument? TODO
+ show_help = true;
+ }
+ }
+
+ if (show_help) {
+ cout << "dinit-shutdown : shutdown the system" << endl;
+ cout << " --help : show this help" << endl;
+ return 1;
+ }
+
+ if (sd_type == 0) {
+ while (true) {
+ pause();
+ }
+ }
+
+ int reboot_type = 0;
+ if (sd_type == 1) reboot_type = RB_AUTOBOOT;
+ else if (sd_type == 2) reboot_type = RB_POWER_OFF;
+ else reboot_type = RB_HALT_SYSTEM;
+
+ // Write to console rather than any terminal, since we lose the terminal it seems:
+ close(STDOUT_FILENO);
+ int consfd = open("/dev/console", O_WRONLY);
+ if (consfd != STDOUT_FILENO) {
+ dup2(consfd, STDOUT_FILENO);
+ }
+
+ // At this point, util-linux 2.13 shutdown sends SIGTERM to all processes with uid >= 100 and
+ // calls it 'sendiong SIGTERM to mortals'.
+ // Equivalent would probably be to rollback 'loginready' service. However, that will happen as
+ // part of the regular rollback anyway.
+
+ //cout << "Writing rollback command..." << endl; // DAV
+
+ //int r = write(socknum, buf, bufsize);
+ //if (r == -1) {
+ // perror("write");
+ //}
+
+ cout << "Sending TERM/KILL..." << endl; // DAV
+
+ // Send TERM/KILL to all (remaining) processes
+ kill(-1, SIGTERM);
+ sleep(1);
+ kill(-1, SIGKILL);
+
+ cout << "Sending QUIT to init..." << endl; // DAV
+
+ // Tell init to exec reboot:
+ // TODO what if it's not PID=1? probably should have dinit pass us its PID
+ kill(1, SIGQUIT);
+
+ // TODO can we wait somehow for above to work?
+ // maybe have a pipe/socket and we read from our end...
+
+ // TODO: close all ancillary file descriptors.
+
+ // perform shutdown
+ cout << "Turning off swap..." << endl;
+ swap_off();
+ cout << "Unmounting disks..." << endl;
+ unmount_disks();
+ sync();
+
+ cout << "Issuing shutdown via kernel..." << endl;
+ reboot(reboot_type);
+
+ return 0;
+}
+
+static void unmount_disks()
+{
+ pid_t chpid = fork();
+ if (chpid == 0) {
+ // umount -a -r
+ // -a : all filesystems (except proc)
+ // -r : mount readonly if can't unmount
+ execl("/bin/umount", "/bin/umount", "-a", "-r", nullptr);
+ }
+ else if (chpid > 0) {
+ int status;
+ waitpid(chpid, &status, 0);
+ }
+}
+
+static void swap_off()
+{
+ pid_t chpid = fork();
+ if (chpid == 0) {
+ // swapoff -a
+ execl("/sbin/swapoff", "/sbin/swapoff", "-a", nullptr);
+ }
+ else if (chpid > 0) {
+ int status;
+ waitpid(chpid, &status, 0);
+ }
+}
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
+
#include "service.h"
#include "ev++.h"
#include "control.h"
#ifdef __linux__
#include <sys/klog.h>
+#include <sys/reboot.h>
#endif
/*
// Variables
-static bool got_sigterm = false;
-
static ServiceSet *service_set;
static bool am_system_init = false; // true if we are the system init process
-static bool do_reboot = false; // whether to reboot (instead of halting)
static bool control_socket_open = false;
int active_control_conns = 0;
if (am_system_init) {
// Disable non-critical kernel output to console
klogctl(6 /* SYSLOG_ACTION_CONSOLE_OFF */, nullptr, 0);
+ // Make ctrl+alt+del combination send SIGINT to PID 1 (this process)
+ reboot(RB_DISABLE_CAD);
}
#endif
event_loop:
// Process events until all services have terminated.
- while (service_set->count_active_services() != 0 || active_control_conns != 0) {
+ while (service_set->count_active_services() != 0) {
ev_loop(loop, EVLOOP_ONESHOT);
}
+
+ ShutdownType shutdown_type = service_set->getShutdownType();
if (am_system_init) {
logMsgBegin(LogLevel::INFO, "No more active services.");
- if (do_reboot) {
+
+ if (shutdown_type == ShutdownType::REBOOT) {
logMsgEnd(" Will reboot.");
}
- else if (got_sigterm) {
+ else if (shutdown_type == ShutdownType::HALT) {
logMsgEnd(" Will halt.");
}
+ else if (shutdown_type == ShutdownType::POWEROFF) {
+ logMsgEnd(" Will power down.");
+ }
else {
logMsgEnd(" Re-initiating boot sequence.");
}
close_control_socket(ev_default_loop(EVFLAG_AUTO));
if (am_system_init) {
- if (do_reboot) {
- // Fork and execute /sbin/reboot
- int fres = fork();
- if (fres == 0) {
- execl("/sbin/reboot", "/sbin/reboot", (char *) 0);
- }
- else if (fres == -1) {
- log(LogLevel::ERROR, "Could not fork for reboot: ", strerror(errno));
- }
- }
- else if (got_sigterm) {
- // Fork and execute /sbin/halt
- int fres = fork();
- if (fres == 0) {
- execl("/sbin/halt", "/sbin/halt", (char *) 0);
- }
- else if (fres == -1) {
- log(LogLevel::ERROR, "Could not fork for halt: ", strerror(errno));
- }
- }
- else {
- // Hmmmmmm.
+ if (shutdown_type == ShutdownType::CONTINUE) {
// It could be that we started in single user mode, and the
// user has now exited the shell. We'll try and re-start the
// boot process...
catch (...) {
// Now WTF do we do? try to reboot
log(LogLevel::ERROR, "Could not start 'boot' service; rebooting.");
- if (fork() == 0) {
- execl("/sbin/reboot", "/sbin/reboot", (char *) 0);
- }
+ shutdown_type = ShutdownType::REBOOT;
}
}
- // PID 1 should never exit:
+ const char * cmd_arg;
+ if (shutdown_type == ShutdownType::HALT) {
+ cmd_arg = "-h";
+ }
+ else if (shutdown_type == ShutdownType::REBOOT) {
+ cmd_arg = "-r";
+ }
+ else {
+ // power off.
+ cmd_arg = "-p";
+ }
+
+ // Fork and execute dinit-reboot.
+ execl("/usr/libexec/dinit-reboot", "/usr/libexec/dinit-reboot", cmd_arg, nullptr);
+ log(LogLevel::ERROR, "Could not execl() for reboot: ", strerror(errno));
+
+ // PID 1 must not actually exit, although we should never reach this point:
while (true) {
- pause();
+ ev_loop(loop, EVLOOP_ONESHOT);
}
}
/* handle SIGINT signal (generated by kernel when ctrl+alt+del pressed) */
static void sigint_reboot_cb(struct ev_loop *loop, ev_signal *w, int revents)
{
- do_reboot = true;
log_to_console = true;
- service_set->stop_all_services();
+ service_set->stop_all_services(ShutdownType::REBOOT);
}
/* handle SIGQUIT (if we are system init) */
// This allows remounting the filesystem read-only if the dinit binary has been
// unlinked. In that case the kernel holds the binary open, so that it can't be
// properly removed.
+ close_control_socket(ev_default_loop(EVFLAG_AUTO));
execl("/sbin/shutdown", "/sbin/shutdown", (char *) 0);
log(LogLevel::ERROR, "Error executing /sbin/shutdown: ", strerror(errno));
}
-/* handle SIGTERM - stop all services */
+/* handle SIGTERM/SIGQUIT - stop all services (not used for system daemon) */
static void sigterm_cb(struct ev_loop *loop, ev_signal *w, int revents)
{
- got_sigterm = true;
log_to_console = true;
service_set->stop_all_services();
}
--- /dev/null
+#!/bin/sh
+shutdown -h
--- /dev/null
+#!/bin/sh
+shutdown -r
--- /dev/null
+// #include <netinet/in.h>
+#include <cstddef>
+#include <cstdio>
+#include <csignal>
+#include <unistd.h>
+#include <cstring>
+#include <string>
+#include <iostream>
+
+#include <sys/reboot.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "control-cmds.h"
+#include "service-constants.h"
+
+// shutdown: shut down the system
+// This utility communicates with the dinit daemon via a unix socket (/dev/initctl).
+
+int main(int argc, char **argv)
+{
+ using namespace std;
+
+ //bool show_help = argc < 2;
+ bool show_help = false;
+
+ auto shutdown_type = ShutdownType::POWEROFF;
+
+ for (int i = 1; i < argc; i++) {
+ if (argv[i][0] == '-') {
+ if (strcmp(argv[i], "--help") == 0) {
+ show_help = true;
+ break;
+ }
+ else if (strcmp(argv[i], "-r") == 0) {
+ shutdown_type = ShutdownType::REBOOT;
+ }
+ else if (strcmp(argv[i], "-h") == 0) {
+ shutdown_type = ShutdownType::POWEROFF;
+ }
+ else {
+ cerr << "Unrecognized command-line parameter: " << argv[i] << endl;
+ return 1;
+ }
+ }
+ else {
+ // time argument? TODO
+ }
+ }
+
+ if (show_help) {
+ cout << "dinit-shutdown : shutdown the system" << endl;
+ cout << " --help : show this help" << endl;
+ return 1;
+ }
+
+ int socknum = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (socknum == -1) {
+ perror("socket");
+ return 1;
+ }
+
+ const char *naddr = "/dev/dinitctl";
+
+ struct sockaddr_un name;
+ name.sun_family = AF_UNIX;
+ strcpy(name.sun_path, naddr);
+ int sunlen = offsetof(struct sockaddr_un, sun_path) + strlen(naddr) + 1; // family, (string), nul
+
+ int connr = connect(socknum, (struct sockaddr *) &name, sunlen);
+ if (connr == -1) {
+ perror("connect");
+ return 1;
+ }
+
+ // Build buffer;
+ //uint16_t sname_len = strlen(service_name);
+ int bufsize = 2;
+ char * buf = new char[bufsize];
+
+ buf[0] = DINIT_CP_SHUTDOWN;
+ buf[1] = static_cast<char>(shutdown_type);
+
+ //memcpy(buf + 1, &sname_len, 2);
+ //memcpy(buf + 3, service_name, sname_len);
+
+ // Make sure we can't die due to a signal at this point:
+ //sigset_t sigmask;
+ //sigfillset(&sigmask);
+ //sigprocmask(SIG_BLOCK, &sigmask, nullptr);
+
+ // Write to console rather than any terminal, since we lose the terminal it seems:
+ //close(STDOUT_FILENO);
+ //int consfd = open("/dev/console", O_WRONLY);
+ //if (consfd != STDOUT_FILENO) {
+ // dup2(consfd, STDOUT_FILENO);
+ //}
+
+ // At this point, util-linux 2.13 shutdown sends SIGTERM to all processes with uid >= 100 and
+ // calls it 'sendiong SIGTERM to mortals'.
+ // Equivalent would probably be to rollback 'loginready' service. However, that will happen as
+ // part of the regular rollback anyway.
+
+ cout << "Writing shutdown command..." << endl; // DAV
+
+ // TODO make sure to write the whole buffer
+ int r = write(socknum, buf, bufsize);
+ if (r == -1) {
+ perror("write");
+ }
+
+ cout << "Waiting for ACK..." << endl; // DAV
+
+ // Wait for ACK/NACK
+ r = read(socknum, buf, 1);
+ // TODO: check result
+
+ return 0;
+}