diff options
Diffstat (limited to 'sd_daemon/notify_linux.go')
-rw-r--r-- | sd_daemon/notify_linux.go | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/sd_daemon/notify_linux.go b/sd_daemon/notify_linux.go new file mode 100644 index 0000000..903f1ad --- /dev/null +++ b/sd_daemon/notify_linux.go @@ -0,0 +1,114 @@ +// Incorporates: git://github.com/docker/docker.git 18c7c67308bd4a24a41028e63c2603bb74eac85e pkg/systemd/sd_notify.go +// Incorporates: git://github.com/coreos/go-systemd.git a606a1e936df81b70d85448221c7b1c6d8a74ef1 daemon/sdnotify.go +// +// Copyright 2013, 2015 Docker, Inc. +// Copyright 2014 CoreOS, Inc. +// Copyright 2015-2019 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" + + "golang.org/x/sys/unix" +) + +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 { + cmsg := unix.UnixCredentials(&unix.Ucred{ + Pid: int32(msg.PID), + Uid: uint32(os.Getuid()), + Gid: uint32(os.Getgid()), + }) + cmsgs = append(cmsgs, cmsg) + } + + // 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) { + fd, err := unix.Socket(unix.AF_UNIX, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0) + if err != nil { + 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 + } + unixConn := conn.(*net.UnixConn) + return unixConn, nil +} |