Browse Source

Implement support for shutdown hooks

Davin McCall 1 year ago
parent
commit
8dcf80ff67
2 changed files with 69 additions and 6 deletions
  1. 26 1
      doc/manpages/shutdown.8.m4
  2. 43 5
      src/shutdown.cc

+ 26 - 1
doc/manpages/shutdown.8.m4

@@ -49,7 +49,7 @@ as \fBreboot\fR.
 Instead of attempting to open a socket connection to the service daemon,
 use a pre-opened connection that has been passed to the process from its parent
 via an open file descriptor. The file descriptor with the connection is identified
-by the DINIT_CS_FD environment variable.
+by the contents of the DINIT_CS_FD environment variable.
 .TP
 \fB\-\-system\fR
 Shut down directly, instead of by issuing a command to the service manager. Use of
@@ -59,6 +59,31 @@ the service manager has stopped responding.
 The service manager may invoke \fBshutdown\fR with this option in order to perform
 system shutdown after it has rolled back services.
 .\"
+.SH SHUTDOWN HOOKS
+.\"
+To allow for special shutdown actions, if an executable file exists at any of the following
+locations, it will be executed before the system is shut down but after terminating all other
+processes:
+.\"
+.RS
+.IP \(bu
+/etc/dinit/shutdown-hook
+.IP \(bu
+/lib/dinit/shutdown-hook
+.RE
+.LP
+Only the first existing executable file from the above list will be executed. The first location
+is intended to allow customisation by the system administrator (and should usually be a script
+which on completion executes the 2nd shutdown hook, if present).
+The 2nd location is intended for distribution control. 
+.LP
+If found and successfully executed, the shutdown hook should perform any special shutdown actions
+that depend on all processes being terminated.
+If the shutdown hook cleanly unmounts (or remounts as read-only) all file systems including the
+root file system, it should exit with status 0 (success), which will prevent \fBshutdown\fR from
+attempting to unmount file systems itself.
+If it does not unmount file systems, the script should not exit with status 0. 
+.\"
 .SH SEE ALSO
 .\"
 \fBdinit\fR(8), \fBdinitctl\fR(8)

+ 43 - 5
src/shutdown.cc

@@ -38,6 +38,7 @@ class subproc_buffer;
 void do_system_shutdown(shutdown_type_t shutdown_type);
 static void unmount_disks(loop_t &loop, subproc_buffer &sub_buf);
 static void swap_off(loop_t &loop, subproc_buffer &sub_buf);
+static int run_process(const char * prog_args[], loop_t &loop, subproc_buffer &sub_buf);
 
 constexpr static int subproc_bufsize = 4096;
 
@@ -453,12 +454,45 @@ void do_system_shutdown(shutdown_type_t shutdown_type)
     } while (! timeout_reached);
 
     kill(-1, SIGKILL);
+
+    // Attempt to execute shutdown hook at three possible locations:
+    const char * const hook_paths[] = {
+            "/etc/dinit/shutdown-hook",
+            "/lib/dinit/shutdown-hook"
+    };
     
+    bool do_unmount_ourself = true;
+    const int execmask = S_IXOTH | S_IXGRP | S_IXUSR;
+    struct stat statbuf;
+
+    for (size_t i = 0; i < sizeof(hook_paths) / sizeof(hook_paths[0]); ++i) {
+        int stat_r = lstat(hook_paths[i], &statbuf);
+        if (stat_r == 0 && (statbuf.st_mode & execmask) != 0) {
+            sub_buf.append("Executing shutdown hook...\n");
+            const char *prog_args[] = { hook_paths[i], nullptr };
+            try {
+                int r = run_process(prog_args, loop, sub_buf);
+                if (r == 0) {
+                    do_unmount_ourself = false;
+                }
+            }
+            catch (std::exception &e) {
+                sub_buf.append("Couldn't fork for shutdown-hook: ");
+                sub_buf.append(e.what());
+                sub_buf.append("\n");
+            }
+            break;
+        }
+    }
+
     // perform shutdown
-    sub_buf.append("Turning off swap...\n");
-    swap_off(loop, sub_buf);
-    sub_buf.append("Unmounting disks...\n");
-    unmount_disks(loop, sub_buf);
+    if (do_unmount_ourself) {
+        sub_buf.append("Turning off swap...\n");
+        swap_off(loop, sub_buf);
+        sub_buf.append("Unmounting disks...\n");
+        unmount_disks(loop, sub_buf);
+    }
+
     sync();
     
     sub_buf.append("Issuing shutdown via kernel...\n");
@@ -521,16 +555,18 @@ class subproc_out_watch : public loop_t::fd_watcher_impl<subproc_out_watch>
 
 // Run process, put its output through the subprocess buffer
 //   may throw: std::system_error, std::bad_alloc
-static void run_process(const char * prog_args[], loop_t &loop, subproc_buffer &sub_buf)
+static int run_process(const char * prog_args[], loop_t &loop, subproc_buffer &sub_buf)
 {
     class sp_watcher_t : public loop_t::child_proc_watcher_impl<sp_watcher_t>
     {
         public:
         bool terminated = false;
+        int exit_status;
 
         rearm status_change(loop_t &, pid_t child, int status)
         {
             terminated = true;
+            exit_status = status;
             return rearm::REMOVE;
         }
     };
@@ -594,6 +630,8 @@ static void run_process(const char * prog_args[], loop_t &loop, subproc_buffer &
         owatch.deregister(loop);
         close(pipefds[0]);
     }
+
+    return sp_watcher.exit_status;
 }
 
 static void unmount_disks(loop_t &loop, subproc_buffer &sub_buf)