From 17934614d3030eeebda44b88ce3061d26199a438 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Wed, 19 Dec 2018 07:56:36 -0800 Subject: sd_daemon: Fix build on macOS, fix FD leak on Linux --- sd_daemon/notify.go | 5 +- sd_daemon/notify_nonlinux.go | 164 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 sd_daemon/notify_nonlinux.go 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 +} -- cgit v1.2.3-54-g00ecf