Browse Source

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.

Davin McCall 8 years ago
parent
commit
c253176535
7 changed files with 336 additions and 38 deletions
  1. 2 0
      .gitignore
  2. 9 1
      Makefile
  3. 162 0
      dinit-reboot.cc
  4. 36 37
      dinit.cc
  5. 2 0
      halt
  6. 2 0
      reboot
  7. 123 0
      shutdown.cc

+ 2 - 0
.gitignore

@@ -2,3 +2,5 @@
 dinit
 dinit-start
 test
+shutdown
+dinit-reboot

+ 9 - 1
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 $@

+ 162 - 0
dinit-reboot.cc

@@ -0,0 +1,162 @@
+// #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);
+    }
+}

+ 36 - 37
dinit.cc

@@ -12,6 +12,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <pwd.h>
+
 #include "service.h"
 #include "ev++.h"
 #include "control.h"
@@ -19,6 +20,7 @@
 
 #ifdef __linux__
 #include <sys/klog.h>
+#include <sys/reboot.h>
 #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();
 }

+ 2 - 0
halt

@@ -0,0 +1,2 @@
+#!/bin/sh
+shutdown -h

+ 2 - 0
reboot

@@ -0,0 +1,2 @@
+#!/bin/sh
+shutdown -r

+ 123 - 0
shutdown.cc

@@ -0,0 +1,123 @@
+// #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;
+}