// 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 ! 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 }