package main import ( "bufio" "errors" "flag" "fmt" "log" "os" "os/exec" "strings" "time" ) var ( daemon = false serviceFile = "/adm/services" services = make(map[string]Service) controlSocket = "/adm/headless9/ctl/headless9.ctl" logPath = "/adm/headless9/log/" ) func main() { flag.Parse() if flag.NArg() == 0 { log.Println(DAEMON_START) daemon = true runDaemon() } if flag.NArg() != 2 { fmt.Println(CMD_SYNTAX) return } verb := flag.Args()[0] service := flag.Args()[1] f := getCtl() _, err := fmt.Fprintf(f, "%+v %+v", verb, service) if err != nil { fmt.Printf(CTL_UNABLE_WRITE, controlSocket) } f.Close() err = watchFile(controlSocket) if err != nil { log.Println(err) } fmt.Println(strings.Join(sysTail(10, controlSocket), "\n")) } func getCtl() *os.File { f, err := os.OpenFile(controlSocket, os.O_TRUNC, 0660) if err != nil { if daemon { log.Printf(CTL_NOT_OPEN, controlSocket) } else { fmt.Printf(CTL_NOT_OPEN, controlSocket) } } err = f.Truncate(0) if err != nil { if daemon { log.Printf(CTL_NOT_CLEAR, controlSocket) } else { fmt.Printf(CTL_NOT_CLEAR, controlSocket) } } _, err = f.Seek(0, 0) if err != nil { if daemon { log.Printf(CTL_NOT_REWOUND, controlSocket) } else { fmt.Printf(CTL_NOT_REWOUND, controlSocket) } } return f } func runDaemon() { go headlessControls() for { log.Printf(DAEMON_FILE_REFRESH, serviceFile) startup, err := readLines(serviceFile) if err != nil { if errors.Is(err, os.ErrNotExist) { test, err := os.Create(serviceFile) if err != nil { log.Fatalln(err) } test.Close() } else { log.Fatalln(err) } } for _, svc := range startup { running := false svcArgs := strings.Split(svc, " ") for runningProc := range services { if svcArgs[0] == runningProc { running = true log.Printf(DAEMON_SVC_EXISTS, svcArgs[0], services[svcArgs[0]]) } } if !running { log.Printf(DAEMON_SVC_MISSING, svcArgs[0]) go execCommand(svcArgs[0], svcArgs[1:]...) } } err = watchFile(serviceFile) if err != nil { log.Println(err) } } } func headlessControls() { for { err := watchFile(controlSocket) if err != nil { log.Println(err) } pendingCommands, err := readLines(controlSocket) if err != nil { log.Println(err) } else { for _, cmd := range pendingCommands { log.Printf(DAEMON_CTL_PROCESSING, cmd) start := time.Now() err = processCommand(cmd) if err != nil { log.Printf(DAEMON_CTL_FAILED, err, time.Since(start)) } else { log.Printf(DAEMON_CTL_PROCESSED, cmd, time.Since(start)) } } } } } func processCommand(cmd string) error { parts := strings.Split(cmd, " ") verb := parts[0] svc := parts[1] start := time.Now() if verb == "restart" || verb == "stop" { f, err := os.OpenFile(fmt.Sprintf("/proc/%d/ctl", services[svc].ProcessHandle.Pid), os.O_WRONLY, 0660) if err != nil { log.Printf("") } defer f.Close() _, err = f.WriteString("kill") if err != nil { log.Printf(DAEMON_CTL_FAILED, err, time.Since(start)) } else { delete(services, svc) log.Printf(DAEMON_CTL_PROCESSED, svc, time.Since(start)) } } if verb == "restart" || verb == "start" { startup, err := readLines(serviceFile) if err != nil { log.Fatalln(err) } for _, testSvc := range startup { svcArgs := strings.Split(testSvc, " ") if svc != testSvc { continue } log.Printf(DAEMON_SVC_MISSING, svcArgs[0]) go execCommand(svcArgs[0], svcArgs[1:]...) } } return nil } func execCommand(cmd string, arg ...string) { defer PanicSafe() if len(cmd) < 2 { log.Printf(DAEMON_SVC_INVALID, cmd, arg) return } if strings.HasPrefix(cmd, "#") { log.Printf(DAEMON_SVC_DISABLED, cmd) return } proc := exec.Command(cmd, arg...) // open the out file for writing outfile, err := os.Create(fmt.Sprintf("%+v/%+v.log", logPath, cmd)) if err != nil { panic(err) } infile, err := os.Create(fmt.Sprintf("%+v/%+v.ctl", logPath, cmd)) if err != nil { panic(err) } defer outfile.Close() defer infile.Close() proc.Stderr = outfile proc.Stdout = outfile proc.Stdin = infile err = proc.Start() if err != nil { log.Printf(DAEMON_SVC_FAIL, cmd) return } services[cmd] = Service{ ProcessHandle: proc.Process, StartupArgs: arg, StartTime: time.Now(), } proc.Wait() } // readLines reads a whole file into memory // and returns a slice of its lines. func readLines(path string) ([]string, error) { file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() var lines []string scanner := bufio.NewScanner(file) for scanner.Scan() { lines = append(lines, scanner.Text()) } return lines, scanner.Err() } func watchFile(filePath string) error { initialStat, err := os.Stat(filePath) if err != nil { return err } for { stat, err := os.Stat(filePath) if err != nil { return err } if stat.Size() != initialStat.Size() || stat.ModTime() != initialStat.ModTime() { break } time.Sleep(250 * time.Millisecond) } return nil } // PanicSafe is a deferrable function to recover from a panic operation. func PanicSafe(a ...interface{}) { if r := recover(); r != nil { log.Printf("Panic detected: %+v", r) log.Printf("Optional panic data: %+v", a...) } } // count for number of lines, path of file func sysTail(count int, path string) []string { c := exec.Command("tail", fmt.Sprintf("-%d", count+1), path) output, _ := c.Output() //log.Printf("SysTail call output: %+v\nEND", string(output)) lines := strings.Split(string(output), "\n") return lines[:len(lines)-1] }