// Copyright 2013-2015 Docker, Inc. // Copyright 2014 CoreOS, Inc. // 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. package sd_daemon import ( "bytes" "net" "os" "golang.org/x/sys/unix" ) // 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 (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. 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 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). // // If the service manager is not listening for notifications from this // process tree (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") }() } 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(files) > 0 { fds := make([]int, len(files)) for i := range files { fds[i] = int(files[i].Fd()) } cmsg := unix.UnixRights(fds...) cmsgs = append(cmsgs, cmsg) } havePid := pid > 0 && pid != os.Getpid() if havePid { cmsg := unix.UnixCredentials(&unix.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), 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 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), 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, err } conn, err := net.FileConn(os.NewFile(uintptr(fd), name)) if err != nil { return nil, err } unixConn := conn.(*net.UnixConn) return unixConn, nil }