package main import ( "bufio" "flag" "fmt" "log" "os" "os/exec" "strings" "time" ) var ( startup []string daemon = false serviceFile = "/adm/services" services = make(map[string]string) controlSocket = "/adm/headless9/ctl/headless9.ctl" logPath = "/adm/headless9/log/" ) func main() { flag.Parse() if flag.NArg() == 0 { log.Println("Starting headless9") daemon = true runDaemon() } if flag.NArg() != 2 { fmt.Println("Command structure: \"headless9 $verb $service\"") return } verb := flag.Args()[1] service := flag.Args()[2] f := getCtl() _, err := fmt.Fprintf(f, "%+v %+v", verb, service) if err != nil { fmt.Printf("Unable to write to Control Socket. Please check the file %+v and that it's permissions are 700", 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, 0700) if err != nil { if daemon { log.Printf("Unable to open Control Socket. Please check the file %+v and that it's permissions are 700", controlSocket) } else { fmt.Printf("Unable to open Control Socket. Please check the file %+v and that it's permissions are 700", controlSocket) } } err = f.Truncate(0) if err != nil { if daemon { log.Printf("Unable to clear Control Socket. Please check the file %+v and that it's permissions are 700", controlSocket) } else { fmt.Printf("Unable to clear Control Socket. Please check the file %+v and that it's permissions are 700", controlSocket) } } _, err = f.Seek(0, 0) if err != nil { if daemon { log.Printf("Unable to rewind Control Socket. Please check the file %+v and that it's permissions are 700", controlSocket) } else { fmt.Printf("Unable to rewind Control Socket. Please check the file %+v and that it's permissions are 700", controlSocket) } } return f } func runDaemon() { go headlessControls() for { log.Printf("Refreshing controlFile %+v", serviceFile) startup, err := readLines(serviceFile) if err != nil { 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("%+v exists as PID %+v", svcArgs[0], services[svcArgs[0]]) } } if !running { log.Printf("Svc not detected, starting: %+v", 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("Processing command: %+v", cmd) } } } } func execCommand(cmd string, arg ...string) { defer PanicSafe() if len(cmd) < 2 { log.Printf("Invalid command `%v`, skipping. Args: { %+v }", cmd, arg) 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("Error starting service %+v, see log for more info.", cmd) return } services[cmd] = fmt.Sprint(proc.Process.Pid) 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] }