package sdnotify import ( "errors" "fmt" "net" "os" "strconv" "strings" "golang.org/x/sys/unix" ) const ( ReadyEnabled = "READY=1" StoppingEnabled = "STOPPING=1" ReloadingEnabled = "RELOADING=1" ) var ( socket *net.UnixAddr errSocketVariableIsNotPresent = errors.New("\"NOTIFY_SOCKET\" environment variable is not present") errSocketIsNotInitialized = errors.New("socket is not initialized") ) // InitSocket initializes socket with provided name of // environment variable. func InitSocket() error { notifySocket := os.Getenv("NOTIFY_SOCKET") if notifySocket == "" { return errSocketVariableIsNotPresent } socket = &net.UnixAddr{ Name: notifySocket, Net: "unixgram", } return nil } // FlagAndStatus sends systemd a combination of a // well-known status and STATUS=%s{status}, separated by newline. func FlagAndStatus(status string) error { if status == ReloadingEnabled { // From https://www.man7.org/linux/man-pages/man5/systemd.service.5.html // // When initiating the reload process the service is // expected to reply with a notification message via // sd_notify(3) that contains the "RELOADING=1" field in // combination with "MONOTONIC_USEC=" set to the current // monotonic time (i.e. CLOCK_MONOTONIC in // clock_gettime(2)) in μs, formatted as decimal string. // Once reloading is complete another notification message // must be sent, containing "READY=1". // // For MONOTONIC_USEC format refer to https://www.man7.org/linux/man-pages/man3/sd_notify.3.html var ts unix.Timespec if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &ts); err != nil { return fmt.Errorf("clock_gettime: %w", err) } status += "\nMONOTONIC_USEC=" + strconv.FormatInt(ts.Nano()/1000, 10) status += "\nSTATUS=RELOADING" return Send(status) } status += "\nSTATUS=" + strings.TrimSuffix(status, "=1") return Send(status) } // Status sends systemd notify STATUS=%s{status}. func Status(status string) error { return Send("STATUS=" + status) } // ClearStatus resets the current service status previously set by Status. func ClearStatus() error { return Status("") } // Send state through the notify socket if any. // If the notify socket was not detected, it returns an error. func Send(state string) error { if socket == nil { return errSocketIsNotInitialized } conn, err := net.DialUnix(socket.Net, nil, socket) if err != nil { return fmt.Errorf("can't open unix socket: %v", err) } defer conn.Close() if _, err = conn.Write([]byte(state)); err != nil { return fmt.Errorf("can't write into the unix socket: %v", err) } return nil }