From: Davin McCall Date: Wed, 30 Dec 2015 22:24:54 +0000 (+0000) Subject: Implement a shutdown commoand which issues a shutdown via Dinit's X-Git-Tag: v0.01~80 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=c25317653592eee1d722326cd273dd0759b0dd40;p=oweals%2Fdinit.git Implement a shutdown commoand which issues a shutdown via Dinit's 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. --- diff --git a/.gitignore b/.gitignore index aa32781..0557ec3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ dinit dinit-start test +shutdown +dinit-reboot diff --git a/Makefile b/Makefile index a0cd8d3..245a0b8 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,24 @@ -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 $@ diff --git a/dinit-reboot.cc b/dinit-reboot.cc new file mode 100644 index 0000000..d57ad31 --- /dev/null +++ b/dinit-reboot.cc @@ -0,0 +1,162 @@ +// #include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +#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); + } +} diff --git a/dinit.cc b/dinit.cc index c79643f..0973946 100644 --- a/dinit.cc +++ b/dinit.cc @@ -12,6 +12,7 @@ #include #include #include + #include "service.h" #include "ev++.h" #include "control.h" @@ -19,6 +20,7 @@ #ifdef __linux__ #include +#include #endif /* @@ -69,12 +71,9 @@ struct ev_io control_socket_io; // 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; @@ -212,6 +211,8 @@ int main(int argc, char **argv) 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 @@ -237,18 +238,24 @@ int main(int argc, char **argv) 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."); } @@ -257,28 +264,7 @@ int main(int argc, char **argv) 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... @@ -289,15 +275,29 @@ int main(int argc, char **argv) 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); } } @@ -399,9 +399,8 @@ void close_control_socket(struct ev_loop *loop) noexcept /* 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) */ @@ -410,14 +409,14 @@ static void sigquit_cb(struct ev_loop *loop, ev_signal *w, int revents) // 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(); } diff --git a/halt b/halt new file mode 100755 index 0000000..d8ad55c --- /dev/null +++ b/halt @@ -0,0 +1,2 @@ +#!/bin/sh +shutdown -h diff --git a/reboot b/reboot new file mode 100755 index 0000000..285a393 --- /dev/null +++ b/reboot @@ -0,0 +1,2 @@ +#!/bin/sh +shutdown -r diff --git a/shutdown.cc b/shutdown.cc new file mode 100644 index 0000000..aa0f6a7 --- /dev/null +++ b/shutdown.cc @@ -0,0 +1,123 @@ +// #include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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(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; +}