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 "bytes" 9 "encoding/xml" 10 "fmt" 11 "os" 12 "os/signal" 13 "regexp" 14 "syscall" 15 "text/template" 16 "time" 17) 18 19const maxPathSize = 32 * 1024 20 21const version = "solaris-smf" 22 23type solarisSystem struct{} 24 25func (solarisSystem) String() string { 26 return version 27} 28func (solarisSystem) Detect() bool { 29 return true 30} 31func (solarisSystem) Interactive() bool { 32 return interactive 33} 34func (solarisSystem) New(i Interface, c *Config) (Service, error) { 35 s := &solarisService{ 36 i: i, 37 Config: c, 38 39 Prefix: c.Option.string(optionPrefix, optionPrefixDefault), 40 } 41 42 return s, nil 43} 44 45func init() { 46 ChooseSystem(solarisSystem{}) 47} 48 49var interactive = false 50 51func init() { 52 var err error 53 interactive, err = isInteractive() 54 if err != nil { 55 panic(err) 56 } 57} 58 59func isInteractive() (bool, error) { 60 // The PPid of a service process be 1 / init. 61 return os.Getppid() != 1, nil 62} 63 64type solarisService struct { 65 i Interface 66 *Config 67 68 Prefix string 69} 70 71func (s *solarisService) String() string { 72 if len(s.DisplayName) > 0 { 73 return s.DisplayName 74 } 75 return s.Name 76} 77 78func (s *solarisService) Platform() string { 79 return version 80} 81 82func (s *solarisService) template() *template.Template { 83 functions := template.FuncMap{ 84 "bool": func(v bool) string { 85 if v { 86 return "true" 87 } 88 return "false" 89 }, 90 } 91 92 customConfig := s.Option.string(optionSysvScript, "") 93 94 if customConfig != "" { 95 return template.Must(template.New("").Funcs(functions).Parse(customConfig)) 96 } else { 97 return template.Must(template.New("").Funcs(functions).Parse(manifest)) 98 } 99} 100 101func (s *solarisService) configPath() (string, error) { 102 return "/lib/svc/manifest/" + s.Prefix + "/" + s.Config.Name + ".xml", nil 103} 104 105func (s *solarisService) getFMRI() string { 106 return "svc:/" + s.Prefix + "/" + s.Config.Name + ":default" 107} 108 109func (s *solarisService) Install() error { 110 // write start script 111 confPath, err := s.configPath() 112 if err != nil { 113 return err 114 } 115 _, err = os.Stat(confPath) 116 if err == nil { 117 return fmt.Errorf("Manifest already exists: %s", confPath) 118 } 119 120 f, err := os.Create(confPath) 121 if err != nil { 122 return err 123 } 124 defer f.Close() 125 126 path, err := s.execPath() 127 if err != nil { 128 return err 129 } 130 Display := "" 131 escaped := &bytes.Buffer{} 132 if err := xml.EscapeText(escaped, []byte(s.DisplayName)); err == nil { 133 Display = escaped.String() 134 } 135 var to = &struct { 136 *Config 137 Prefix string 138 Display string 139 Path string 140 }{ 141 s.Config, 142 s.Prefix, 143 Display, 144 path, 145 } 146 147 err = s.template().Execute(f, to) 148 if err != nil { 149 return err 150 } 151 152 // import service 153 err = run("svcadm", "restart", "manifest-import") 154 if err != nil { 155 return err 156 } 157 158 return nil 159} 160 161func (s *solarisService) Uninstall() error { 162 s.Stop() 163 164 confPath, err := s.configPath() 165 if err != nil { 166 return err 167 } 168 err = os.Remove(confPath) 169 if err != nil { 170 return err 171 } 172 173 // unregister service 174 err = run("svcadm", "restart", "manifest-import") 175 if err != nil { 176 return err 177 } 178 179 return nil 180} 181 182func (s *solarisService) Status() (Status, error) { 183 fmri := s.getFMRI() 184 exitCode, out, err := runWithOutput("svcs", fmri) 185 if exitCode != 0 { 186 return StatusUnknown, ErrNotInstalled 187 } 188 189 re := regexp.MustCompile(`(degraded|disabled|legacy_run|maintenance|offline|online)\s+\w+` + fmri) 190 matches := re.FindStringSubmatch(out) 191 if len(matches) == 2 { 192 status := string(matches[1]) 193 if status == "online" { 194 return StatusRunning, nil 195 } else { 196 return StatusStopped, nil 197 } 198 } 199 return StatusUnknown, err 200} 201 202func (s *solarisService) Start() error { 203 return run("/usr/sbin/svcadm", "enable", s.getFMRI()) 204} 205func (s *solarisService) Stop() error { 206 return run("/usr/sbin/svcadm", "disable", s.getFMRI()) 207} 208func (s *solarisService) Restart() error { 209 err := s.Stop() 210 if err != nil { 211 return err 212 } 213 time.Sleep(50 * time.Millisecond) 214 return s.Start() 215} 216 217func (s *solarisService) Run() error { 218 var err error 219 220 err = s.i.Start(s) 221 if err != nil { 222 return err 223 } 224 225 s.Option.funcSingle(optionRunWait, func() { 226 var sigChan = make(chan os.Signal, 3) 227 signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) 228 <-sigChan 229 })() 230 231 return s.i.Stop(s) 232} 233 234func (s *solarisService) Logger(errs chan<- error) (Logger, error) { 235 if interactive { 236 return ConsoleLogger, nil 237 } 238 return s.SystemLogger(errs) 239} 240func (s *solarisService) SystemLogger(errs chan<- error) (Logger, error) { 241 return newSysLogger(s.Name, errs) 242} 243 244var manifest = `<?xml version="1.0"?> 245<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> 246 247<service_bundle type='manifest' name='golang-{{.Name}}'> 248<service 249 name='{{.Prefix}}/{{.Name}}' 250 type='service' 251 version='1'> 252 253 <create_default_instance enabled='false' /> 254 255 <single_instance /> 256 257 <!-- 258 Wait for network interfaces to be initialized. 259 --> 260 <dependency name='network' 261 grouping='require_all' 262 restart_on='restart' 263 type='service'> 264 <service_fmri value='svc:/milestone/network:default'/> 265 </dependency> 266 267 <!-- 268 Wait for all local filesystems to be mounted. 269 --> 270 <dependency name='filesystem-local' 271 grouping='require_all' 272 restart_on='none' 273 type='service'> 274 <service_fmri 275 value='svc:/system/filesystem/local:default'/> 276 </dependency> 277 278 <exec_method 279 type='method' 280 name='start' 281 exec='bash -c {{.Path}} &' 282 timeout_seconds='10' /> 283 284 <exec_method 285 type='method' 286 name='stop' 287 exec='pkill -TERM -f {{.Path}}' 288 timeout_seconds='60' /> 289 290 <!-- 291 <property_group name='startd' type='framework'> 292 <propval name='duration' type='astring' value='transient' /> 293 </property_group> 294 --> 295 296 <stability value='Unstable' /> 297 298 <template> 299 <common_name> 300 <loctext xml:lang='C'> 301 {{.Display}} 302 </loctext> 303 </common_name> 304 </template> 305</service> 306 307</service_bundle> 308` 309