From fe062ea7527e29cb73bfb11505235f86215be82e Mon Sep 17 00:00:00 2001 From: Wenzhou Zhang Date: Wed, 28 Mar 2018 11:24:09 +0800 Subject: [PATCH 1/6] fix windows sc command --- daemon_windows.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/daemon_windows.go b/daemon_windows.go index 54ca1f0..5d9695b 100644 --- a/daemon_windows.go +++ b/daemon_windows.go @@ -10,6 +10,7 @@ import ( "fmt" "os/exec" "regexp" + "strings" "syscall" "unicode/utf16" "unsafe" @@ -37,8 +38,9 @@ func (windows *windowsRecord) Install(args ...string) (string, error) { return installAction + failed, err } - cmdArgs := []string{"create", windows.name, "start=auto", "binPath=" + execp} - cmdArgs = append(cmdArgs, args...) + execpWithArgs := strings.Join(append([]string{"\"" + execp + "\""}, args...), " ") + + cmdArgs := []string{"create", windows.name, "start=", "auto", "binPath=", execpWithArgs} cmd := exec.Command("sc", cmdArgs...) _, err = cmd.Output() From 98d0a02e63e307be5396ec910a2e41d51e3be135 Mon Sep 17 00:00:00 2001 From: Wenzhou Zhang Date: Wed, 28 Mar 2018 15:16:49 +0800 Subject: [PATCH 2/6] add Executable interface --- daemon.go | 13 +++ daemon_darwin.go | 7 ++ daemon_freebsd.go | 7 ++ daemon_linux_systemd.go | 7 ++ daemon_linux_systemv.go | 7 ++ daemon_linux_upstart.go | 7 ++ daemon_windows.go | 220 ++++++++++++++++++++++++++++++++++++---- 7 files changed, 248 insertions(+), 20 deletions(-) diff --git a/daemon.go b/daemon.go index 96e31b5..c771358 100644 --- a/daemon.go +++ b/daemon.go @@ -174,6 +174,19 @@ type Daemon interface { // Status - check the service status Status() (string, error) + + // Run - run executable service + Run(e Executable) (string, error) +} + +// Executable interface defines controlling methods of executable service +type Executable interface { + // Start - non-blocking start service + Start() + // Stop - non-blocking stop service + Stop() + // Run - blocking run service + Run() } // New - Create a new daemon diff --git a/daemon_darwin.go b/daemon_darwin.go index 5fb016c..e639f3c 100644 --- a/daemon_darwin.go +++ b/daemon_darwin.go @@ -186,6 +186,13 @@ func (darwin *darwinRecord) Status() (string, error) { return statusAction, nil } +// Run - Run service +func (darwin *darwinRecord) Run(e Executable) (string, error) { + runAction := "Running " + darwin.description + ":" + e.Run() + return runAction + " completed.", nil +} + var propertyList = ` diff --git a/daemon_freebsd.go b/daemon_freebsd.go index 9451c4e..5bf6403 100644 --- a/daemon_freebsd.go +++ b/daemon_freebsd.go @@ -227,6 +227,13 @@ func (bsd *bsdRecord) Status() (string, error) { return statusAction, nil } +// Run - Run service +func (bsd *bsdRecord) Run(e Executable) (string, error) { + runAction := "Running " + bsd.description + ":" + e.Run() + return runAction + " completed.", nil +} + var bsdConfig = `#!/bin/sh # # PROVIDE: {{.Name}} diff --git a/daemon_linux_systemd.go b/daemon_linux_systemd.go index 299b8c4..bfd928b 100644 --- a/daemon_linux_systemd.go +++ b/daemon_linux_systemd.go @@ -192,6 +192,13 @@ func (linux *systemDRecord) Status() (string, error) { return statusAction, nil } +// Run - Run service +func (linux *systemDRecord) Run(e Executable) (string, error) { + runAction := "Running " + linux.description + ":" + e.Run() + return runAction + " completed.", nil +} + var systemDConfig = `[Unit] Description={{.Description}} Requires={{.Dependencies}} diff --git a/daemon_linux_systemv.go b/daemon_linux_systemv.go index 810e74c..ba2d158 100644 --- a/daemon_linux_systemv.go +++ b/daemon_linux_systemv.go @@ -200,6 +200,13 @@ func (linux *systemVRecord) Status() (string, error) { return statusAction, nil } +// Run - Run service +func (linux *systemVRecord) Run(e Executable) (string, error) { + runAction := "Running " + linux.description + ":" + e.Run() + return runAction + " completed.", nil +} + var systemVConfig = `#! /bin/sh # # /etc/rc.d/init.d/{{.Name}} diff --git a/daemon_linux_upstart.go b/daemon_linux_upstart.go index 899653b..b5f6728 100644 --- a/daemon_linux_upstart.go +++ b/daemon_linux_upstart.go @@ -178,6 +178,13 @@ func (linux *upstartRecord) Status() (string, error) { return statusAction, nil } +// Run - Run service +func (linux *upstartRecord) Run(e Executable) (string, error) { + runAction := "Running " + linux.description + ":" + e.Run() + return runAction + " completed.", nil +} + var upstatConfig = `# {{.Name}} {{.Description}} description "{{.Description}}" diff --git a/daemon_windows.go b/daemon_windows.go index 5d9695b..cfcde3f 100644 --- a/daemon_windows.go +++ b/daemon_windows.go @@ -9,9 +9,9 @@ import ( "errors" "fmt" "os/exec" - "regexp" - "strings" + "strconv" "syscall" + "time" "unicode/utf16" "unsafe" ) @@ -38,59 +38,160 @@ func (windows *windowsRecord) Install(args ...string) (string, error) { return installAction + failed, err } - execpWithArgs := strings.Join(append([]string{"\"" + execp + "\""}, args...), " ") + m, err := mgr.Connect() + if err != nil { + return installAction + failed, err + } + defer m.Disconnect() - cmdArgs := []string{"create", windows.name, "start=", "auto", "binPath=", execpWithArgs} + s, err := m.OpenService(windows.name) + if err == nil { + s.Close() + return installAction + failed, err + } - cmd := exec.Command("sc", cmdArgs...) - _, err = cmd.Output() + s, err = m.CreateService(windows.name, execp, mgr.Config{ + DisplayName: windows.name, + Description: windows.description, + StartType: mgr.StartAutomatic, + Dependencies: windows.dependencies, + }, args...) if err != nil { - return installAction + failed, getWindowsError(err) + return installAction + failed, err } + defer s.Close() + return installAction + " completed.", nil } // Remove the service func (windows *windowsRecord) Remove() (string, error) { removeAction := "Removing " + windows.description + ":" - cmd := exec.Command("sc", "delete", windows.name, "confirm") - err := cmd.Run() + + m, err := mgr.Connect() if err != nil { return removeAction + failed, getWindowsError(err) } + defer m.Disconnect() + s, err := m.OpenService(windows.name) + if err != nil { + return removeAction + failed, getWindowsError(err) + } + defer s.Close() + err = s.Delete() + if err != nil { + return removeAction + failed, getWindowsError(err) + } + return removeAction + " completed.", nil } // Start the service func (windows *windowsRecord) Start() (string, error) { startAction := "Starting " + windows.description + ":" - cmd := exec.Command("sc", "start", windows.name) - err := cmd.Run() + + m, err := mgr.Connect() + if err != nil { + return startAction + failed, getWindowsError(err) + } + defer m.Disconnect() + s, err := m.OpenService(windows.name) if err != nil { return startAction + failed, getWindowsError(err) } + defer s.Close() + if err = s.Start(); err != nil { + return startAction + failed, getWindowsError(err) + } + return startAction + " completed.", nil } // Stop the service func (windows *windowsRecord) Stop() (string, error) { stopAction := "Stopping " + windows.description + ":" - cmd := exec.Command("sc", "stop", windows.name) - err := cmd.Run() + + m, err := mgr.Connect() if err != nil { return stopAction + failed, getWindowsError(err) } + defer m.Disconnect() + s, err := m.OpenService(windows.name) + if err != nil { + return stopAction + failed, getWindowsError(err) + } + defer s.Close() + if err := stopAndWait(s); err != nil { + return stopAction + failed, getWindowsError(err) + } + return stopAction + " completed.", nil } +func stopAndWait(s *mgr.Service) error { + // First stop the service. Then wait for the service to + // actually stop before starting it. + status, err := s.Control(svc.Stop) + if err != nil { + return err + } + + timeDuration := time.Millisecond * 50 + + timeout := time.After(getStopTimeout() + (timeDuration * 2)) + tick := time.NewTicker(timeDuration) + defer tick.Stop() + + for status.State != svc.Stopped { + select { + case <-tick.C: + status, err = s.Query() + if err != nil { + return err + } + case <-timeout: + break + } + } + return nil +} + +func getStopTimeout() time.Duration { + // For default and paths see https://support.microsoft.com/en-us/kb/146092 + defaultTimeout := time.Millisecond * 20000 + key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control`, registry.READ) + if err != nil { + return defaultTimeout + } + sv, _, err := key.GetStringValue("WaitToKillServiceTimeout") + if err != nil { + return defaultTimeout + } + v, err := strconv.Atoi(sv) + if err != nil { + return defaultTimeout + } + return time.Millisecond * time.Duration(v) +} + // Status - Get service status func (windows *windowsRecord) Status() (string, error) { - cmd := exec.Command("sc", "query", windows.name) - out, err := cmd.Output() + m, err := mgr.Connect() if err != nil { return "Getting status:" + failed, getWindowsError(err) } - return "Status: " + "SERVICE_" + getWindowsServiceState(out), nil + defer m.Disconnect() + s, err := m.OpenService(windows.name) + if err != nil { + return "Getting status:" + failed, getWindowsError(err) + } + defer s.Close() + status, err := s.Query() + if err != nil { + return "Getting status:" + failed, getWindowsError(err) + } + + return "Status: " + getWindowsServiceStateFromUint32(status.State), nil } // Get executable path @@ -125,9 +226,88 @@ func getWindowsError(inputError error) error { } // Get windows service state -func getWindowsServiceState(out []byte) string { - regex := regexp.MustCompile("STATE.*: (?P[0-9]) (?P.*) ") - service := regex.FindAllStringSubmatch(string(out), -1)[0] +func getWindowsServiceStateFromUint32(state svc.State) string { + switch state { + case svc.Stopped: + return "SERVICE_STOPPED" + case svc.StartPending: + return "SERVICE_START_PENDING" + case svc.StopPending: + return "SERVICE_STOP_PENDING" + case svc.Running: + return "SERVICE_RUNNING" + case svc.ContinuePending: + return "SERVICE_CONTINUE_PENDING" + case svc.PausePending: + return "SERVICE_PAUSE_PENDING" + case svc.Paused: + return "SERVICE_PAUSED" + } + return "SERVICE_UNKNOWN" +} + +type serviceHandler struct { + executable Executable +} + +func (sh *serviceHandler) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { + const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue + changes <- svc.Status{State: svc.StartPending} + + fasttick := time.Tick(500 * time.Millisecond) + slowtick := time.Tick(2 * time.Second) + tick := fasttick + + sh.executable.Start() + changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} + +loop: + for { + select { + case <-tick: + break + case c := <-r: + switch c.Cmd { + case svc.Interrogate: + changes <- c.CurrentStatus + // Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4 + time.Sleep(100 * time.Millisecond) + changes <- c.CurrentStatus + case svc.Stop, svc.Shutdown: + changes <- svc.Status{State: svc.StopPending} + sh.executable.Stop() + break loop + case svc.Pause: + changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted} + tick = slowtick + case svc.Continue: + changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} + tick = fasttick + default: + continue loop + } + } + } + return +} + +func (windows *windowsRecord) Run(e Executable) (string, error) { + runAction := "Running " + windows.description + ":" + + interactive, err := svc.IsAnInteractiveSession() + if err != nil { + return runAction + failed, getWindowsError(err) + } + if interactive { + e.Run() + } + + err := svc.Run(windows.name, &serviceHandler{ + executable: e, + }) + if err != nil { + return runAction + failed, getWindowsError(err) + } - return service[2] + return runAction + " completed.", nil } From 214104b050a84f8a4118a4a795cfa48e4b7ef6f1 Mon Sep 17 00:00:00 2001 From: Wenzhou Zhang Date: Wed, 28 Mar 2018 15:39:19 +0800 Subject: [PATCH 3/6] add import of sys windows lib --- daemon_windows.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/daemon_windows.go b/daemon_windows.go index cfcde3f..97ba5d0 100644 --- a/daemon_windows.go +++ b/daemon_windows.go @@ -14,6 +14,10 @@ import ( "time" "unicode/utf16" "unsafe" + + "golang.org/x/sys/windows/registry" + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/mgr" ) // windowsRecord - standard record (struct) for windows version of daemon package @@ -302,7 +306,7 @@ func (windows *windowsRecord) Run(e Executable) (string, error) { e.Run() } - err := svc.Run(windows.name, &serviceHandler{ + err = svc.Run(windows.name, &serviceHandler{ executable: e, }) if err != nil { From 7b0f9893e24934bbedef065a1768c33779951e7d Mon Sep 17 00:00:00 2001 From: Wenzhou Zhang Date: Wed, 28 Mar 2018 16:07:42 +0800 Subject: [PATCH 4/6] fix if condition bug and add comments --- daemon_windows.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/daemon_windows.go b/daemon_windows.go index 97ba5d0..1ed5e86 100644 --- a/daemon_windows.go +++ b/daemon_windows.go @@ -302,16 +302,19 @@ func (windows *windowsRecord) Run(e Executable) (string, error) { if err != nil { return runAction + failed, getWindowsError(err) } - if interactive { + if !interactive { + // service called from windows service manager + // use API provided by golang.org/x/sys/windows + err = svc.Run(windows.name, &serviceHandler{ + executable: e, + }) + if err != nil { + return runAction + failed, getWindowsError(err) + } + } else { + // otherwise, service should be called from terminal session e.Run() } - err = svc.Run(windows.name, &serviceHandler{ - executable: e, - }) - if err != nil { - return runAction + failed, getWindowsError(err) - } - return runAction + " completed.", nil } From ed0acd17ccf1f9459b31feecd2cbef945effe208 Mon Sep 17 00:00:00 2001 From: Igor Dolzhikov Date: Tue, 3 Apr 2018 18:36:49 +0700 Subject: [PATCH 5/6] Contributors --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0ddd43a..9c24e9f 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,7 @@ See `examples/cron/cron_job.go` - [Maximus](https://github.com/maximus12793) - [AlgorathDev](https://github.com/AlgorathDev) - [Alexis Camilleri](https://github.com/krysennn) +- [neverland4u](https://github.com/neverland4u) All the contributors are welcome. If you would like to be the contributor please accept some rules. From cc5cf791b6c24894ae577cbbeadb9a7ac703932f Mon Sep 17 00:00:00 2001 From: Igor Dolzhikov Date: Tue, 3 Apr 2018 18:37:30 +0700 Subject: [PATCH 6/6] Bumped version number to 0.11.0 --- daemon.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon.go b/daemon.go index c771358..45f69a4 100644 --- a/daemon.go +++ b/daemon.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. /* -Package daemon 0.10.4 for use with Go (golang) services. +Package daemon 0.11.0 for use with Go (golang) services. Package daemon provides primitives for daemonization of golang services. This package is not provide implementation of user daemon,