summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@datawire.io>2018-12-19 07:56:36 -0800
committerLuke Shumaker <lukeshu@datawire.io>2018-12-19 11:00:21 -0500
commit17934614d3030eeebda44b88ce3061d26199a438 (patch)
treef0d50ca071485be0f1f09e9317573a2b8efd4abe
parent1e84510c24b467de60f3938a365b5085457c50a3 (diff)
sd_daemon: Fix build on macOS, fix FD leak on Linux
-rw-r--r--sd_daemon/notify.go5
-rw-r--r--sd_daemon/notify_nonlinux.go164
2 files changed, 168 insertions, 1 deletions
diff --git a/sd_daemon/notify.go b/sd_daemon/notify.go
index 62e507f..b0a0d72 100644
--- a/sd_daemon/notify.go
+++ b/sd_daemon/notify.go
@@ -14,6 +14,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+// +build linux
+
package sd_daemon
import (
@@ -142,8 +144,9 @@ func (msg Notification) Send(unsetEnv bool) error {
func socketUnixgram(name string) (*net.UnixConn, error) {
fd, err := unix.Socket(unix.AF_UNIX, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0)
if err != nil {
- return nil, err
+ return nil, os.NewSyscallError("socket", err)
}
+ defer unix.Close(fd)
conn, err := net.FileConn(os.NewFile(uintptr(fd), name))
if err != nil {
return nil, err
diff --git a/sd_daemon/notify_nonlinux.go b/sd_daemon/notify_nonlinux.go
new file mode 100644
index 0000000..5acc5d7
--- /dev/null
+++ b/sd_daemon/notify_nonlinux.go
@@ -0,0 +1,164 @@
+// Copyright 2013-2015 Docker, Inc.
+// Copyright 2014 CoreOS, Inc.
+// Copyright 2015-2018 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.
+
+// +build !linux
+
+package sd_daemon
+
+import (
+ "bytes"
+ "net"
+ "os"
+ "syscall"
+
+ "golang.org/x/sys/unix"
+)
+
+// Notification is a message to be sent to the service manager about
+// state changes.
+type Notification struct {
+ // PID specifies which process to send a notification about.
+ // If PID <= 0, or if the current process does not have
+ // privileges to send messages on behalf of other processes,
+ // then the message is simply sent from the current process.
+ PID int
+
+ // State should contain a newline-separated list of variable
+ // assignments. See the documentation for sd_notify(3) for
+ // well-known variable assignments.
+ //
+ // https://www.freedesktop.org/software/systemd/man/sd_notify.html
+ State string
+
+ // Files is a list of file descriptors to send to the service
+ // manager with the message. This is useful for keeping files
+ // open across restarts, as it enables the service manager to
+ // 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 (again,
+ // see sd_notify(3) for well-known variable assignments).
+ Files []*os.File
+}
+
+// Send sends the Notification to the service manager.
+//
+// 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 notify operations to fail.
+//
+// If the service manager is not listening for notifications from this
+// process tree (or a Notification has has already been send 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, then 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 (msg Notification) Send(unsetEnv bool) error {
+ if unsetEnv {
+ defer func() { _ = os.Unsetenv("NOTIFY_SOCKET") }()
+ }
+
+ socketAddr := &net.UnixAddr{
+ Name: os.Getenv("NOTIFY_SOCKET"),
+ Net: "unixgram",
+ }
+
+ if socketAddr.Name == "" {
+ return ErrDisabled
+ }
+
+ conn, err := socketUnixgram(socketAddr.Name)
+ if err != nil {
+ return err
+ }
+ defer func() { _ = conn.Close() }()
+
+ var cmsgs [][]byte
+
+ if len(msg.Files) > 0 {
+ fds := make([]int, len(msg.Files))
+ for i := range msg.Files {
+ fds[i] = int(msg.Files[i].Fd())
+ }
+ cmsg := unix.UnixRights(fds...)
+ cmsgs = append(cmsgs, cmsg)
+ }
+
+ havePid := msg.PID > 0 && msg.PID != os.Getpid()
+ if havePid {
+ // BUG(lukeshu): Spoofing the socket credentials is
+ // not implemnted on non-Linux kernels. If you are
+ // knowledgable about how to do this on other kernels,
+ // please let me know at lukeshu@lukeshu.com!
+ havePid = false
+ }
+
+ // If the 2nd argument is empty, this is equivalent to
+ //
+ // conn, _ := net.DialUnix(socketAddr.Net, nil, socketAddr)
+ // conn.Write([]byte(msg.State))
+ _, _, err = conn.WriteMsgUnix([]byte(msg.State), bytes.Join(cmsgs, nil), socketAddr)
+
+ 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 we do this silently without
+ // notifying the caller, but that's what
+ // sd_pid_notify_with_fds does.
+ cmsgs = cmsgs[:len(cmsgs)-1]
+ _, _, err = conn.WriteMsgUnix([]byte(msg.State), bytes.Join(cmsgs, nil), socketAddr)
+ }
+
+ return err
+}
+
+// socketUnixgram wraps socket(2), but doesn't bind(2) or connect(2)
+// the socket to anything. This is an ugly hack because none of the
+// functions in "net" actually allow you to get a AF_UNIX socket not
+// bound/connected to anything.
+//
+// At some point you begin to question if it is worth it to keep up
+// the high-level interface of "net", and messing around with FileConn
+// and UnixConn. Maybe we just drop to using unix.Socket and
+// unix.SendmsgN directly.
+func socketUnixgram(name string) (*net.UnixConn, error) {
+ syscall.ForkLock.RLock()
+ fd, err := unix.Socket(unix.AF_UNIX, unix.SOCK_DGRAM, 0)
+ if err == nil {
+ syscall.CloseOnExec(fd)
+ }
+ syscall.ForkLock.RUnlock()
+ if err != nil {
+ return nil, os.NewSyscallError("socket", err)
+ }
+ defer unix.Close(fd)
+ if err = unix.SetNonblock(fd, true); err != nil {
+ return nil, os.NewSyscallError("setnonblock", err)
+ }
+ conn, err := net.FileConn(os.NewFile(uintptr(fd), name))
+ if err != nil {
+ return nil, err
+ }
+ unixConn := conn.(*net.UnixConn)
+ return unixConn, nil
+}