From 7c2e4f75a4582116aad3828eac70db35d32320c8 Mon Sep 17 00:00:00 2001 From: chbuescher Date: Wed, 11 Dec 2019 04:14:15 +0100 Subject: [PATCH] Initial support for aix and solaris (#194) * initial support for aix and solaris * support for aix & solaris - missing changes --- service.go | 2 + service_aix.go | 278 ++++++++++++++++++++++++++++++++++++++++ service_solaris.go | 308 +++++++++++++++++++++++++++++++++++++++++++++ service_unix.go | 2 +- 4 files changed, 589 insertions(+), 1 deletion(-) create mode 100644 service_aix.go create mode 100644 service_solaris.go diff --git a/service.go b/service.go index cf41748..cec456c 100644 --- a/service.go +++ b/service.go @@ -77,6 +77,8 @@ const ( optionSessionCreateDefault = false optionLogOutput = "LogOutput" optionLogOutputDefault = false + optionPrefix = "Prefix" + optionPrefixDefault = "application" optionRunWait = "RunWait" optionReloadSignal = "ReloadSignal" diff --git a/service_aix.go b/service_aix.go new file mode 100644 index 0000000..af7e20c --- /dev/null +++ b/service_aix.go @@ -0,0 +1,278 @@ +// 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 ( + "bytes" + "fmt" + "os" + "os/exec" + "os/signal" + "regexp" + "strconv" + "strings" + "syscall" + "text/template" + "time" +) + +const maxPathSize = 32 * 1024 + +const version = "aix-ssrc" + +type aixSystem struct{} + +func (aixSystem) String() string { + return version +} +func (aixSystem) Detect() bool { + return true +} +func (aixSystem) Interactive() bool { + return interactive +} +func (aixSystem) New(i Interface, c *Config) (Service, error) { + s := &aixService{ + i: i, + Config: c, + } + + return s, nil +} + +func getPidOfSvcMaster() int { + pat := regexp.MustCompile(`\s+root\s+(\d+)\s+\d+\s+\d+\s+\w+\s+\d+\s+\S+\s+[0-9:]+\s+/usr/sbin/srcmstr`) + cmd := exec.Command("ps", "-ef") + var out bytes.Buffer + cmd.Stdout = &out + pid := 0 + if err := cmd.Run(); err == nil { + matches := pat.FindAllStringSubmatch(out.String(),-1) + for _, match := range matches { + pid, _ = strconv.Atoi(match[1]) + break + } + } + return pid +} + +func init() { + ChooseSystem(aixSystem{}) +} + +var interactive = false + +func init() { + var err error + interactive, err = isInteractive() + if err != nil { + panic(err) + } +} + +func isInteractive() (bool, error) { + // The PPid of a service process should match PID of srcmstr. + return os.Getppid() != getPidOfSvcMaster(), nil +} + +type aixService struct { + i Interface + *Config +} + +func (s *aixService) String() string { + if len(s.DisplayName) > 0 { + return s.DisplayName + } + return s.Name +} + +func (s *aixService) Platform() string { + return version +} + +func (s *aixService) template() *template.Template { + functions := template.FuncMap{ + "bool": func(v bool) string { + if v { + return "true" + } + return "false" + }, + } + + customConfig := s.Option.string(optionSysvScript, "") + + if customConfig != "" { + return template.Must(template.New("").Funcs(functions).Parse(customConfig)) + } else { + return template.Must(template.New("").Funcs(functions).Parse(svcConfig)) + } +} + +func (s *aixService) configPath() (cp string, err error) { + cp = "/etc/rc.d/init.d/" + s.Config.Name + return +} + +func (s *aixService) Install() error { + // install service + path, err := s.execPath() + if err != nil { + return err + } + err = run("mkssys", "-s", s.Name, "-p", path, "-u", "0", "-R", "-Q", "-S", "-n", "15", "-f", "9", "-d", "-w", "30" ) + if err != nil { + return err + } + + // write start script + confPath, err := s.configPath() + if err != nil { + return err + } + _, err = os.Stat(confPath) + if err == nil { + return fmt.Errorf("Init already exists: %s", confPath) + } + + f, err := os.Create(confPath) + if err != nil { + return err + } + defer f.Close() + + var to = &struct { + *Config + Path string + }{ + s.Config, + path, + } + + err = s.template().Execute(f, to) + if err != nil { + return err + } + + if err = os.Chmod(confPath, 0755); err != nil { + return err + } + for _, i := range [...]string{"2", "3"} { + if err = os.Symlink(confPath, "/etc/rc"+i+".d/S50"+s.Name); err != nil { + continue + } + if err = os.Symlink(confPath, "/etc/rc"+i+".d/K02"+s.Name); err != nil { + continue + } + } + + return nil +} + +func (s *aixService) Uninstall() error { + s.Stop() + + err := run("rmssys", "-s", s.Name) + if err != nil { + return err + } + + confPath, err := s.configPath() + if err != nil { + return err + } + return os.Remove(confPath) +} + +func (s *aixService) Status() (Status, error) { + exitCode, out, err := runWithOutput("lssrc", "-s", s.Name) + if exitCode == 0 && err != nil { + if !strings.Contains(err.Error(), "failed with stderr") { + return StatusUnknown, err + } + } + + re := regexp.MustCompile(`\s+` + s.Name + `\s+(\w+\s+)?(\d+\s+)?(\w+)`) + matches := re.FindStringSubmatch(out) + if len(matches) == 4 { + status := string(matches[3]) + if status == "inoperative" { + return StatusStopped, nil + } else if status == "active" { + return StatusRunning, nil + } else { + fmt.Printf("Got unknown service status %s\n", status) + return StatusUnknown, err + } + } + + confPath, err := s.configPath() + if err != nil { + return StatusUnknown, err + } + + if _, err = os.Stat(confPath); err == nil { + return StatusStopped, nil + } + + return StatusUnknown, ErrNotInstalled +} + +func (s *aixService) Start() error { + return run("startsrc", "-s", s.Name) +} +func (s *aixService) Stop() error { + return run("stopsrc", "-s", s.Name) +} +func (s *aixService) Restart() error { + err := s.Stop() + if err != nil { + return err + } + time.Sleep(50 * time.Millisecond) + return s.Start() +} + +func (s *aixService) Run() error { + var err 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 *aixService) Logger(errs chan<- error) (Logger, error) { + if interactive { + return ConsoleLogger, nil + } + return s.SystemLogger(errs) +} +func (s *aixService) SystemLogger(errs chan<- error) (Logger, error) { + return newSysLogger(s.Name, errs) +} + +var svcConfig = `#!/bin/ksh +case "$1" in +start ) + startsrc -s {{.Name}} + ;; +stop ) + stopsrc -s {{.Name}} + ;; +* ) + echo "Usage: $0 (start | stop)" + exit 1 +esac +` diff --git a/service_solaris.go b/service_solaris.go new file mode 100644 index 0000000..b03357c --- /dev/null +++ b/service_solaris.go @@ -0,0 +1,308 @@ +// 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 ( + "bytes" + "encoding/xml" + "fmt" + "os" + "os/signal" + "regexp" + "syscall" + "text/template" + "time" +) + +const maxPathSize = 32 * 1024 + +const version = "solaris-smf" + +type solarisSystem struct{} + +func (solarisSystem) String() string { + return version +} +func (solarisSystem) Detect() bool { + return true +} +func (solarisSystem) Interactive() bool { + return interactive +} +func (solarisSystem) New(i Interface, c *Config) (Service, error) { + s := &solarisService{ + i: i, + Config: c, + + Prefix: c.Option.string(optionPrefix, optionPrefixDefault), + } + + return s, nil +} + +func init() { + ChooseSystem(solarisSystem{}) +} + +var interactive = false + +func init() { + var err error + interactive, err = isInteractive() + if err != nil { + panic(err) + } +} + +func isInteractive() (bool, error) { + // The PPid of a service process be 1 / init. + return os.Getppid() != 1, nil +} + +type solarisService struct { + i Interface + *Config + + Prefix string +} + +func (s *solarisService) String() string { + if len(s.DisplayName) > 0 { + return s.DisplayName + } + return s.Name +} + +func (s *solarisService) Platform() string { + return version +} + +func (s *solarisService) template() *template.Template { + functions := template.FuncMap{ + "bool": func(v bool) string { + if v { + return "true" + } + return "false" + }, + } + + customConfig := s.Option.string(optionSysvScript, "") + + if customConfig != "" { + return template.Must(template.New("").Funcs(functions).Parse(customConfig)) + } else { + return template.Must(template.New("").Funcs(functions).Parse(manifest)) + } +} + +func (s *solarisService) configPath() (string, error) { + return "/lib/svc/manifest/" + s.Prefix + "/" + s.Config.Name + ".xml", nil +} + +func (s *solarisService) getFMRI() string { + return "svc:/" + s.Prefix + "/" + s.Config.Name + ":default" +} + +func (s *solarisService) Install() error { + // write start script + confPath, err := s.configPath() + if err != nil { + return err + } + _, err = os.Stat(confPath) + if err == nil { + return fmt.Errorf("Manifest already exists: %s", confPath) + } + + f, err := os.Create(confPath) + if err != nil { + return err + } + defer f.Close() + + path, err := s.execPath() + if err != nil { + return err + } + Display := "" + escaped := &bytes.Buffer{} + if err := xml.EscapeText(escaped, []byte(s.DisplayName)); err == nil { + Display = escaped.String() + } + var to = &struct { + *Config + Prefix string + Display string + Path string + }{ + s.Config, + s.Prefix, + Display, + path, + } + + err = s.template().Execute(f, to) + if err != nil { + return err + } + + // import service + err = run("svcadm", "restart", "manifest-import" ) + if err != nil { + return err + } + + return nil +} + +func (s *solarisService) Uninstall() error { + s.Stop() + + confPath, err := s.configPath() + if err != nil { + return err + } + err = os.Remove(confPath) + if err != nil { + return err + } + + // unregister service + err = run("svcadm", "restart", "manifest-import" ) + if err != nil { + return err + } + + return nil +} + +func (s *solarisService) Status() (Status, error) { + fmri := s.getFMRI() + exitCode, out, err := runWithOutput("svcs", fmri) + if exitCode != 0 { + return StatusUnknown, ErrNotInstalled + } + + re := regexp.MustCompile(`(degraded|disabled|legacy_run|maintenance|offline|online)\s+\w+` + fmri) + matches := re.FindStringSubmatch(out) + if len(matches) == 2 { + status := string(matches[1]) + if status == "online" { + return StatusRunning, nil + } else { + return StatusStopped, nil + } + } + return StatusUnknown, err +} + +func (s *solarisService) Start() error { + return run("/usr/sbin/svcadm", "enable", s.getFMRI()) +} +func (s *solarisService) Stop() error { + return run("/usr/sbin/svcadm", "disable", s.getFMRI()) +} +func (s *solarisService) Restart() error { + err := s.Stop() + if err != nil { + return err + } + time.Sleep(50 * time.Millisecond) + return s.Start() +} + +func (s *solarisService) Run() error { + var err 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 *solarisService) Logger(errs chan<- error) (Logger, error) { + if interactive { + return ConsoleLogger, nil + } + return s.SystemLogger(errs) +} +func (s *solarisService) SystemLogger(errs chan<- error) (Logger, error) { + return newSysLogger(s.Name, errs) +} + +var manifest = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +` diff --git a/service_unix.go b/service_unix.go index 1b4be3b..b98189f 100644 --- a/service_unix.go +++ b/service_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. -// +build linux darwin +// +build linux darwin solaris aix package service