-
Notifications
You must be signed in to change notification settings - Fork 0
/
daemon.go
108 lines (102 loc) · 3.28 KB
/
daemon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package main
import (
"fmt"
"net"
"os"
"os/exec"
"os/user"
"slices"
"strconv"
"syscall"
)
// id returns the UID and GID for the given user and group which can be either identifiers or names
func id(u, g string) (uint32, uint32, error) {
var uid, gid int
if g != "" {
if grp, err := user.LookupGroup(g); err == nil {
if gid, err = strconv.Atoi(grp.Gid); err != nil {
return 0, 0, fmt.Errorf("invalid GID for group %s: %v", g, err)
}
} else {
return 0, 0, fmt.Errorf("failed to lookup group %s: %v", g, err)
}
}
if u != "" {
if usr, err := user.Lookup(u); err == nil {
if uid, err = strconv.Atoi(usr.Uid); err != nil {
return 0, 0, fmt.Errorf("invalid UID for user %s: %v", u, err)
}
if g == "" {
if gid, err = strconv.Atoi(usr.Gid); err != nil {
return 0, 0, fmt.Errorf("invalid GID for user %s: %v", u, err)
}
}
} else {
return 0, 0, fmt.Errorf("failed to lookup user %s: %v", u, err)
}
}
return uint32(uid), uint32(gid), nil
}
// Listener returns the environment variable for the listener file descriptor it adds to extraFiles
func Listener(on string, extraFiles []*os.File) error {
if listener, err := net.Listen("tcp", on); err != nil {
return fmt.Errorf("unable to listen on %s: %v", on, err)
} else if file, err := listener.(*net.TCPListener).File(); err != nil {
return fmt.Errorf("unable to get listener file: %v", err)
} else {
extraFiles[0] = file
return nil
}
}
// RunDaemon starts the child process in the background and exits the parent process
func RunDaemon(pidFilePath, user, group string, env []string, extraFiles []*os.File) int {
// cmd is the background (child) process this (parent) process will start then exit
cmd := exec.Command(os.Args[0])
cmd.Env = env
cmd.ExtraFiles = extraFiles
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
// Remove flags only relavent to the parent process
dFlagIndex := slices.Index(os.Args, "-d")
gFlagIndex := slices.Index(os.Args, "-g")
uFlagIndex := slices.Index(os.Args, "-u")
vFlagIndex := slices.Index(os.Args, "-v")
for i := 1; i < len(os.Args); i++ {
switch i {
case gFlagIndex, uFlagIndex:
i++ // Skip the flag's argument
case dFlagIndex, vFlagIndex:
default:
cmd.Args = append(cmd.Args, os.Args[i])
}
}
// Set the user and group for the child process, i.e., drop root privileges
if user != "" || group != "" {
if uid, gid, err := id(user, group); err == nil {
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
} else {
fmt.Fprintf(os.Stderr, "unable to set user and group: %v\n", err)
os.Exit(1)
}
}
// Start the child process in the background
if err := cmd.Start(); err != nil {
fmt.Fprintf(os.Stderr, "unable to start as a daemon: %v\n", err)
os.Exit(1)
}
// exitAll exits the parent process after killing the child process
exitAll := func(status int, message string, args ...any) {
fmt.Fprintf(os.Stderr, message, args...)
cmd.Process.Kill()
os.Exit(status)
}
// Write the PID file
if pidFile, err := os.Create(pidFilePath); err != nil {
exitAll(1, "unable to create PID file: '%s': %v\n", pidFilePath, err)
} else {
defer pidFile.Close()
if _, err := pidFile.WriteString(strconv.Itoa(cmd.Process.Pid)); err != nil {
exitAll(1, "unable to write PID to file: %v\n", err)
}
}
return cmd.Process.Pid
}