// Package spawn provides utilities for daemonizing services. package spawn import "os" import "fmt" import "time" import "errors" import "syscall" import "os/user" import "strconv" import "path/filepath" // Spawn spawns a process in the background and returns its PID. func Spawn (path string, uid, gid uint32, env []string, args ...string) (pid int, err error) { cred := &syscall.Credential{ Uid: uid, Gid: gid, Groups: []uint32{}, NoSetGroups: false, } // the Noctty flag is used to detach the process from parent tty sysproc := &syscall.SysProcAttr{ Credential: cred, Noctty: true, } attr := os.ProcAttr{ Dir: ".", Env: os.Environ(), Files: []*os.File{ os.Stdin, nil, nil, }, Sys: sysproc, } process, err := os.StartProcess(path, args, &attr) if err != nil { return 0, err } // Release() is what actually detatches the process and places it under // init return process.Pid, process.Release() } // LookupUID returns the uid and gid of the given username, if it exists. func LookupUID (name string) (uid, gid uint32, err error) { user, err := user.Lookup(name) if err != nil { return 0, 0, err } puid, err := strconv.Atoi(user.Uid) if err != nil { return 0, 0, err } pgid, err := strconv.Atoi(user.Gid) if err != nil { return 0, 0, err } return uint32(puid), uint32(pgid), nil } // PidFile returns the path of a pidfile under the specified name. More // specifically, it returns `/run/.pid`. func PidFile (name string) string { return filepath.Join("/run/", name + ".pid") } // PidOf returns the PID stored in the pidfile of the given name as defined by // PidFile. func PidOf (name string) (pid int, err error) { content, err := os.ReadFile(PidFile(name)) if err != nil { return 0, err } pid, err = strconv.Atoi(string(content)) if err != nil { return 0, err } return pid, nil } // Running returns whether or not a process with the given PID is running. func Running (pid int) bool { directoryInfo, err := os.Stat("/proc/") if os.IsNotExist(err) || !directoryInfo.IsDir() { // if /proc/ does not exist, fallback to sending a signal process, err := os.FindProcess(pid) if err != nil { return false } err = process.Signal(syscall.Signal(0)) if err != nil { return false } } else { // if /proc/ exists, see if the process's directory exists there _, err = os.Stat("/proc/" + strconv.Itoa(pid)) if err != nil { return false } } return true } // KillAndWait kills a process and waits for it to finish, with a timeout. If // the timeout is zero, it will wait indefinetly. This function will poll every // 100 milliseconds to see if the process has finished. func KillAndWait (process *os.Process, timeout time.Duration) error { pid := process.Pid err := process.Kill() if err != nil { return err } // wait for the process to exit, with a timeout timeoutPoint := time.Now() for timeout == 0 || time.Since(timeoutPoint) < 16 * time.Second { if !Running(pid) { return nil } time.Sleep(100 * time.Millisecond) } return errors.New(fmt.Sprintf ( "timeout exceeded while waiting for process %d to finish", pid)) }