114 lines
2.7 KiB
Go
114 lines
2.7 KiB
Go
|
// Package spawn provides utilities for daemonizing services.
|
||
|
package spawn
|
||
|
|
||
|
import "os"
|
||
|
import "fmt"
|
||
|
import "time"
|
||
|
import "errors"
|
||
|
import "syscall"
|
||
|
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()
|
||
|
}
|
||
|
|
||
|
// PidFile returns the path of a pidfile under the specified name. More
|
||
|
// specifically, it returns `/run/<name>.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))
|
||
|
}
|