1// Copyright 2015 Daniel Theophanes. 2// Use of this source code is governed by a zlib-style 3// license that can be found in the LICENSE file. 4 5package service 6 7import ( 8 "errors" 9 "fmt" 10 "os" 11 "os/signal" 12 "regexp" 13 "strings" 14 "syscall" 15 "text/template" 16) 17 18func isUpstart() bool { 19 if _, err := os.Stat("/sbin/upstart-udev-bridge"); err == nil { 20 return true 21 } 22 if _, err := os.Stat("/sbin/initctl"); err == nil { 23 if _, out, err := runWithOutput("/sbin/initctl", "--version"); err == nil { 24 if strings.Contains(out, "initctl (upstart") { 25 return true 26 } 27 } 28 } 29 return false 30} 31 32type upstart struct { 33 i Interface 34 platform string 35 *Config 36} 37 38func newUpstartService(i Interface, platform string, c *Config) (Service, error) { 39 s := &upstart{ 40 i: i, 41 platform: platform, 42 Config: c, 43 } 44 45 return s, nil 46} 47 48func (s *upstart) String() string { 49 if len(s.DisplayName) > 0 { 50 return s.DisplayName 51 } 52 return s.Name 53} 54 55func (s *upstart) Platform() string { 56 return s.platform 57} 58 59// Upstart has some support for user services in graphical sessions. 60// Due to the mix of actual support for user services over versions, just don't bother. 61// Upstart will be replaced by systemd in most cases anyway. 62var errNoUserServiceUpstart = errors.New("User services are not supported on Upstart.") 63 64func (s *upstart) configPath() (cp string, err error) { 65 if s.Option.bool(optionUserService, optionUserServiceDefault) { 66 err = errNoUserServiceUpstart 67 return 68 } 69 cp = "/etc/init/" + s.Config.Name + ".conf" 70 return 71} 72 73func (s *upstart) hasKillStanza() bool { 74 defaultValue := true 75 version := s.getUpstartVersion() 76 if version == nil { 77 return defaultValue 78 } 79 80 maxVersion := []int{0, 6, 5} 81 if matches, err := versionAtMost(version, maxVersion); err != nil || matches { 82 return false 83 } 84 85 return defaultValue 86} 87 88func (s *upstart) hasSetUIDStanza() bool { 89 defaultValue := true 90 version := s.getUpstartVersion() 91 if version == nil { 92 return defaultValue 93 } 94 95 maxVersion := []int{1, 4, 0} 96 if matches, err := versionAtMost(version, maxVersion); err != nil || matches { 97 return false 98 } 99 100 return defaultValue 101} 102 103func (s *upstart) getUpstartVersion() []int { 104 _, out, err := runWithOutput("/sbin/initctl", "--version") 105 if err != nil { 106 return nil 107 } 108 109 re := regexp.MustCompile(`initctl \(upstart (\d+.\d+.\d+)\)`) 110 matches := re.FindStringSubmatch(out) 111 if len(matches) != 2 { 112 return nil 113 } 114 115 return parseVersion(matches[1]) 116} 117 118func (s *upstart) template() *template.Template { 119 customScript := s.Option.string(optionUpstartScript, "") 120 121 if customScript != "" { 122 return template.Must(template.New("").Funcs(tf).Parse(customScript)) 123 } else { 124 return template.Must(template.New("").Funcs(tf).Parse(upstartScript)) 125 } 126} 127 128func (s *upstart) Install() error { 129 confPath, err := s.configPath() 130 if err != nil { 131 return err 132 } 133 _, err = os.Stat(confPath) 134 if err == nil { 135 return fmt.Errorf("Init already exists: %s", confPath) 136 } 137 138 f, err := os.Create(confPath) 139 if err != nil { 140 return err 141 } 142 defer f.Close() 143 144 path, err := s.execPath() 145 if err != nil { 146 return err 147 } 148 149 var to = &struct { 150 *Config 151 Path string 152 HasKillStanza bool 153 HasSetUIDStanza bool 154 LogOutput bool 155 }{ 156 s.Config, 157 path, 158 s.hasKillStanza(), 159 s.hasSetUIDStanza(), 160 s.Option.bool(optionLogOutput, optionLogOutputDefault), 161 } 162 163 return s.template().Execute(f, to) 164} 165 166func (s *upstart) Uninstall() error { 167 cp, err := s.configPath() 168 if err != nil { 169 return err 170 } 171 if err := os.Remove(cp); err != nil { 172 return err 173 } 174 return nil 175} 176 177func (s *upstart) Logger(errs chan<- error) (Logger, error) { 178 if system.Interactive() { 179 return ConsoleLogger, nil 180 } 181 return s.SystemLogger(errs) 182} 183func (s *upstart) SystemLogger(errs chan<- error) (Logger, error) { 184 return newSysLogger(s.Name, errs) 185} 186 187func (s *upstart) Run() (err error) { 188 err = s.i.Start(s) 189 if err != nil { 190 return err 191 } 192 193 s.Option.funcSingle(optionRunWait, func() { 194 var sigChan = make(chan os.Signal, 3) 195 signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) 196 <-sigChan 197 })() 198 199 return s.i.Stop(s) 200} 201 202func (s *upstart) Status() (Status, error) { 203 exitCode, out, err := runWithOutput("initctl", "status", s.Name) 204 if exitCode == 0 && err != nil { 205 return StatusUnknown, err 206 } 207 208 switch { 209 case strings.HasPrefix(out, fmt.Sprintf("%s start/running", s.Name)): 210 return StatusRunning, nil 211 case strings.HasPrefix(out, fmt.Sprintf("%s stop/waiting", s.Name)): 212 return StatusStopped, nil 213 default: 214 return StatusUnknown, ErrNotInstalled 215 } 216} 217 218func (s *upstart) Start() error { 219 return run("initctl", "start", s.Name) 220} 221 222func (s *upstart) Stop() error { 223 return run("initctl", "stop", s.Name) 224} 225 226func (s *upstart) Restart() error { 227 return run("initctl", "restart", s.Name) 228} 229 230// The upstart script should stop with an INT or the Go runtime will terminate 231// the program before the Stop handler can run. 232const upstartScript = `# {{.Description}} 233 234{{if .DisplayName}}description "{{.DisplayName}}"{{end}} 235 236{{if .HasKillStanza}}kill signal INT{{end}} 237{{if .ChRoot}}chroot {{.ChRoot}}{{end}} 238{{if .WorkingDirectory}}chdir {{.WorkingDirectory}}{{end}} 239start on filesystem or runlevel [2345] 240stop on runlevel [!2345] 241 242{{if and .UserName .HasSetUIDStanza}}setuid {{.UserName}}{{end}} 243 244respawn 245respawn limit 10 5 246umask 022 247 248console none 249 250pre-start script 251 test -x {{.Path}} || { stop; exit 0; } 252end script 253 254# Start 255script 256 {{if .LogOutput}} 257 stdout_log="/var/log/{{.Name}}.out" 258 stderr_log="/var/log/{{.Name}}.err" 259 {{end}} 260 261 if [ -f "/etc/sysconfig/{{.Name}}" ]; then 262 set -a 263 source /etc/sysconfig/{{.Name}} 264 set +a 265 fi 266 267 exec {{if and .UserName (not .HasSetUIDStanza)}}sudo -E -u {{.UserName}} {{end}}{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}{{if .LogOutput}} >> $stdout_log 2>> $stderr_log{{end}} 268end script 269` 270