dnscrypt-proxy/vendor/github.com/kardianos/service/service_darwin.go

348 lines
7.2 KiB
Go

// Copyright 2015 Daniel Theophanes.
// Use of this source code is governed by a zlib-style
// license that can be found in the LICENSE file.
package service
import (
"errors"
"fmt"
"os"
"os/signal"
"os/user"
"path/filepath"
"regexp"
"strings"
"syscall"
"text/template"
"time"
)
const maxPathSize = 32 * 1024
const (
version = "darwin-launchd"
defaultDarwinLogDirectory = "/var/log"
)
type darwinSystem struct{}
func (darwinSystem) String() string {
return version
}
func (darwinSystem) Detect() bool {
return true
}
func (darwinSystem) Interactive() bool {
return interactive
}
func (darwinSystem) New(i Interface, c *Config) (Service, error) {
s := &darwinLaunchdService{
i: i,
Config: c,
userService: c.Option.bool(optionUserService, optionUserServiceDefault),
}
return s, nil
}
func init() {
ChooseSystem(darwinSystem{})
}
var interactive = false
func init() {
var err error
interactive, err = isInteractive()
if err != nil {
panic(err)
}
}
func isInteractive() (bool, error) {
// TODO: The PPID of Launchd is 1. The PPid of a service process should match launchd's PID.
return os.Getppid() != 1, nil
}
type darwinLaunchdService struct {
i Interface
*Config
userService bool
}
func (s *darwinLaunchdService) String() string {
if len(s.DisplayName) > 0 {
return s.DisplayName
}
return s.Name
}
func (s *darwinLaunchdService) Platform() string {
return version
}
func (s *darwinLaunchdService) getHomeDir() (string, error) {
u, err := user.Current()
if err == nil {
return u.HomeDir, nil
}
// alternate methods
homeDir := os.Getenv("HOME") // *nix
if homeDir == "" {
return "", errors.New("User home directory not found.")
}
return homeDir, nil
}
func (s *darwinLaunchdService) getServiceFilePath() (string, error) {
if s.userService {
homeDir, err := s.getHomeDir()
if err != nil {
return "", err
}
return homeDir + "/Library/LaunchAgents/" + s.Name + ".plist", nil
}
return "/Library/LaunchDaemons/" + s.Name + ".plist", nil
}
func (s *darwinLaunchdService) logDir() (string, error) {
if customDir := s.Option.string(optionLogDirectory, ""); customDir != "" {
return customDir, nil
}
if !s.userService {
return defaultDarwinLogDirectory, nil
}
return s.getHomeDir()
}
func (s *darwinLaunchdService) getLogPaths() (string, string, error) {
logDir, err := s.logDir()
if err != nil {
return "", "", err
}
return s.getLogPath(logDir, "out"), s.getLogPath(logDir, "err"), nil
}
func (s *darwinLaunchdService) getLogPath(logDir, logType string) string {
return fmt.Sprintf("%s/%s.%s.log", logDir, s.Name, logType)
}
func (s *darwinLaunchdService) template() *template.Template {
functions := template.FuncMap{
"bool": func(v bool) string {
if v {
return "true"
}
return "false"
},
}
customConfig := s.Option.string(optionLaunchdConfig, "")
if customConfig != "" {
return template.Must(template.New("").Funcs(functions).Parse(customConfig))
}
return template.Must(template.New("").Funcs(functions).Parse(launchdConfig))
}
func (s *darwinLaunchdService) Install() error {
confPath, err := s.getServiceFilePath()
if err != nil {
return err
}
_, err = os.Stat(confPath)
if err == nil {
return fmt.Errorf("Init already exists: %s", confPath)
}
if s.userService {
// Ensure that ~/Library/LaunchAgents exists.
err = os.MkdirAll(filepath.Dir(confPath), 0700)
if err != nil {
return err
}
}
f, err := os.Create(confPath)
if err != nil {
return err
}
defer f.Close()
path, err := s.execPath()
if err != nil {
return err
}
stdOutPath, stdErrPath, _ := s.getLogPaths()
var to = &struct {
*Config
Path string
KeepAlive, RunAtLoad bool
SessionCreate bool
StandardOutPath string
StandardErrorPath string
}{
Config: s.Config,
Path: path,
KeepAlive: s.Option.bool(optionKeepAlive, optionKeepAliveDefault),
RunAtLoad: s.Option.bool(optionRunAtLoad, optionRunAtLoadDefault),
SessionCreate: s.Option.bool(optionSessionCreate, optionSessionCreateDefault),
StandardOutPath: stdOutPath,
StandardErrorPath: stdErrPath,
}
return s.template().Execute(f, to)
}
func (s *darwinLaunchdService) Uninstall() error {
s.Stop()
confPath, err := s.getServiceFilePath()
if err != nil {
return err
}
return os.Remove(confPath)
}
func (s *darwinLaunchdService) Status() (Status, error) {
exitCode, out, err := runWithOutput("launchctl", "list", s.Name)
if exitCode == 0 && err != nil {
if !strings.Contains(err.Error(), "failed with stderr") {
return StatusUnknown, err
}
}
re := regexp.MustCompile(`"PID" = ([0-9]+);`)
matches := re.FindStringSubmatch(out)
if len(matches) == 2 {
return StatusRunning, nil
}
confPath, err := s.getServiceFilePath()
if err != nil {
return StatusUnknown, err
}
if _, err = os.Stat(confPath); err == nil {
return StatusStopped, nil
}
return StatusUnknown, ErrNotInstalled
}
func (s *darwinLaunchdService) Start() error {
confPath, err := s.getServiceFilePath()
if err != nil {
return err
}
return run("launchctl", "load", confPath)
}
func (s *darwinLaunchdService) Stop() error {
confPath, err := s.getServiceFilePath()
if err != nil {
return err
}
return run("launchctl", "unload", confPath)
}
func (s *darwinLaunchdService) Restart() error {
err := s.Stop()
if err != nil {
return err
}
time.Sleep(50 * time.Millisecond)
return s.Start()
}
func (s *darwinLaunchdService) Run() error {
err := s.i.Start(s)
if err != nil {
return err
}
s.Option.funcSingle(optionRunWait, func() {
var sigChan = make(chan os.Signal, 3)
signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt)
<-sigChan
})()
return s.i.Stop(s)
}
func (s *darwinLaunchdService) Logger(errs chan<- error) (Logger, error) {
if interactive {
return ConsoleLogger, nil
}
return s.SystemLogger(errs)
}
func (s *darwinLaunchdService) SystemLogger(errs chan<- error) (Logger, error) {
return newSysLogger(s.Name, errs)
}
var launchdConfig = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Disabled</key>
<false/>
{{- if .EnvVars}}
<key>EnvironmentVariables</key>
<dict>
{{- range $k, $v := .EnvVars}}
<key>{{html $k}}</key>
<string>{{html $v}}</string>
{{- end}}
</dict>
{{- end}}
<key>KeepAlive</key>
<{{bool .KeepAlive}}/>
<key>Label</key>
<string>{{html .Name}}</string>
<key>ProgramArguments</key>
<array>
<string>{{html .Path}}</string>
{{- if .Config.Arguments}}
{{- range .Config.Arguments}}
<string>{{html .}}</string>
{{- end}}
{{- end}}
</array>
{{- if .ChRoot}}
<key>RootDirectory</key>
<string>{{html .ChRoot}}</string>
{{- end}}
<key>RunAtLoad</key>
<{{bool .RunAtLoad}}/>
<key>SessionCreate</key>
<{{bool .SessionCreate}}/>
{{- if .StandardErrorPath}}
<key>StandardErrorPath</key>
<string>{{html .StandardErrorPath}}</string>
{{- end}}
{{- if .StandardOutPath}}
<key>StandardOutPath</key>
<string>{{html .StandardOutPath}}</string>
{{- end}}
{{- if .UserName}}
<key>UserName</key>
<string>{{html .UserName}}</string>
{{- end}}
{{- if .WorkingDirectory}}
<key>WorkingDirectory</key>
<string>{{html .WorkingDirectory}}</string>
{{- end}}
</dict>
</plist>
`