Ctrl K
ComunidadGuías…

portlist/portlist_linux.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

package portlist

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"syscall"
	"time"
	"unsafe"

	"go4.org/mem"
	"golang.org/x/sys/unix"
	"tailscale.com/util/dirwalk"
	"tailscale.com/util/mak"
)

func init() {
	newOSImpl = newLinuxImpl
	// Refresh service discovery once per second.
	pollInterval = 1 * time.Second
}

type linuxImpl struct {
	procNetFiles    []*os.File // seeked to start & reused between calls
	readlinkPathBuf []byte
	diagBuf         []byte          // reused for every inet_diag receive
	diagPorts       map[string]Port // keyed by "socket:[<decimal inode>]", matching /proc/<pid>/fd symlink targets

	known            map[string]*portMeta // inode string => metadata
	br               *bufio.Reader
	includeLocalhost bool
	diagFD           int // -1 means not yet opened or closed
	diagPID          uint32
	diagSeq          uint32
	namespace        netNamespace
	diagPermanent    bool
}

type portMeta struct {
	port          Port
	pid           int
	keep          bool
	needsProcName bool
}

func newLinuxImplBase(includeLocalhost bool) *linuxImpl {
	return &linuxImpl{
		br:               bufio.NewReader(eofReader),
		known:            map[string]*portMeta{},
		diagPorts:        map[string]Port{},
		includeLocalhost: includeLocalhost,
		diagFD:           -1,
	}
}

func newLinuxImpl(includeLocalhost bool) osImpl {
	return newLinuxImplWithDiag(includeLocalhost, true)
}

// newLinuxImplWithDiag is also used by the Linux benchmarks to construct a
// proc-only implementation without opening an unused diagnostic socket.
func newLinuxImplWithDiag(includeLocalhost, useDiag bool) *linuxImpl {
	// /proc/thread-self and netlink socket creation use the calling thread's
	// network namespace. Stay on one thread from the namespace check through
	// socket creation so both operations use the same namespace.
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	li := newLinuxImplBase(includeLocalhost)
	var procErr error
	li.namespace, procErr = netNamespaceID("/proc/self/ns/net")
	threadNS, threadErr := netNamespaceID("/proc/thread-self/ns/net")
	for _, name := range []string{
		"/proc/net/tcp",
		"/proc/net/tcp6",
		"/proc/net/udp",
		"/proc/net/udp6",
	} {
		f, err := os.Open(name)
		if err != nil {
			if os.IsNotExist(err) {
				continue
			}
			log.Printf("portlist warning; ignoring: %v", err)
			continue
		}
		li.procNetFiles = append(li.procNetFiles, f)
	}
	// /proc/net is resolved through /proc/self, which denotes the process
	// leader. inet_diag is scoped to the calling thread's namespace. If those
	// views differ, use the complete proc fallback rather than mixing them.
	if !useDiag || runtime.GOOS == "android" || (procErr != nil || threadErr != nil || threadNS != li.namespace) {
		li.diagPermanent = true
	} else {
		li.initDiag()
	}
	return li
}

func (li *linuxImpl) initDiag() {
	fd, err := unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW|unix.SOCK_CLOEXEC, unix.NETLINK_INET_DIAG)
	if err != nil {
		li.diagPermanent = isDiagCapabilityError(err)
		return
	}
	if err := unix.Bind(fd, &unix.SockaddrNetlink{Family: unix.AF_NETLINK}); err != nil {
		unix.Close(fd)
		li.diagPermanent = isDiagCapabilityError(err)
		return
	}
	sa, err := unix.Getsockname(fd)
	if err != nil {
		unix.Close(fd)
		li.diagPermanent = isDiagCapabilityError(err)
		return
	}
	li.diagPID = sa.(*unix.SockaddrNetlink).Pid
	li.diagFD = fd
}

// netNamespace identifies the namespace pinned by the proc and netlink files.
type netNamespace struct{ dev, ino uint64 }

func netNamespaceID(path string) (netNamespace, error) {
	var st unix.Stat_t
	if err := unix.Stat(path, &st); err != nil {
		return netNamespace{}, err
	}
	return netNamespace{uint64(st.Dev), uint64(st.Ino)}, nil
}

func (li *linuxImpl) sameNetNamespace() bool {
	ns, err := netNamespaceID("/proc/thread-self/ns/net")
	return err == nil && ns == li.namespace
}

func isDiagCapabilityError(err error) bool {
	// A missing protocol diagnostic handler (for example, CONFIG_INET_UDP_DIAG
	// disabled) returns ENOENT after the kernel attempts to load its module.
	return errors.Is(err, unix.ENOENT) || errors.Is(err, unix.EPERM) || errors.Is(err, unix.EACCES) || errors.Is(err, unix.EAFNOSUPPORT) ||
		errors.Is(err, unix.EPROTONOSUPPORT) || errors.Is(err, unix.ENOPROTOOPT) ||
		errors.Is(err, unix.EINVAL) || errors.Is(err, unix.ENOSYS)
}

func (li *linuxImpl) Close() error {
	li.closeDiag()
	for _, f := range li.procNetFiles {
		f.Close()
	}
	li.procNetFiles = nil
	return nil
}

const (
	v6Localhost = "00000000000000000000000001000000:"
	v6Any       = "00000000000000000000000000000000:0000"
	v4Localhost = "0100007F:"
	v4Any       = "00000000:0000"
)

var eofReader = bytes.NewReader(nil)

func (li *linuxImpl) AppendListeningPorts(base []Port) ([]Port, error) {
	if runtime.GOOS == "android" {
		// Android 10+ doesn't allow access to this anymore.
		// https://developer.android.com/about/versions/10/privacy/changes#proc-net-filesystem
		// Ignore it rather than have the system log about our violation.
		return nil, nil
	}
	br := li.br
	defer br.Reset(eofReader)

	// Start by marking all previous known ports as gone. If this mark
	// bit is still false later, we'll remove them.
	for _, pm := range li.known {
		pm.keep = false
	}

	var err error
	if !li.diagPermanent {
		err = li.appendDiagPorts()
	}
	if err != nil || li.diagPermanent {
		for _, f := range li.procNetFiles {
			name := f.Name()
			_, err := f.Seek(0, io.SeekStart)
			if err != nil {
				return nil, err
			}
			br.Reset(f)
			err = li.parseProcNetFile(br, filepath.Base(name))
			if err != nil {
				return nil, fmt.Errorf("parsing %q: %w", name, err)
			}
		}
	}

	// Delete ports that aren't open any longer.
	// And see if there are any process names we need to look for.
	var needProc map[string]*portMeta
	for inode, pm := range li.known {
		if !pm.keep {
			delete(li.known, inode)
			continue
		}
		if pm.needsProcName {
			mak.Set(&needProc, inode, pm)
		}
	}
	err = li.findProcessNames(needProc)
	if err != nil {
		return nil, err
	}

	ret := base
	for _, pm := range li.known {
		ret = append(ret, pm.port)
	}
	return sortAndDedup(ret), nil
}

// appendDiagPorts obtains the same listener set as parseProcNetFile, but asks
// the kernel to do the expensive table filtering. No state is changed until
// all four dumps have completed successfully; callers can safely fall back to
// proc without leaving a partial result in known.
func (li *linuxImpl) appendDiagPorts() error {
	if li.diagPermanent {
		return errors.New("inet_diag unavailable")
	}
	if li.diagFD < 0 {
		// A replacement socket must be created in the namespace where the
		// proc descriptors were opened. Keep the identity check and socket
		// construction on one OS thread; an existing socket remains pinned to
		// its creation namespace and needs no per-poll thread pinning.
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()
		if !li.sameNetNamespace() {
			return errors.New("inet_diag: network namespace differs from proc view")
		}
		li.initDiag()
		if li.diagFD < 0 {
			return errors.New("inet_diag unavailable")
		}
	}
	type query struct {
		family uint8
		proto  uint8
	}
	queries := []query{{unix.AF_INET, unix.IPPROTO_TCP}, {unix.AF_INET6, unix.IPPROTO_TCP}, {unix.AF_INET, unix.IPPROTO_UDP}, {unix.AF_INET6, unix.IPPROTO_UDP}}
	newPorts := li.diagPorts
	for inode := range newPorts {
		delete(newPorts, inode)
	}
	for _, q := range queries {
		err := li.diagDump(q.family, q.proto, newPorts)
		if err != nil {
			if isDiagCapabilityError(err) {
				li.diagPermanent = true
			}
			li.closeDiag()
			return err
		}
	}
	for inode, pm := range li.known {
		if p, ok := newPorts[inode]; ok {
			pm.keep = true
			if pm.port.Proto != p.Proto || pm.port.Port != p.Port {
				pm.port.Process, pm.port.Pid = "", 0
				pm.needsProcName = true
			}
			pm.port.Proto, pm.port.Port = p.Proto, p.Port
			continue
		}
		delete(li.known, inode)
	}
	for inode, p := range newPorts {
		if _, ok := li.known[inode]; !ok {
			li.known[inode] = &portMeta{port: p, keep: true, needsProcName: true}
		}
	}
	return nil
}

func (li *linuxImpl) closeDiag() {
	if li.diagFD >= 0 {
		_ = unix.Close(li.diagFD)
		li.diagFD = -1
		li.diagPID = 0
	}
}

// tcpListen is Linux's TCP_LISTEN state from include/net/tcp_states.h.
const tcpListen = 10

func diagRequest(family, proto uint8) []byte {
	// Pack these layouts from include/uapi/linux/inet_diag.h. Offsets below
	// are relative to the start of each struct; states uses native byte order.
	//
	// struct inet_diag_sockid {      // 48 bytes
	//     __be16 idiag_sport;        // 0
	//     __be16 idiag_dport;        // 2
	//     __be32 idiag_src[4];       // 4
	//     __be32 idiag_dst[4];       // 20
	//     __u32  idiag_if;           // 36
	//     __u32  idiag_cookie[2];    // 40
	// };
	// struct inet_diag_req_v2 {      // 56 bytes
	//     __u8 sdiag_family;         // 0
	//     __u8 sdiag_protocol;       // 1
	//     __u8 idiag_ext;            // 2
	//     __u8 pad;                  // 3
	//     __u32 idiag_states;        // 4
	//     struct inet_diag_sockid id; // 8
	// };
	b := make([]byte, 56)
	b[0], b[1] = family, proto
	// Query TCP_LISTEN only. On Linux, entries with a zero remote endpoint in
	// /proc/net/tcp are listeners; requesting other TCP states is substantially
	// slower and does not add ports. UDP uses all states and retains the exact
	// zero-remote filtering above.
	if proto == unix.IPPROTO_TCP {
		binary.NativeEndian.PutUint32(b[4:8], 1<<tcpListen)
	} else {
		binary.NativeEndian.PutUint32(b[4:8], ^uint32(0))
	}
	return b
}

const diagTimeout = 250 * time.Millisecond

// diagDump performs one complete multipart SOCK_DIAG_BY_FAMILY transaction.
// The pinned mdlayher/netlink Conn.Receive (also used by Execute) removes
// NLMSG_DONE without checking its NLM_F_DUMP_INTR flag. Reading datagrams
// directly lets us reject interruptions reported only on that final message.
func (li *linuxImpl) diagDump(family, proto uint8, ports map[string]Port) error {
	li.diagSeq++
	if li.diagSeq == 0 {
		li.diagSeq++
	}
	seq := li.diagSeq
	data := diagRequest(family, proto)
	msg := make([]byte, unix.NLMSG_HDRLEN+len(data))
	binary.NativeEndian.PutUint32(msg[0:4], uint32(len(msg)))
	binary.NativeEndian.PutUint16(msg[4:6], unix.SOCK_DIAG_BY_FAMILY)
	binary.NativeEndian.PutUint16(msg[6:8], unix.NLM_F_REQUEST|unix.NLM_F_DUMP)
	binary.NativeEndian.PutUint32(msg[8:12], seq)
	copy(msg[unix.NLMSG_HDRLEN:], data)
	deadline := time.Now().Add(diagTimeout)
	if err := unix.Sendto(li.diagFD, msg, 0, &unix.SockaddrNetlink{Family: unix.AF_NETLINK}); err != nil {
		return err
	}
	if li.diagBuf == nil {
		li.diagBuf = make([]byte, 128<<10)
	}
	const (
		maxMessages = 1 << 20
		maxRecords  = 1 << 20
		maxBytes    = 128 << 20
	)
	messages := 0
	records := 0
	var totalBytes int
	for messages < maxMessages {
		remaining := time.Until(deadline)
		if remaining <= 0 {
			return errors.New("inet_diag: receive deadline exceeded")
		}
		tv := unix.NsecToTimeval(remaining.Nanoseconds())
		if tv.Sec == 0 && tv.Usec == 0 {
			tv.Usec = 1
		}
		if err := unix.SetsockoptTimeval(li.diagFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv); err != nil {
			return err
		}
		n, _, flags, from, err := unix.Recvmsg(li.diagFD, li.diagBuf, nil, 0)
		if err == unix.EINTR {
			continue
		}
		if err != nil {
			return err
		}
		if n > maxBytes-totalBytes {
			return errors.New("inet_diag: response byte limit exceeded")
		}
		totalBytes += n
		if flags&unix.MSG_TRUNC != 0 {
			return errors.New("inet_diag: truncated netlink datagram")
		}
		if sa, ok := from.(*unix.SockaddrNetlink); !ok || sa.Pid != 0 {
			return errors.New("inet_diag: unexpected netlink sender")
		}
		done, err := parseDiagDatagram(li.diagBuf[:n], seq, li.diagPID, family, func(body []byte) error {
			records++
			if records > maxRecords {
				return errors.New("inet_diag: response record limit exceeded")
			}
			// inet_diag_msg has the same address layout as the request. The
			// remote address begins at 24 and inode is the final uint32.
			remote := body[24:40]
			if !allZero(remote) || body[6] != 0 || body[7] != 0 {
				return nil
			}
			local := body[8:24]
			if !li.includeLocalhost && (isDiagV4Localhost(local, family) || isDiagV6Localhost(local, family)) {
				return nil
			}
			port := uint16(body[4])<<8 | uint16(body[5])
			inode := uint64(binary.NativeEndian.Uint32(body[68:72]))
			key := fmt.Sprintf("socket:[%d]", inode)
			ports[key] = Port{Proto: protocolName(proto), Port: port}
			return nil
		})
		if err != nil {
			return err
		}
		messages++
		if done {
			return nil
		}
	}
	return errors.New("inet_diag: response limit exceeded")
}

func protocolName(proto uint8) string {
	if proto == unix.IPPROTO_TCP {
		return "tcp"
	}
	return "udp"
}

// parseDiagDatagram validates one received netlink datagram for an
// inet_diag transaction and invokes onRecord for each diagnostic record.
// It is pure with respect to transport state: callback body slices refer to
// datagram and must be consumed or copied before returning. A false result
// means the multipart transaction has not received NLMSG_DONE yet; the caller
// must continue receiving and reject timeout or EOF without a true result.
func parseDiagDatagram(datagram []byte, seq, pid uint32, family uint8, onRecord func([]byte) error) (done bool, err error) {
	for off := 0; off < len(datagram); {
		if len(datagram)-off < unix.NLMSG_HDRLEN {
			return false, errors.New("inet_diag: truncated netlink header")
		}
		mlen := int(binary.NativeEndian.Uint32(datagram[off : off+4]))
		if mlen < unix.NLMSG_HDRLEN || mlen > len(datagram)-off {
			return false, errors.New("inet_diag: malformed netlink length")
		}
		aligned := (mlen + 3) &^ 3
		if aligned > len(datagram)-off {
			return false, errors.New("inet_diag: missing netlink alignment padding")
		}
		for _, p := range datagram[off+mlen : off+aligned] {
			if p != 0 {
				return false, errors.New("inet_diag: non-zero netlink padding")
			}
		}
		typ := binary.NativeEndian.Uint16(datagram[off+4 : off+6])
		flags := binary.NativeEndian.Uint16(datagram[off+6 : off+8])
		mseq := binary.NativeEndian.Uint32(datagram[off+8 : off+12])
		mpid := binary.NativeEndian.Uint32(datagram[off+12 : off+16])
		if mseq != seq || mpid != pid {
			return false, errors.New("inet_diag: mismatched netlink response")
		}
		if flags&unix.NLM_F_DUMP_INTR != 0 {
			return false, errors.New("inet_diag: dump interrupted")
		}
		body := datagram[off+unix.NLMSG_HDRLEN : off+mlen]
		switch typ {
		case unix.NLMSG_DONE:
			if mlen != unix.NLMSG_HDRLEN && mlen != unix.NLMSG_HDRLEN+4 {
				return false, errors.New("inet_diag: malformed netlink completion")
			}
			if len(body) == 4 && binary.NativeEndian.Uint32(body) != 0 {
				return false, diagNetlinkError(int32(binary.NativeEndian.Uint32(body)))
			}
			if off+aligned != len(datagram) {
				return false, errors.New("inet_diag: data after netlink completion")
			}
			return true, nil
		case unix.NLMSG_ERROR:
			if len(body) < 4 {
				return false, errors.New("inet_diag: truncated netlink error")
			}
			code := int32(binary.NativeEndian.Uint32(body[:4]))
			if code != 0 {
				return false, diagNetlinkError(code)
			}
			return false, errors.New("inet_diag: netlink ACK without completion")
		default:
			if typ != unix.SOCK_DIAG_BY_FAMILY || flags&unix.NLM_F_MULTI == 0 {
				return false, errors.New("inet_diag: unexpected netlink response")
			}
			if len(body) < 72 {
				return false, fmt.Errorf("inet_diag: short response (%d bytes)", len(body))
			}
			if body[0] != family {
				return false, errors.New("inet_diag: response family mismatch")
			}
			if onRecord != nil {
				if err := onRecord(body); err != nil {
					return false, err
				}
			}
		}
		off += aligned
	}
	return false, nil
}

func diagNetlinkError(code int32) error {
	if code > 0 {
		return fmt.Errorf("inet_diag: malformed netlink error code %d", code)
	}
	return fmt.Errorf("inet_diag: netlink error: %w", unix.Errno(-code))
}

func allZero(b []byte) bool {
	for _, x := range b {
		if x != 0 {
			return false
		}
	}
	return true
}
func isDiagV4Localhost(b []byte, family uint8) bool {
	return family == unix.AF_INET && b[0] == 127 && b[1] == 0 && b[2] == 0 && b[3] == 1 && allZero(b[4:])
}
func isDiagV6Localhost(b []byte, family uint8) bool {
	return family == unix.AF_INET6 && allZero(b[:15]) && b[15] == 1
}

// fileBase is one of "tcp", "tcp6", "udp", "udp6".
func (li *linuxImpl) parseProcNetFile(r *bufio.Reader, fileBase string) error {
	proto := strings.TrimSuffix(fileBase, "6")

	// skip header row
	_, err := r.ReadSlice('\n')
	if err != nil {
		return err
	}

	fields := make([]mem.RO, 0, 20) // 17 current fields + some future slop

	wantRemote := mem.S(v4Any)
	if strings.HasSuffix(fileBase, "6") {
		wantRemote = mem.S(v6Any)
	}

	// remoteIndex is the index within a line to the remote address field.
	// -1 means not yet found.
	remoteIndex := -1

	// Add an upper bound on how many rows we'll attempt to read just
	// to make sure this doesn't consume too much of their CPU.
	// TODO(bradfitz,crawshaw): adaptively adjust polling interval as function
	// of open sockets.
	const maxRows = 1e6
	rows := 0

	// Scratch buffer for making inode strings.
	inoBuf := make([]byte, 0, 50)

	for {
		line, err := r.ReadSlice('\n')
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		rows++
		if rows >= maxRows {
			break
		}
		if len(line) == 0 {
			continue
		}

		// On the first row of output, find the index of the 3rd field (index 2),
		// the remote address. All the rows are aligned, at least until 4 billion open
		// TCP connections, per the Linux get_tcp4_sock's "%4d: " on an int i.
		if remoteIndex == -1 {
			remoteIndex = fieldIndex(line, 2)
			if remoteIndex == -1 {
				break
			}
		}

		if len(line) < remoteIndex || !mem.HasPrefix(mem.B(line).SliceFrom(remoteIndex), wantRemote) {
			// Fast path for not being a listener port.
			continue
		}

		// sl local rem ... inode
		fields = mem.AppendFields(fields[:0], mem.B(line))
		local := fields[1]
		rem := fields[2]
		inode := fields[9]

		if !rem.Equal(wantRemote) {
			// not a "listener" port
			continue
		}

		// If a port is bound to localhost, ignore it.
		// TODO: localhost is bigger than 1 IP, we need to ignore
		// more things.
		if !li.includeLocalhost && (mem.HasPrefix(local, mem.S(v4Localhost)) || mem.HasPrefix(local, mem.S(v6Localhost))) {
			continue
		}

		// Don't use strings.Split here, because it causes
		// allocations significant enough to show up in profiles.
		i := mem.IndexByte(local, ':')
		if i == -1 {
			return fmt.Errorf("%q unexpectedly didn't have a colon", local.StringCopy())
		}
		portv, err := mem.ParseUint(local.SliceFrom(i+1), 16, 16)
		if err != nil {
			return fmt.Errorf("%#v: %s", local.SliceFrom(9).StringCopy(), err)
		}
		inoBuf = append(inoBuf[:0], "socket:["...)
		inoBuf = mem.Append(inoBuf, inode)
		inoBuf = append(inoBuf, ']')

		if pm, ok := li.known[string(inoBuf)]; ok {
			pm.keep = true
			// Rest should be unchanged.
		} else {
			li.known[string(inoBuf)] = &portMeta{
				needsProcName: true,
				keep:          true,
				port: Port{
					Proto: proto,
					Port:  uint16(portv),
				},
			}
		}
	}

	return nil
}

// errDone is an internal sentinel error that we found everything we were looking for.
var errDone = errors.New("done")

// need is keyed by inode string.
func (li *linuxImpl) findProcessNames(need map[string]*portMeta) error {
	if len(need) == 0 {
		return nil
	}
	defer func() {
		// Anything we didn't find, give up on and don't try to look for it later.
		for _, pm := range need {
			pm.needsProcName = false
		}
	}()

	err := foreachPID(func(pid mem.RO) error {
		var procBuf [128]byte
		fdPath := mem.Append(procBuf[:0], mem.S("/proc/"))
		fdPath = mem.Append(fdPath, pid)
		fdPath = mem.Append(fdPath, mem.S("/fd"))

		// Android logs a bunch of audit violations in logcat
		// if we try to open things we don't have access
		// to. So on Android only, ask if we have permission
		// rather than just trying it to determine whether we
		// have permission.
		if runtime.GOOS == "android" && syscall.Access(string(fdPath), unix.R_OK) != nil {
			return nil
		}

		dirwalk.WalkShallow(mem.B(fdPath), func(fd mem.RO, de fs.DirEntry) error {
			targetBuf := make([]byte, 64) // plenty big for "socket:[165614651]"

			linkPath := li.readlinkPathBuf[:0]
			linkPath = fmt.Appendf(linkPath, "/proc/")
			linkPath = mem.Append(linkPath, pid)
			linkPath = append(linkPath, "/fd/"...)
			linkPath = mem.Append(linkPath, fd)
			linkPath = append(linkPath, 0) // terminating NUL
			li.readlinkPathBuf = linkPath  // to reuse its buffer next time
			n, ok := readlink(linkPath, targetBuf)
			if !ok {
				// Not a symlink or no permission.
				// Skip it.
				return nil
			}

			pe := need[string(targetBuf[:n])] // m[string([]byte)] avoids alloc
			if pe == nil {
				return nil
			}
			bs, err := os.ReadFile(fmt.Sprintf("/proc/%s/cmdline", pid.StringCopy()))
			if err != nil {
				// Usually shouldn't happen. One possibility is
				// the process has gone away, so let's skip it.
				return nil
			}

			argv := strings.Split(strings.TrimSuffix(string(bs), "\x00"), "\x00")
			if p, err := mem.ParseInt(pid, 10, 0); err == nil {
				pe.pid = int(p)
			}
			pe.port.Process = argvSubject(argv...)
			pid64, _ := mem.ParseInt(pid, 10, 0)
			pe.port.Pid = int(pid64)
			pe.needsProcName = false
			delete(need, string(targetBuf[:n]))
			if len(need) == 0 {
				return errDone
			}
			return nil
		})
		return nil
	})
	if err == errDone {
		return nil
	}
	return err
}

func foreachPID(fn func(pidStr mem.RO) error) error {
	err := dirwalk.WalkShallow(mem.S("/proc"), func(name mem.RO, de fs.DirEntry) error {
		if !isNumeric(name) {
			return nil
		}
		return fn(name)
	})
	if os.IsNotExist(err) {
		// This can happen if the directory we're
		// reading disappears during the run. No big
		// deal.
		return nil
	}
	return err
}

func isNumeric(s mem.RO) bool {
	for i, n := 0, s.Len(); i < n; i++ {
		b := s.At(i)
		if b < '0' || b > '9' {
			return false
		}
	}
	return s.Len() > 0
}

// fieldIndex returns the offset in line where the Nth field (0-based) begins, or -1
// if there aren't that many fields. Fields are separated by 1 or more spaces.
func fieldIndex(line []byte, n int) int {
	skip := 0
	for i := 0; i <= n; i++ {
		// Skip spaces.
		for skip < len(line) && line[skip] == ' ' {
			skip++
		}
		if skip == len(line) {
			return -1
		}
		if i == n {
			break
		}
		// Skip non-space.
		for skip < len(line) && line[skip] != ' ' {
			skip++
		}
	}
	return skip
}

// path must be null terminated.
func readlink(path, buf []byte) (n int, ok bool) {
	if len(buf) == 0 || len(path) < 2 || path[len(path)-1] != 0 {
		return 0, false
	}
	var dirfd int = unix.AT_FDCWD
	r0, _, e1 := unix.Syscall6(unix.SYS_READLINKAT,
		uintptr(dirfd),
		uintptr(unsafe.Pointer(&path[0])),
		uintptr(unsafe.Pointer(&buf[0])),
		uintptr(len(buf)),
		0, 0)
	n = int(r0)
	if e1 != 0 {
		return 0, false
	}
	return n, true
}