git » systemd » v1 » tree

[v1] / systemd.go

// Package systemd implements a way to get network listeners from systemd,
// similar to C's sd_listen_fds(3) and sd_listen_fds_with_names(3).
package systemd // import "blitiri.com.ar/go/systemd"

import (
	"errors"
	"fmt"
	"net"
	"os"
	"strconv"
	"strings"
	"syscall"
)

var (
	// Error to return when $LISTEN_PID does not refer to us.
	ErrPIDMismatch = errors.New("$LISTEN_PID != our PID")

	// First FD for listeners.
	// It's 3 by definition, but using a variable simplifies testing.
	firstFD = 3
)

// Listeners creates a slice net.Listener from the file descriptors passed
// by systemd, via the LISTEN_FDS environment variable.
// See sd_listen_fds(3) and sd_listen_fds_with_names(3) for more details.
func Listeners() (map[string][]net.Listener, error) {
	pidStr := os.Getenv("LISTEN_PID")
	nfdsStr := os.Getenv("LISTEN_FDS")
	fdNamesStr := os.Getenv("LISTEN_FDNAMES")
	fdNames := strings.Split(fdNamesStr, ":")

	// Nothing to do if the variables are not set.
	if pidStr == "" || nfdsStr == "" {
		return nil, nil
	}

	pid, err := strconv.Atoi(pidStr)
	if err != nil {
		return nil, fmt.Errorf(
			"error converting $LISTEN_PID=%q: %v", pidStr, err)
	} else if pid != os.Getpid() {
		return nil, ErrPIDMismatch
	}

	nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS"))
	if err != nil {
		return nil, fmt.Errorf(
			"error reading $LISTEN_FDS=%q: %v", nfdsStr, err)
	}

	// We should have as many names as we have descriptors.
	// Note that if we have no descriptors, fdNames will be [""] (due to how
	// strings.Split works), so we consider that special case.
	if nfds > 0 && (fdNamesStr == "" || len(fdNames) != nfds) {
		return nil, fmt.Errorf(
			"Incorrect LISTEN_FDNAMES, have you set FileDescriptorName?")
	}

	listeners := map[string][]net.Listener{}

	for i := 0; i < nfds; i++ {
		fd := firstFD + i
		// We don't want childs to inherit these file descriptors.
		syscall.CloseOnExec(fd)

		name := fdNames[i]

		sysName := fmt.Sprintf("[systemd-fd-%d-%v]", fd, name)
		lis, err := net.FileListener(os.NewFile(uintptr(fd), sysName))
		if err != nil {
			return nil, fmt.Errorf(
				"Error making listener out of fd %d: %v", fd, err)
		}

		listeners[name] = append(listeners[name], lis)
	}

	// Remove them from the environment, to prevent accidental reuse (by
	// us or children processes).
	os.Unsetenv("LISTEN_PID")
	os.Unsetenv("LISTEN_FDS")
	os.Unsetenv("LISTEN_FDNAMES")

	return listeners, nil
}