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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
|
package launchd
import (
"bytes"
"errors"
"fmt"
goos "os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"text/template"
"github.com/crc-org/crc/v2/pkg/crc/constants"
"github.com/crc-org/crc/v2/pkg/os"
)
const (
plistTemplate = `<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version='1.0'>
<dict>
<key>Label</key>
<string>{{ .Label }}</string>
<key>ProgramArguments</key>
<array>
<string>{{ .ExecutablePath }}</string>
{{ range .Args }}
<string>{{ . }}</string>
{{ end }}
</array>
<key>StandardOutPath</key>
<string>{{ .StdOutFilePath }}</string>
<key>StandardErrorPath</key>
<string>{{ .StdErrFilePath }}</string>
<key>EnvironmentVariables</key>
<dict>
{{ range $key, $value := .Env }}
<key>{{ $key }}</key>
<string>{{ $value }}</string>
{{ end }}
</dict>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
`
)
// AgentConfig is struct to contain configuration for agent plist file
type AgentConfig struct {
Label string
ExecutablePath string
StdOutFilePath string
StdErrFilePath string
Args []string
Env map[string]string
}
var (
launchAgentsDir = filepath.Join(constants.GetHomeDir(), "Library", "LaunchAgents")
)
func ensureLaunchAgentsDirExists() error {
return goos.MkdirAll(launchAgentsDir, 0700)
}
func getPlistPath(label string) string {
plistName := fmt.Sprintf("%s.plist", label)
return filepath.Join(launchAgentsDir, plistName)
}
func generatePlistContent(config AgentConfig) ([]byte, error) {
var plistContent bytes.Buffer
t, err := template.New("plist").Parse(plistTemplate)
if err != nil {
return nil, err
}
err = t.Execute(&plistContent, config)
if err != nil {
return nil, err
}
return plistContent.Bytes(), nil
}
// CreatePlist creates a launchd agent plist config file
func CreatePlist(config AgentConfig) error {
if err := ensureLaunchAgentsDirExists(); err != nil {
return err
}
plist, err := generatePlistContent(config)
if err != nil {
return err
}
err = goos.WriteFile(getPlistPath(config.Label), plist, 0600)
return err
}
func PlistExists(label string) bool {
return os.FileExists(getPlistPath(label))
}
func CheckPlist(config AgentConfig) error {
plist, err := generatePlistContent(config)
if err != nil {
return err
}
return os.FileContentMatches(getPlistPath(config.Label), plist)
}
// LoadPlist loads a launchd agents' plist file
func LoadPlist(label string) error {
return runLaunchCtl("load", "-w", getPlistPath(label))
}
// UnloadPlist Unloads a launchd agent's service
func UnloadPlist(label string) error {
return runLaunchCtl("unload", "-w", getPlistPath(label))
}
// RemovePlist removes a launchd agent plist config file
func RemovePlist(label string) error {
if _, err := goos.Stat(getPlistPath(label)); !goos.IsNotExist(err) {
return goos.Remove(getPlistPath(label))
}
return nil
}
// StartAgent starts a launchd agent
func StartAgent(label string) error {
return runLaunchCtl("start", label)
}
// StopAgent stops a launchd agent
func StopAgent(label string) error {
return runLaunchCtl("stop", label)
}
// RestartAgent restarts a launchd agent
func RestartAgent(label string) error {
err := StopAgent(label)
if err != nil {
return err
}
return StartAgent(label)
}
// AgentRunning checks if a launchd service is running
func AgentRunning(label string) bool {
// This command return a PID if the process
// is running, otherwise returns "-" or empty
// output if the agent is not loaded in launchd
launchctlListCommand := `launchctl list | grep %s | awk '{print $1}'`
cmd := fmt.Sprintf(launchctlListCommand, label)
out, _, err := os.RunWithDefaultLocale("bash", "-c", cmd)
if err != nil {
return false
}
// match PID
if match, err := regexp.MatchString(`^\d+$`, strings.TrimSpace(string(out))); err == nil && match {
return true
}
return false
}
// Remove removes the agent from launchd
func Remove(label string) error {
return runLaunchCtl("remove", label)
}
func runLaunchCtl(args ...string) error {
_, _, err := os.RunWithDefaultLocale("launchctl", args...)
return exitCodeToError(err)
}
func exitCodeToError(err error) error {
var exitErr *exec.ExitError
if !errors.As(err, &exitErr) {
return err
}
stdout, _, localErr := os.RunWithDefaultLocale("launchctl", "error", fmt.Sprintf("%d", exitErr.ExitCode()))
if localErr != nil {
return err
}
return errors.New(stdout)
}
|