226 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			226 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								package main
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import (
							 | 
						||
| 
								 | 
							
									"fmt"
							 | 
						||
| 
								 | 
							
									"io/ioutil"
							 | 
						||
| 
								 | 
							
									"os"
							 | 
						||
| 
								 | 
							
									"os/exec"
							 | 
						||
| 
								 | 
							
									"os/signal"
							 | 
						||
| 
								 | 
							
									"path/filepath"
							 | 
						||
| 
								 | 
							
									"strings"
							 | 
						||
| 
								 | 
							
									"syscall"
							 | 
						||
| 
								 | 
							
									"time"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									"novit.nc/direktil/pkg/color"
							 | 
						||
| 
								 | 
							
									"novit.nc/direktil/pkg/config"
							 | 
						||
| 
								 | 
							
									"novit.nc/direktil/pkg/log"
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const cfgPath = "/config.yaml"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var (
							 | 
						||
| 
								 | 
							
									bootVarPrefix = []byte("direktil.var.")
							 | 
						||
| 
								 | 
							
									cfg           *config.Config
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									initLog = log.Get("init")
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func main() {
							 | 
						||
| 
								 | 
							
									switch filepath.Base(os.Args[0]) {
							 | 
						||
| 
								 | 
							
									case "poweroff":
							 | 
						||
| 
								 | 
							
										initCommand("poweroff\n")
							 | 
						||
| 
								 | 
							
									case "reboot":
							 | 
						||
| 
								 | 
							
										initCommand("reboot\n")
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if len(os.Args) > 1 {
							 | 
						||
| 
								 | 
							
										switch os.Args[1] {
							 | 
						||
| 
								 | 
							
										case "0":
							 | 
						||
| 
								 | 
							
											initCommand("poweroff\n")
							 | 
						||
| 
								 | 
							
										case "6":
							 | 
						||
| 
								 | 
							
											initCommand("reboot\n")
							 | 
						||
| 
								 | 
							
										default:
							 | 
						||
| 
								 | 
							
											fmt.Fprintf(os.Stderr, "unknown args: %v\n", os.Args)
							 | 
						||
| 
								 | 
							
											os.Exit(1)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if os.Getpid() != 1 {
							 | 
						||
| 
								 | 
							
										fmt.Println("not PID 1")
							 | 
						||
| 
								 | 
							
										os.Exit(1)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									color.Write(os.Stderr, color.Cyan, "Direktil system starting\n")
							 | 
						||
| 
								 | 
							
									initLog.SetConsole(os.Stderr)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									go handleChildren()
							 | 
						||
| 
								 | 
							
									go handleSignals()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// handle abnormal ends
							 | 
						||
| 
								 | 
							
									defer func() {
							 | 
						||
| 
								 | 
							
										if err := recover(); err != nil {
							 | 
						||
| 
								 | 
							
											fatal("FATAL: panic in main: ", err)
							 | 
						||
| 
								 | 
							
										} else {
							 | 
						||
| 
								 | 
							
											fatal("FATAL: exited from main")
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// set a reasonable path
							 | 
						||
| 
								 | 
							
									os.Setenv("PATH", strings.Join([]string{
							 | 
						||
| 
								 | 
							
										"/usr/local/bin:/usr/local/sbin",
							 | 
						||
| 
								 | 
							
										"/usr/bin:/usr/sbin",
							 | 
						||
| 
								 | 
							
										"/bin:/sbin",
							 | 
						||
| 
								 | 
							
									}, ":"))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// load the configuration
							 | 
						||
| 
								 | 
							
									{
							 | 
						||
| 
								 | 
							
										c, err := config.Load(cfgPath)
							 | 
						||
| 
								 | 
							
										if err != nil {
							 | 
						||
| 
								 | 
							
											fatal("failed to load config: ", err)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if err := os.Remove(cfgPath); err != nil {
							 | 
						||
| 
								 | 
							
											initLog.Taint(log.Warning, "failed to remove config: ", err)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										cfg = c
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// bootstrap the basic things
							 | 
						||
| 
								 | 
							
									bootstrap()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									go listenInitctl()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// start the services
							 | 
						||
| 
								 | 
							
									services.Start()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Wait for configuration, but timeout to always give a login
							 | 
						||
| 
								 | 
							
									ch := make(chan int, 1)
							 | 
						||
| 
								 | 
							
									go func() {
							 | 
						||
| 
								 | 
							
										services.Wait(func() bool {
							 | 
						||
| 
								 | 
							
											return services.HasFlag("configured") ||
							 | 
						||
| 
								 | 
							
												services.HasFlag("boot-failed")
							 | 
						||
| 
								 | 
							
										})
							 | 
						||
| 
								 | 
							
										close(ch)
							 | 
						||
| 
								 | 
							
									}()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									select {
							 | 
						||
| 
								 | 
							
									case <-time.After(1 * time.Minute):
							 | 
						||
| 
								 | 
							
										initLog.Taint(log.Warning, "configuration took too long, allowing login anyway.")
							 | 
						||
| 
								 | 
							
									case <-ch:
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Handle CAD command (ctrl+alt+del)
							 | 
						||
| 
								 | 
							
									intCh := make(chan os.Signal, 1)
							 | 
						||
| 
								 | 
							
									signal.Notify(intCh, syscall.SIGINT)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									syscall.Reboot(syscall.LINUX_REBOOT_CMD_CAD_ON)
							 | 
						||
| 
								 | 
							
									go func() {
							 | 
						||
| 
								 | 
							
										<-intCh
							 | 
						||
| 
								 | 
							
										initLog.Taint(log.Warning, "received ctrl+alt+del, rebooting...")
							 | 
						||
| 
								 | 
							
										reboot()
							 | 
						||
| 
								 | 
							
									}()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Allow login now
							 | 
						||
| 
								 | 
							
									go allowLogin()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Wait all services
							 | 
						||
| 
								 | 
							
									services.WaitAll()
							 | 
						||
| 
								 | 
							
									initLog.Taint(log.OK, "all services are started")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									endOfProcess()
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func initCommand(c string) {
							 | 
						||
| 
								 | 
							
									err := ioutil.WriteFile("/run/initctl", []byte(c), 0600)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										os.Exit(1)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									os.Exit(0)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func mkdir(dir string, mode os.FileMode) {
							 | 
						||
| 
								 | 
							
									if err := os.MkdirAll(dir, mode); err != nil {
							 | 
						||
| 
								 | 
							
										fatalf("mkdir %q failed: %v", dir, err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func run(cmd string, args ...string) {
							 | 
						||
| 
								 | 
							
									c := exec.Command(cmd, args...)
							 | 
						||
| 
								 | 
							
									c.Stdout = os.Stdout
							 | 
						||
| 
								 | 
							
									c.Stderr = os.Stderr
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if err := c.Run(); err != nil {
							 | 
						||
| 
								 | 
							
										fatalf("command %s %q failed: %v", cmd, args, err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func touch(path string) {
							 | 
						||
| 
								 | 
							
									run("touch", path)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func poweroff() {
							 | 
						||
| 
								 | 
							
									prepareShutdown()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									initLog.Print("final sync")
							 | 
						||
| 
								 | 
							
									syscall.Sync()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									syscall.Reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func reboot() {
							 | 
						||
| 
								 | 
							
									prepareShutdown()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									initLog.Print("final sync")
							 | 
						||
| 
								 | 
							
									syscall.Sync()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func prepareShutdown() {
							 | 
						||
| 
								 | 
							
									services.Stop()
							 | 
						||
| 
								 | 
							
									initLog.Taint(log.Info, "services stopped")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									log.DisableFiles()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									for try := 0; try < 5; try++ {
							 | 
						||
| 
								 | 
							
										initLog.Taint(log.Info, "unmounting filesystems")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// FIXME: filesystem list should be build from non "nodev" lines in /proc/filesystems
							 | 
						||
| 
								 | 
							
										c := exec.Command("umount", "-a", "-t", "ext2,ext3,ext4,vfat,msdos,xfs,btrfs")
							 | 
						||
| 
								 | 
							
										c.Stdout = initLog
							 | 
						||
| 
								 | 
							
										c.Stderr = initLog
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if err := c.Run(); err != nil {
							 | 
						||
| 
								 | 
							
											initLog.Taint(log.Warning, "umounting failed: ", err)
							 | 
						||
| 
								 | 
							
											time.Sleep(time.Duration(2*try) * time.Second)
							 | 
						||
| 
								 | 
							
											continue
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										break
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									initLog.Taint(log.Info, "sync'ing")
							 | 
						||
| 
								 | 
							
									exec.Command("sync").Run()
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func allowLogin() {
							 | 
						||
| 
								 | 
							
									b := make([]byte, 1)
							 | 
						||
| 
								 | 
							
									for {
							 | 
						||
| 
								 | 
							
										os.Stdout.Write([]byte("\n" + color.Yellow + "[press enter to login]" + color.Reset + "\n\n"))
							 | 
						||
| 
								 | 
							
										for {
							 | 
						||
| 
								 | 
							
											os.Stdin.Read(b)
							 | 
						||
| 
								 | 
							
											if b[0] == '\n' {
							 | 
						||
| 
								 | 
							
												break
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										c := exec.Command("/sbin/agetty", "--noclear", "--noissue", "console", "linux")
							 | 
						||
| 
								 | 
							
										c.Stdin = os.Stdin
							 | 
						||
| 
								 | 
							
										c.Stdout = os.Stdout
							 | 
						||
| 
								 | 
							
										c.Stderr = os.Stderr
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										c.Run()
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 |