From 3b1bdfbd2687e81bef85260f9cdfbf617ece3527 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sun, 18 Dec 2016 03:20:47 -0500 Subject: Implement almost all of sd-daemon. BREAKING CHANGES. 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. --- sd_daemon/notify.go | 84 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 15 deletions(-) (limited to 'sd_daemon/notify.go') 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 } -- cgit v1.2.3