From 7bbfa9f1981858b722a966bb18e494428d5da07d Mon Sep 17 00:00:00 2001 From: EnumDev Date: Mon, 17 Nov 2025 12:29:09 +0200 Subject: [PATCH] Kill processes and unmount filesystems before shutdown/reboot --- src/enit/go.mod | 5 +- src/enit/go.sum | 2 + src/enit/main.go | 46 +++++++++++++++- src/enit/mount.go | 130 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 180 insertions(+), 3 deletions(-) diff --git a/src/enit/go.mod b/src/enit/go.mod index fa25cb7..e055e43 100644 --- a/src/enit/go.mod +++ b/src/enit/go.mod @@ -2,4 +2,7 @@ module enit go 1.23.4 -require golang.org/x/sys v0.31.0 +require ( + github.com/mitchellh/go-ps v1.0.0 + golang.org/x/sys v0.31.0 +) diff --git a/src/enit/go.sum b/src/enit/go.sum index c55261f..c2ced9d 100644 --- a/src/enit/go.sum +++ b/src/enit/go.sum @@ -1,2 +1,4 @@ +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= diff --git a/src/enit/main.go b/src/enit/main.go index a14f571..febab4f 100644 --- a/src/enit/main.go +++ b/src/enit/main.go @@ -13,6 +13,8 @@ import ( "syscall" "time" "unsafe" + + "github.com/mitchellh/go-ps" ) // Build-time variables @@ -184,6 +186,40 @@ func stopServiceManager() { } +func killProcesses() { + fmt.Print("Killing processes... ") + + // Send sigterm to all processes + processes, err := ps.Processes() + if err != nil { + return + } + for _, process := range processes { + if process.Pid() == 1 { + continue + } + + syscall.Kill(process.Pid(), syscall.SIGTERM) + } + + time.Sleep(1 * time.Second) + + // Send sigkill to remaining processes + processes, err = ps.Processes() + if err != nil { + return + } + for _, process := range processes { + if process.Pid() == 1 { + continue + } + + syscall.Kill(process.Pid(), syscall.SIGKILL) + } + + fmt.Println("Done.") +} + func setHostname() { fmt.Print("Setting hostname... ") @@ -232,9 +268,12 @@ func shutdownSystem() { fmt.Println("Shutting down...") stopServiceManager() + killProcesses() + unmountFilesystems() - fmt.Println("Syncing disks...") + fmt.Print("Syncing disks... ") syscall.Sync() + fmt.Println("Done.") fmt.Println("Sending shutdown syscall...") err := syscall.Reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF) @@ -247,9 +286,12 @@ func rebootSystem() { fmt.Println("Rebooting...") stopServiceManager() + killProcesses() + unmountFilesystems() - fmt.Println("Syncing disks...") + fmt.Print("Syncing disks... ") syscall.Sync() + fmt.Println("Done.") fmt.Println("Sending reboot syscall...") err := syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART) diff --git a/src/enit/mount.go b/src/enit/mount.go index ad5b344..a27ad5d 100644 --- a/src/enit/mount.go +++ b/src/enit/mount.go @@ -1,11 +1,14 @@ package main import ( + "errors" "fmt" "log" "os" "slices" "strings" + "syscall" + "time" "unsafe" "golang.org/x/sys/unix" @@ -203,3 +206,130 @@ func mountFstabEntries() (error, int) { return nil, 0 } + +func unmountFilesystems() { + // Disable all swap memory + data, err := os.ReadFile("/proc/swaps") + if err != nil { + log.Fatal(err) + } + + for i, entry := range strings.Split(string(data), "\n") { + if i == 0 { + continue + } + entry = strings.TrimSpace(entry) + if len(entry) == 0 { + continue + } + + mountpoint := strings.Fields(entry)[0] + + // Unmount swap at mountpoint + fmt.Printf("Disabling swap at %s... ", mountpoint) + b := append([]byte(mountpoint), 0) + _, _, err := unix.Syscall(unix.SYS_SWAPOFF, uintptr(unsafe.Pointer(&b[0])), 0, 0) + if err == 0 { + fmt.Println("Done.") + } else { + fmt.Printf("Error: %s\n", err.Error()) + } + } + + data, err = os.ReadFile("/proc/self/mountinfo") + if err != nil { + log.Fatal(err) + } + + // Reserve variables for root filesytem + rootSource := "" + rootFilesystem := "" + rootData := "" + + // Unmount filesystems + entries := strings.Split(string(data), "\n") + slices.Reverse(entries) + for _, entry := range entries { + entry = strings.TrimSpace(entry) + if len(entry) == 0 { + continue + } + + // Get entry fields + fields := strings.Fields(entry) + mountpoint := fields[4] + filesystem := "" + source := "" + data := "" + for i := 6; i < len(fields); i++ { + if fields[i] == "-" { + filesystem = fields[i+1] + source = fields[i+2] + data = fields[i+3] + break + } + } + + // Skip root and ignored filesystems + ignoredFilesystems := []string{ + "devtmpfs", + "proc", + "sysfs", + "tmpfs", + } + if mountpoint == "/" { + rootSource = source + rootFilesystem = filesystem + _, rootData, _ = convertMountOptions(data) + continue + } + + if slices.Contains(ignoredFilesystems, filesystem) { + continue + } + + // Unmount filesystem at mountpoint + fmt.Printf("Unmounting %s...", mountpoint) + tries := 0 + for { + err := unix.Unmount(mountpoint, 0) + if errors.Is(err, syscall.EBUSY) { + fmt.Print(".") + tries++ + time.Sleep(1 * time.Second) + if tries >= 60 { + unix.Unmount(mountpoint, syscall.MNT_FORCE) + fmt.Println(" Timeout.") + break + } + } else if err != nil { + fmt.Printf(" Error: %s\n", err.Error()) + break + } else { + fmt.Println(" Done.") + break + } + } + } + + fmt.Print("Remounting root as read-only...") + tries := 0 + for { + err = unix.Mount(rootSource, "/", rootFilesystem, syscall.MS_RDONLY|syscall.MS_REMOUNT, rootData) + if errors.Is(err, syscall.EBUSY) { + fmt.Print(".") + tries++ + time.Sleep(1 * time.Second) + if tries >= 60 { + fmt.Println(" Timeout.") + break + } + } else if err != nil { + fmt.Printf(" Error: %s\n", err.Error()) + break + } else { + fmt.Println(" Done.") + break + } + } +}