summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@sbcglobal.net>2016-12-18 03:20:47 -0500
committerLuke Shumaker <lukeshu@sbcglobal.net>2016-12-18 03:20:47 -0500
commit3b1bdfbd2687e81bef85260f9cdfbf617ece3527 (patch)
tree93f2c45e6e616ab63e5fb21f4d2a740d8b9b05e2
parenteb6e8a6ca87879a6ca85788fcf6d3bf8848088e6 (diff)
Implement almost all of sd-daemon. BREAKING CHANGES.v0.2.0
This does not include the sd_is_* utility functions. BREAKING CHANGES: - The import name is now "sd_daemon" instead of "sd". - The logger interface is now entirely different. - Notify now takes more arguments.
-rw-r--r--sd_daemon/.gitignore1
-rw-r--r--sd_daemon/Makefile25
-rw-r--r--sd_daemon/booted.go28
-rw-r--r--sd_daemon/doc.go26
-rw-r--r--sd_daemon/listen_fds.go10
-rw-r--r--sd_daemon/log.go87
-rwxr-xr-xsd_daemon/log_util.go.gen37
-rw-r--r--sd_daemon/logger/logger.go62
-rw-r--r--sd_daemon/lsb/exit-status.go7
-rw-r--r--sd_daemon/notify.go84
-rw-r--r--sd_daemon/watchdog.go68
11 files changed, 345 insertions, 90 deletions
diff --git a/sd_daemon/.gitignore b/sd_daemon/.gitignore
new file mode 100644
index 0000000..1687796
--- /dev/null
+++ b/sd_daemon/.gitignore
@@ -0,0 +1 @@
+/log_util.go
diff --git a/sd_daemon/Makefile b/sd_daemon/Makefile
new file mode 100644
index 0000000..ec66e67
--- /dev/null
+++ b/sd_daemon/Makefile
@@ -0,0 +1,25 @@
+# Copyright (C) 2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+files.src.gen = log_util.go
+
+files.generate: $(files.src.gen)
+maintainer-clean:
+ rm -f -- $(files.src.gen)
+.PHONY: files.generate maintainer-clean
+
+%.go: %.go.gen
+ ./$^ > $@
+
+.DELETE_ON_ERROR:
diff --git a/sd_daemon/booted.go b/sd_daemon/booted.go
new file mode 100644
index 0000000..bfbc685
--- /dev/null
+++ b/sd_daemon/booted.go
@@ -0,0 +1,28 @@
+// Copyright 2015 CoreOS, Inc.
+// Copyright 2016 Luke Shumaker
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package sd_daemon
+
+import "os"
+
+// Returns whether the operating system booted using the Systemd init
+// system.
+//
+// Please do not use this function. All of the other functionality in
+// this package uses interfaces that are not Systemd-specific.
+func SdBooted() bool {
+ fi, err := os.Lstat("/run/systemd/system")
+ return err != nil && fi.IsDir()
+}
diff --git a/sd_daemon/doc.go b/sd_daemon/doc.go
index 665e25e..8f1fb00 100644
--- a/sd_daemon/doc.go
+++ b/sd_daemon/doc.go
@@ -1,4 +1,4 @@
-// Copyright 2015 Luke Shumaker
+// Copyright 2015-2016 Luke Shumaker
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,5 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Package sd provides APIs for systemd new-style daemons.
-package sd
+// Package sd_daemon provides functions for writing "new-style"
+// daemons.
+//
+// The daemon(7) manual page has historically documented the very long
+// list of things that a daemon must do at start-up to be a
+// well-behaved SysV daemon. Modern service managers allow daemons to
+// be much simpler; modern versions of the daemon(7) page on GNU/Linux
+// systems also describe "new-style" daemons. Though many of the
+// mechanisms described there and implemented here originated with
+// Systemd, they are all very simple mechanisms which can easily be
+// implemented with a variety of service managers.
+//
+// [daemon(7)]: https://www.freedesktop.org/software/systemd/man/daemon.html
+package sd_daemon
+
+import "errors"
+
+// ErrDisabled is the error returned when the service manager does not
+// want/support a mechanism; or when that mechanism has been disabled
+// for this process by setting unsetEnv=true when calling one of these
+// functions.
+var ErrDisabled = errors.New("Mechanism Disabled")
diff --git a/sd_daemon/listen_fds.go b/sd_daemon/listen_fds.go
index 434f7cc..f512384 100644
--- a/sd_daemon/listen_fds.go
+++ b/sd_daemon/listen_fds.go
@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package sd
+package sd_daemon
import (
"os"
@@ -22,14 +22,10 @@ import (
"syscall"
)
-///*#include <systemd/sd-daemon.h>*/
-//#define SD_LISTEN_FDS_START 3
-import "C"
-
// ListenFds returns a list of file descriptors passed in by the
// service manager as part of the socket-based activation logic.
//
-// If unsetEnv is true, then (regarless of whether the function call
+// If unsetEnv is true, then (regardless of whether the function call
// itself succeeds or not) it will unset the environmental variables
// LISTEN_FDS and LISTEN_PID, which will cause further calls to this
// function to fail.
@@ -58,7 +54,7 @@ func ListenFds(unsetEnv bool) []*os.File {
files := make([]*os.File, 0, nfds)
for i := 0; i < nfds; i++ {
- fd := i+C.SD_LISTEN_FDS_START
+ fd := i+3
syscall.CloseOnExec(fd)
name := "unknown"
if i < len(names) {
diff --git a/sd_daemon/log.go b/sd_daemon/log.go
new file mode 100644
index 0000000..76a735f
--- /dev/null
+++ b/sd_daemon/log.go
@@ -0,0 +1,87 @@
+// Copyright 2015-2016 Luke Shumaker
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:generate make
+
+package sd_daemon
+
+import (
+ "io"
+ "os"
+ "strings"
+ "sync"
+ "fmt"
+ "log/syslog"
+)
+
+// Logger writes "<N>"-prefixed lines to an io.Writer, where N is a
+// syslog priority number. It implements mostly the same interface as
+// "log/syslog".Writer.
+//
+// You probably don't need any instance of this other than the
+// constant "Log", which uses os.Stderr as the writer. It is
+// implemented as a struct rather than a set of functions so that it
+// can be passed around as an implementation of an interface.
+type Logger struct{
+ mu sync.Mutex
+ out io.Writer
+}
+
+func NewLogger(w io.Writer) Logger {
+ return Logger{out: w}
+}
+
+// Log is a Logger that use used very similarly to
+// "log/syslog".Writer, but writes to os.Stderr under the assumption
+// that stderr is forwarded to syslog/journald.
+//
+// You are encouraged to use stderr unless you have a good reason to
+// talk to syslog or journald directly.
+var Log = Logger{out: os.Stderr}
+
+// WriteString writes a message with the specified priority to the
+// log.
+func (l Logger) WriteBytes(level syslog.Priority, msg []byte) (n int, err error) {
+ return l.WriteString(level, string(msg))
+}
+
+// WriteString writes a message with the specified priority to the
+// log.
+func (l Logger) WriteString(level syslog.Priority, msg string) (n int, err error) {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+
+ // BUG(lukeshu): Logger uses high-level string functions that
+ // do many small allocations, making it insuitable for in
+ // tight loops; it should use a buffer property to be
+ // essentially zero-allocation.
+ prefix := fmt.Sprintf("<%d>", level)
+ buf := prefix + strings.Replace(strings.TrimSuffix(msg, "\n"), "\n", "\n"+prefix, -1)
+ return io.WriteString(l.out, buf)
+}
+
+type loggerWriter struct {
+ log Logger
+ level syslog.Priority
+}
+
+func (lw loggerWriter) Write(p []byte) (n int, err error) {
+ return lw.log.WriteBytes(lw.level, p)
+}
+
+// Writer returns an io.Writer that writes messages with the specified
+// priority to the log.
+func (l Logger) Writer(level syslog.Priority) io.Writer {
+ return loggerWriter{log: l, level: level}
+}
diff --git a/sd_daemon/log_util.go.gen b/sd_daemon/log_util.go.gen
new file mode 100755
index 0000000..f7f43c0
--- /dev/null
+++ b/sd_daemon/log_util.go.gen
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+# Copyright (C) 2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+ printf '//'
+ printf ' %q' "$0" "$@"
+ printf '\n// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT\n\n'
+ cat <<EOF
+package sd_daemon
+
+import "log/syslog"
+
+EOF
+
+ for level in Emerg Alert Crit Err Warning Notice Info Debug; do
+ cat <<EOF
+// $level writes a message with priority syslog.LOG_${level^^} to the log.
+func (l Logger) $level(msg string) error {
+ _, err := l.WriteString(syslog.LOG_${level^^}, msg)
+ return err
+}
+
+EOF
+ done
+} | gofmt
diff --git a/sd_daemon/logger/logger.go b/sd_daemon/logger/logger.go
deleted file mode 100644
index 005b193..0000000
--- a/sd_daemon/logger/logger.go
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2015 Luke Shumaker
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package logger implements a simple stderr-based logger with systemd
-// log levels.
-package logger
-
-import (
- "fmt"
- "os"
-)
-
-///*#include <systemd/sd-daemon.h>*/
-//#define SD_EMERG "<0>"
-//#define SD_ALERT "<1>"
-//#define SD_CRIT "<2>"
-//#define SD_ERR "<3>"
-//#define SD_WARNING "<4>"
-//#define SD_NOTICE "<5>"
-//#define SD_INFO "<6>"
-//#define SD_DEBUG "<7>"
-import "C"
-
-func log(level string, format string, a ...interface{}) {
- f := level + format + "\n"
- fmt.Fprintf(os.Stderr, f, a...)
-}
-
-// system is unusable
-func Emerg( /* */ format string, a ...interface{}) { log(C.SD_EMERG /* */, format, a...) }
-
-// action must be taken immediately
-func Alert( /* */ format string, a ...interface{}) { log(C.SD_ALERT /* */, format, a...) }
-
-// critical conditions
-func Crit( /* */ format string, a ...interface{}) { log(C.SD_CRIT /* */, format, a...) }
-
-// error conditions
-func Err( /* */ format string, a ...interface{}) { log(C.SD_ERR /* */, format, a...) }
-
-// warning conditions
-func Warning( /**/ format string, a ...interface{}) { log(C.SD_WARNING /**/, format, a...) }
-
-// normal but significant condition
-func Notice( /* */ format string, a ...interface{}) { log(C.SD_NOTICE /* */, format, a...) }
-
-// informational
-func Info( /* */ format string, a ...interface{}) { log(C.SD_INFO /* */, format, a...) }
-
-// debug-level messages
-func Debug( /* */ format string, a ...interface{}) { log(C.SD_DEBUG /* */, format, a...) }
diff --git a/sd_daemon/lsb/exit-status.go b/sd_daemon/lsb/exit-status.go
index 14e2128..fbbb5e3 100644
--- a/sd_daemon/lsb/exit-status.go
+++ b/sd_daemon/lsb/exit-status.go
@@ -1,4 +1,4 @@
-// Copyright 2015 Luke Shumaker
+// Copyright 2015-2016 Luke Shumaker
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -17,9 +17,10 @@
package lsb
import (
+ "fmt"
"os"
- "lukeshu.com/git/go/libsystemd.git/sd_daemon/logger"
+ "lukeshu.com/git/go/libsystemd.git/sd_daemon"
)
// systemd daemon(7) recommends using the exit codes defined in the
@@ -87,7 +88,7 @@ const (
// panic.
func Recover() {
if r := recover(); r != nil {
- logger.Err("panic: %v", r)
+ sd_daemon.Log.Err(fmt.Sprintf("panic: %v", r))
os.Exit(int(EXIT_FAILURE))
}
}
diff --git a/sd_daemon/notify.go b/sd_daemon/notify.go
index b159a22..ad63659 100644
--- a/sd_daemon/notify.go
+++ b/sd_daemon/notify.go
@@ -1,6 +1,6 @@
// Copyright 2013-2015 Docker, Inc.
// Copyright 2014 CoreOS, Inc.
-// Copyright 2015 Luke Shumaker
+// Copyright 2015-2016 Luke Shumaker
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,31 +14,47 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package sd
+package sd_daemon
import (
- "errors"
"net"
"os"
+ "syscall"
+ "bytes"
)
-// errNotifyNoSocket is an error returned if no socket was specified.
-var errNotifyNoSocket = errors.New("No socket")
-
-// Notify sends a message to the service manager aobout state
-// changes. It is common to ignore the error.
+// Notify sends a message from process pid to the service manager
+// about state changes. If pid <= 0, or if the current process does
+// not have priveleges to send messages on behalf of other processes,
+// then the message is simply sent from the current process.
//
-// If unsetEnv is true, then (regarless of whether the function call
+// If unsetEnv is true, then (regardless of whether the function call
// itself succeeds or not) it will unset the environmental variable
// NOTIFY_SOCKET, which will cause further calls to this function to
// fail.
//
// The state parameter should countain a newline-separated list of
-// variable assignments.
+// variable assignments. See the documentation for sd_notify(3) for
+// well-known variable assignments.
+// https://www.freedesktop.org/software/systemd/man/sd_notify.html
+//
+// It is possible to include a set of file descriptors with the
+// message. This is useful for keeping files open across restarts, as
+// it enables the service manager will pass those files to the new
+// process when it is restarted (see ListenFds). Note: The service
+// manager will only actually store the file descriptors if you
+// include "FDSTORE=1" in the state.
//
-// See the documentation for sd_notify(3) for well-known variable
-// assignments.
-func Notify(unsetEnv bool, state string) error {
+// If the service manager is not listening for notifications from this
+// process (or this has already been called with unsetEnv=true), then
+// ErrDisabled is returned. If the service manager appears to be
+// listening, but there is an error sending the message, then that
+// error is returned. It is generally recommended that you ignore the
+// return value: if there is an error, this is function no-op; meaning
+// that by calling the function but ignoring the return value, you can
+// easily support both service managers that support these
+// notifications and those that do not.
+func Notify(pid int, unsetEnv bool, state string, files []*os.File) error {
if unsetEnv {
defer func() { _ = os.Unsetenv("NOTIFY_SOCKET") }()
}
@@ -49,7 +65,7 @@ func Notify(unsetEnv bool, state string) error {
}
if socketAddr.Name == "" {
- return errNotifyNoSocket
+ return ErrDisabled
}
conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr)
@@ -58,6 +74,44 @@ func Notify(unsetEnv bool, state string) error {
}
defer func() { _ = conn.Close() }()
- _, err = conn.Write([]byte(state))
+ var cmsgs [][]byte
+
+ if len(files) > 0 {
+ fds := make([]int, len(files))
+ for i := range files {
+ fds[i] = int(files[i].Fd())
+ }
+ cmsg := syscall.UnixRights(fds...)
+ cmsgs = append(cmsgs, cmsg)
+ }
+
+ havePid := pid > 0 && pid != os.Getpid()
+ if havePid {
+ // The types of members of syscall.Ucred aren't
+ // guaranteed across processors. However,
+ // fortunately, they are the same on all supported
+ // processors as of go 1.7.4.
+ cmsg := syscall.UnixCredentials(&syscall.Ucred{
+ Pid: int32(pid),
+ Uid: uint32(os.Getuid()),
+ Gid: uint32(os.Getgid()),
+ })
+ cmsgs = append(cmsgs, cmsg)
+ }
+
+ // If the 2nd argument is empty, this is equivalent to .Write([]byte(state))
+ _, _, err = conn.WriteMsgUnix([]byte(state), bytes.Join(cmsgs, nil), nil)
+
+ if err != nil && havePid {
+ // Maybe it failed because we don't have privileges to
+ // spoof our pid, retry without spoofing the pid.
+ //
+ // I'm not too happy that does this silently without
+ // notifying the user, but that's what
+ // sd_pid_notify_with_fds does.
+ cmsgs = cmsgs[:len(cmsgs)-1]
+ _, _, err = conn.WriteMsgUnix([]byte(state), bytes.Join(cmsgs, nil), nil)
+ }
+
return err
}
diff --git a/sd_daemon/watchdog.go b/sd_daemon/watchdog.go
new file mode 100644
index 0000000..b32f470
--- /dev/null
+++ b/sd_daemon/watchdog.go
@@ -0,0 +1,68 @@
+// Copyright 2016 CoreOS, Inc.
+// Copyright 2016 Luke Shumaker
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package sd_daemon
+
+import (
+ "os"
+ "strconv"
+ "time"
+)
+
+// WatchdogEnabled returns how often the process is expected to send a
+// keep-alive notification to the service manager.
+//
+// Notify(0, false, "WATCHDOG=1", nil) // send keep-alive notification
+//
+// If unsetEnv is true, then (regardless of whether the function call
+// itself succeeds or not) it will unset the environmental variables
+// WATCHDOG_USEC and WATCHDOG_PID, which will cause further calls to
+// this function to fail.
+//
+// If an error is returned, then the duration is 0. If the service
+// manager is not expecting a keep-alive notification from this
+// process (or if this has already been called with unsetEnv=true),
+// then ErrDisabled is returned. If there is an error parsing the
+// service manager's watchdog request, then an appropriate other error
+// is returned.
+func WatchdogEnabled(unsetEnv bool) (time.Duration, error) {
+ if unsetEnv {
+ defer func() {
+ _ = os.Unsetenv("WATCHDOG_USEC")
+ _ = os.Unsetenv("WATCHDOG_PID")
+ }()
+ }
+
+ usecStr, haveUsec := os.LookupEnv("WATCHDOG_USEC")
+ if !haveUsec {
+ return 0, ErrDisabled
+ }
+ usec, err := strconv.ParseInt(usecStr, 10, 64)
+ if err != nil || usec < 0 {
+ return 0, err
+ }
+
+ if pidStr, havePid := os.LookupEnv("WATCHDOG_PID"); havePid {
+ pid, err := strconv.Atoi(pidStr)
+ if err != nil {
+ return 0, err
+ }
+ if pid != os.Getpid() {
+ return 0, ErrDisabled
+ }
+ }
+
+ return time.Duration(usec) * time.Microsecond, nil
+}