mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 11:05:45 +00:00
types/result, util/lineiter: add package for a result type, use it
This adds a new generic result type (motivated by golang/go#70084) to try it out, and uses it in the new lineutil package (replacing the old lineread package), changing that package to return iterators: sometimes over []byte (when the input is all in memory), but sometimes iterators over results of []byte, if errors might happen at runtime. Updates #12912 Updates golang/go#70084 Change-Id: Iacdc1070e661b5fb163907b1e8b07ac7d51d3f83 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
809a6eba80
commit
01185e436f
@ -140,6 +140,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
tailscale.com/types/persist from tailscale.com/ipn
|
tailscale.com/types/persist from tailscale.com/ipn
|
||||||
tailscale.com/types/preftype from tailscale.com/ipn
|
tailscale.com/types/preftype from tailscale.com/ipn
|
||||||
tailscale.com/types/ptr from tailscale.com/hostinfo+
|
tailscale.com/types/ptr from tailscale.com/hostinfo+
|
||||||
|
tailscale.com/types/result from tailscale.com/util/lineiter
|
||||||
tailscale.com/types/structs from tailscale.com/ipn+
|
tailscale.com/types/structs from tailscale.com/ipn+
|
||||||
tailscale.com/types/tkatype from tailscale.com/client/tailscale+
|
tailscale.com/types/tkatype from tailscale.com/client/tailscale+
|
||||||
tailscale.com/types/views from tailscale.com/ipn+
|
tailscale.com/types/views from tailscale.com/ipn+
|
||||||
@ -154,7 +155,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
tailscale.com/util/fastuuid from tailscale.com/tsweb
|
tailscale.com/util/fastuuid from tailscale.com/tsweb
|
||||||
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
||||||
tailscale.com/util/httpm from tailscale.com/client/tailscale
|
tailscale.com/util/httpm from tailscale.com/client/tailscale
|
||||||
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
tailscale.com/util/lineiter from tailscale.com/hostinfo+
|
||||||
L tailscale.com/util/linuxfw from tailscale.com/net/netns
|
L tailscale.com/util/linuxfw from tailscale.com/net/netns
|
||||||
tailscale.com/util/mak from tailscale.com/health+
|
tailscale.com/util/mak from tailscale.com/health+
|
||||||
tailscale.com/util/multierr from tailscale.com/health+
|
tailscale.com/util/multierr from tailscale.com/health+
|
||||||
|
@ -775,6 +775,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
|||||||
tailscale.com/types/persist from tailscale.com/control/controlclient+
|
tailscale.com/types/persist from tailscale.com/control/controlclient+
|
||||||
tailscale.com/types/preftype from tailscale.com/ipn+
|
tailscale.com/types/preftype from tailscale.com/ipn+
|
||||||
tailscale.com/types/ptr from tailscale.com/cmd/k8s-operator+
|
tailscale.com/types/ptr from tailscale.com/cmd/k8s-operator+
|
||||||
|
tailscale.com/types/result from tailscale.com/util/lineiter
|
||||||
tailscale.com/types/structs from tailscale.com/control/controlclient+
|
tailscale.com/types/structs from tailscale.com/control/controlclient+
|
||||||
tailscale.com/types/tkatype from tailscale.com/client/tailscale+
|
tailscale.com/types/tkatype from tailscale.com/client/tailscale+
|
||||||
tailscale.com/types/views from tailscale.com/appc+
|
tailscale.com/types/views from tailscale.com/appc+
|
||||||
@ -792,7 +793,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
|||||||
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
||||||
tailscale.com/util/httphdr from tailscale.com/ipn/ipnlocal+
|
tailscale.com/util/httphdr from tailscale.com/ipn/ipnlocal+
|
||||||
tailscale.com/util/httpm from tailscale.com/client/tailscale+
|
tailscale.com/util/httpm from tailscale.com/client/tailscale+
|
||||||
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
tailscale.com/util/lineiter from tailscale.com/hostinfo+
|
||||||
L tailscale.com/util/linuxfw from tailscale.com/net/netns+
|
L tailscale.com/util/linuxfw from tailscale.com/net/netns+
|
||||||
tailscale.com/util/mak from tailscale.com/appc+
|
tailscale.com/util/mak from tailscale.com/appc+
|
||||||
tailscale.com/util/multierr from tailscale.com/control/controlclient+
|
tailscale.com/util/multierr from tailscale.com/control/controlclient+
|
||||||
|
@ -67,6 +67,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
|||||||
tailscale.com/types/logger from tailscale.com/tsweb
|
tailscale.com/types/logger from tailscale.com/tsweb
|
||||||
tailscale.com/types/opt from tailscale.com/envknob+
|
tailscale.com/types/opt from tailscale.com/envknob+
|
||||||
tailscale.com/types/ptr from tailscale.com/tailcfg+
|
tailscale.com/types/ptr from tailscale.com/tailcfg+
|
||||||
|
tailscale.com/types/result from tailscale.com/util/lineiter
|
||||||
tailscale.com/types/structs from tailscale.com/tailcfg+
|
tailscale.com/types/structs from tailscale.com/tailcfg+
|
||||||
tailscale.com/types/tkatype from tailscale.com/tailcfg+
|
tailscale.com/types/tkatype from tailscale.com/tailcfg+
|
||||||
tailscale.com/types/views from tailscale.com/net/tsaddr+
|
tailscale.com/types/views from tailscale.com/net/tsaddr+
|
||||||
@ -74,7 +75,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
|||||||
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
|
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
|
||||||
tailscale.com/util/dnsname from tailscale.com/tailcfg
|
tailscale.com/util/dnsname from tailscale.com/tailcfg
|
||||||
tailscale.com/util/fastuuid from tailscale.com/tsweb
|
tailscale.com/util/fastuuid from tailscale.com/tsweb
|
||||||
tailscale.com/util/lineread from tailscale.com/version/distro
|
tailscale.com/util/lineiter from tailscale.com/version/distro
|
||||||
tailscale.com/util/nocasemaps from tailscale.com/types/ipproto
|
tailscale.com/util/nocasemaps from tailscale.com/types/ipproto
|
||||||
tailscale.com/util/slicesx from tailscale.com/tailcfg
|
tailscale.com/util/slicesx from tailscale.com/tailcfg
|
||||||
tailscale.com/util/vizerror from tailscale.com/tailcfg+
|
tailscale.com/util/vizerror from tailscale.com/tailcfg+
|
||||||
|
@ -148,6 +148,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
tailscale.com/types/persist from tailscale.com/ipn
|
tailscale.com/types/persist from tailscale.com/ipn
|
||||||
tailscale.com/types/preftype from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/types/preftype from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/types/ptr from tailscale.com/hostinfo+
|
tailscale.com/types/ptr from tailscale.com/hostinfo+
|
||||||
|
tailscale.com/types/result from tailscale.com/util/lineiter
|
||||||
tailscale.com/types/structs from tailscale.com/ipn+
|
tailscale.com/types/structs from tailscale.com/ipn+
|
||||||
tailscale.com/types/tkatype from tailscale.com/types/key+
|
tailscale.com/types/tkatype from tailscale.com/types/key+
|
||||||
tailscale.com/types/views from tailscale.com/tailcfg+
|
tailscale.com/types/views from tailscale.com/tailcfg+
|
||||||
@ -162,7 +163,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
tailscale.com/util/groupmember from tailscale.com/client/web
|
tailscale.com/util/groupmember from tailscale.com/client/web
|
||||||
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
||||||
tailscale.com/util/httpm from tailscale.com/client/tailscale+
|
tailscale.com/util/httpm from tailscale.com/client/tailscale+
|
||||||
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
tailscale.com/util/lineiter from tailscale.com/hostinfo+
|
||||||
L tailscale.com/util/linuxfw from tailscale.com/net/netns
|
L tailscale.com/util/linuxfw from tailscale.com/net/netns
|
||||||
tailscale.com/util/mak from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/util/mak from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/util/multierr from tailscale.com/control/controlhttp+
|
tailscale.com/util/multierr from tailscale.com/control/controlhttp+
|
||||||
|
@ -364,6 +364,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
tailscale.com/types/persist from tailscale.com/control/controlclient+
|
tailscale.com/types/persist from tailscale.com/control/controlclient+
|
||||||
tailscale.com/types/preftype from tailscale.com/ipn+
|
tailscale.com/types/preftype from tailscale.com/ipn+
|
||||||
tailscale.com/types/ptr from tailscale.com/control/controlclient+
|
tailscale.com/types/ptr from tailscale.com/control/controlclient+
|
||||||
|
tailscale.com/types/result from tailscale.com/util/lineiter
|
||||||
tailscale.com/types/structs from tailscale.com/control/controlclient+
|
tailscale.com/types/structs from tailscale.com/control/controlclient+
|
||||||
tailscale.com/types/tkatype from tailscale.com/tka+
|
tailscale.com/types/tkatype from tailscale.com/tka+
|
||||||
tailscale.com/types/views from tailscale.com/ipn/ipnlocal+
|
tailscale.com/types/views from tailscale.com/ipn/ipnlocal+
|
||||||
@ -381,7 +382,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
||||||
tailscale.com/util/httphdr from tailscale.com/ipn/ipnlocal+
|
tailscale.com/util/httphdr from tailscale.com/ipn/ipnlocal+
|
||||||
tailscale.com/util/httpm from tailscale.com/client/tailscale+
|
tailscale.com/util/httpm from tailscale.com/client/tailscale+
|
||||||
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
tailscale.com/util/lineiter from tailscale.com/hostinfo+
|
||||||
L tailscale.com/util/linuxfw from tailscale.com/net/netns+
|
L tailscale.com/util/linuxfw from tailscale.com/net/netns+
|
||||||
tailscale.com/util/mak from tailscale.com/control/controlclient+
|
tailscale.com/util/mak from tailscale.com/control/controlclient+
|
||||||
tailscale.com/util/multierr from tailscale.com/cmd/tailscaled+
|
tailscale.com/util/multierr from tailscale.com/cmd/tailscaled+
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
"tailscale.com/types/ptr"
|
"tailscale.com/types/ptr"
|
||||||
"tailscale.com/util/cloudenv"
|
"tailscale.com/util/cloudenv"
|
||||||
"tailscale.com/util/dnsname"
|
"tailscale.com/util/dnsname"
|
||||||
"tailscale.com/util/lineread"
|
"tailscale.com/util/lineiter"
|
||||||
"tailscale.com/version"
|
"tailscale.com/version"
|
||||||
"tailscale.com/version/distro"
|
"tailscale.com/version/distro"
|
||||||
)
|
)
|
||||||
@ -231,12 +231,12 @@ func desktop() (ret opt.Bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
seenDesktop := false
|
seenDesktop := false
|
||||||
lineread.File("/proc/net/unix", func(line []byte) error {
|
for lr := range lineiter.File("/proc/net/unix") {
|
||||||
|
line, _ := lr.Value()
|
||||||
seenDesktop = seenDesktop || mem.Contains(mem.B(line), mem.S(" @/tmp/dbus-"))
|
seenDesktop = seenDesktop || mem.Contains(mem.B(line), mem.S(" @/tmp/dbus-"))
|
||||||
seenDesktop = seenDesktop || mem.Contains(mem.B(line), mem.S(".X11-unix"))
|
seenDesktop = seenDesktop || mem.Contains(mem.B(line), mem.S(".X11-unix"))
|
||||||
seenDesktop = seenDesktop || mem.Contains(mem.B(line), mem.S("/wayland-1"))
|
seenDesktop = seenDesktop || mem.Contains(mem.B(line), mem.S("/wayland-1"))
|
||||||
return nil
|
}
|
||||||
})
|
|
||||||
ret.Set(seenDesktop)
|
ret.Set(seenDesktop)
|
||||||
|
|
||||||
// Only cache after a minute - compositors might not have started yet.
|
// Only cache after a minute - compositors might not have started yet.
|
||||||
@ -305,21 +305,21 @@ func inContainer() opt.Bool {
|
|||||||
ret.Set(true)
|
ret.Set(true)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
lineread.File("/proc/1/cgroup", func(line []byte) error {
|
for lr := range lineiter.File("/proc/1/cgroup") {
|
||||||
|
line, _ := lr.Value()
|
||||||
if mem.Contains(mem.B(line), mem.S("/docker/")) ||
|
if mem.Contains(mem.B(line), mem.S("/docker/")) ||
|
||||||
mem.Contains(mem.B(line), mem.S("/lxc/")) {
|
mem.Contains(mem.B(line), mem.S("/lxc/")) {
|
||||||
ret.Set(true)
|
ret.Set(true)
|
||||||
return io.EOF // arbitrary non-nil error to stop loop
|
break
|
||||||
}
|
}
|
||||||
return nil
|
}
|
||||||
})
|
for lr := range lineiter.File("/proc/mounts") {
|
||||||
lineread.File("/proc/mounts", func(line []byte) error {
|
line, _ := lr.Value()
|
||||||
if mem.Contains(mem.B(line), mem.S("lxcfs /proc/cpuinfo fuse.lxcfs")) {
|
if mem.Contains(mem.B(line), mem.S("lxcfs /proc/cpuinfo fuse.lxcfs")) {
|
||||||
ret.Set(true)
|
ret.Set(true)
|
||||||
return io.EOF
|
break
|
||||||
}
|
}
|
||||||
return nil
|
}
|
||||||
})
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"tailscale.com/types/ptr"
|
"tailscale.com/types/ptr"
|
||||||
"tailscale.com/util/lineread"
|
"tailscale.com/util/lineiter"
|
||||||
"tailscale.com/version/distro"
|
"tailscale.com/version/distro"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -106,15 +106,18 @@ func linuxVersionMeta() (meta versionMeta) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m := map[string]string{}
|
m := map[string]string{}
|
||||||
lineread.File(propFile, func(line []byte) error {
|
for lr := range lineiter.File(propFile) {
|
||||||
|
line, err := lr.Value()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
eq := bytes.IndexByte(line, '=')
|
eq := bytes.IndexByte(line, '=')
|
||||||
if eq == -1 {
|
if eq == -1 {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
k, v := string(line[:eq]), strings.Trim(string(line[eq+1:]), `"'`)
|
k, v := string(line[:eq]), strings.Trim(string(line[eq+1:]), `"'`)
|
||||||
m[k] = v
|
m[k] = v
|
||||||
return nil
|
}
|
||||||
})
|
|
||||||
|
|
||||||
if v := m["VERSION_CODENAME"]; v != "" {
|
if v := m["VERSION_CODENAME"]; v != "" {
|
||||||
meta.DistroCodeName = v
|
meta.DistroCodeName = v
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
"github.com/tailscale/golang-x-crypto/ssh"
|
"github.com/tailscale/golang-x-crypto/ssh"
|
||||||
"go4.org/mem"
|
"go4.org/mem"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/util/lineread"
|
"tailscale.com/util/lineiter"
|
||||||
"tailscale.com/util/mak"
|
"tailscale.com/util/mak"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -80,30 +80,32 @@ func (b *LocalBackend) getSSHUsernames(req *tailcfg.C2NSSHUsernamesRequest) (*ta
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
lineread.Reader(bytes.NewReader(out), func(line []byte) error {
|
for line := range lineiter.Bytes(out) {
|
||||||
line = bytes.TrimSpace(line)
|
line = bytes.TrimSpace(line)
|
||||||
if len(line) == 0 || line[0] == '_' {
|
if len(line) == 0 || line[0] == '_' {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
add(string(line))
|
add(string(line))
|
||||||
return nil
|
}
|
||||||
})
|
|
||||||
default:
|
default:
|
||||||
lineread.File("/etc/passwd", func(line []byte) error {
|
for lr := range lineiter.File("/etc/passwd") {
|
||||||
|
line, err := lr.Value()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
line = bytes.TrimSpace(line)
|
line = bytes.TrimSpace(line)
|
||||||
if len(line) == 0 || line[0] == '#' || line[0] == '_' {
|
if len(line) == 0 || line[0] == '#' || line[0] == '_' {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
if mem.HasSuffix(mem.B(line), mem.S("/nologin")) ||
|
if mem.HasSuffix(mem.B(line), mem.S("/nologin")) ||
|
||||||
mem.HasSuffix(mem.B(line), mem.S("/false")) {
|
mem.HasSuffix(mem.B(line), mem.S("/false")) {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
colon := bytes.IndexByte(line, ':')
|
colon := bytes.IndexByte(line, ':')
|
||||||
if colon != -1 {
|
if colon != -1 {
|
||||||
add(string(line[:colon]))
|
add(string(line[:colon]))
|
||||||
}
|
}
|
||||||
return nil
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"log"
|
"log"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -15,7 +14,7 @@
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"tailscale.com/net/netaddr"
|
"tailscale.com/net/netaddr"
|
||||||
"tailscale.com/syncs"
|
"tailscale.com/syncs"
|
||||||
"tailscale.com/util/lineread"
|
"tailscale.com/util/lineiter"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -34,11 +33,6 @@ func init() {
|
|||||||
|
|
||||||
var procNetRouteErr atomic.Bool
|
var procNetRouteErr atomic.Bool
|
||||||
|
|
||||||
// errStopReading is a sentinel error value used internally by
|
|
||||||
// lineread.File callers to stop reading. It doesn't escape to
|
|
||||||
// callers/users.
|
|
||||||
var errStopReading = errors.New("stop reading")
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Parse 10.0.0.1 out of:
|
Parse 10.0.0.1 out of:
|
||||||
|
|
||||||
@ -54,44 +48,42 @@ func likelyHomeRouterIPAndroid() (ret netip.Addr, myIP netip.Addr, ok bool) {
|
|||||||
}
|
}
|
||||||
lineNum := 0
|
lineNum := 0
|
||||||
var f []mem.RO
|
var f []mem.RO
|
||||||
err := lineread.File(procNetRoutePath, func(line []byte) error {
|
for lr := range lineiter.File(procNetRoutePath) {
|
||||||
|
line, err := lr.Value()
|
||||||
|
if err != nil {
|
||||||
|
procNetRouteErr.Store(true)
|
||||||
|
return likelyHomeRouterIP()
|
||||||
|
}
|
||||||
|
|
||||||
lineNum++
|
lineNum++
|
||||||
if lineNum == 1 {
|
if lineNum == 1 {
|
||||||
// Skip header line.
|
// Skip header line.
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
if lineNum > maxProcNetRouteRead {
|
if lineNum > maxProcNetRouteRead {
|
||||||
return errStopReading
|
break
|
||||||
}
|
}
|
||||||
f = mem.AppendFields(f[:0], mem.B(line))
|
f = mem.AppendFields(f[:0], mem.B(line))
|
||||||
if len(f) < 4 {
|
if len(f) < 4 {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
gwHex, flagsHex := f[2], f[3]
|
gwHex, flagsHex := f[2], f[3]
|
||||||
flags, err := mem.ParseUint(flagsHex, 16, 16)
|
flags, err := mem.ParseUint(flagsHex, 16, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil // ignore error, skip line and keep going
|
continue // ignore error, skip line and keep going
|
||||||
}
|
}
|
||||||
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
|
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
ipu32, err := mem.ParseUint(gwHex, 16, 32)
|
ipu32, err := mem.ParseUint(gwHex, 16, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil // ignore error, skip line and keep going
|
continue // ignore error, skip line and keep going
|
||||||
}
|
}
|
||||||
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
|
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
|
||||||
if ip.IsPrivate() {
|
if ip.IsPrivate() {
|
||||||
ret = ip
|
ret = ip
|
||||||
return errStopReading
|
break
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if errors.Is(err, errStopReading) {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
procNetRouteErr.Store(true)
|
|
||||||
return likelyHomeRouterIP()
|
|
||||||
}
|
}
|
||||||
if ret.IsValid() {
|
if ret.IsValid() {
|
||||||
// Try to get the local IP of the interface associated with
|
// Try to get the local IP of the interface associated with
|
||||||
@ -144,23 +136,26 @@ func likelyHomeRouterIPHelper() (ret netip.Addr, _ netip.Addr, ok bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Search for line like "default via 10.0.2.2 dev radio0 table 1016 proto static mtu 1500 "
|
// Search for line like "default via 10.0.2.2 dev radio0 table 1016 proto static mtu 1500 "
|
||||||
lineread.Reader(out, func(line []byte) error {
|
for lr := range lineiter.Reader(out) {
|
||||||
|
line, err := lr.Value()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
const pfx = "default via "
|
const pfx = "default via "
|
||||||
if !mem.HasPrefix(mem.B(line), mem.S(pfx)) {
|
if !mem.HasPrefix(mem.B(line), mem.S(pfx)) {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
line = line[len(pfx):]
|
line = line[len(pfx):]
|
||||||
sp := bytes.IndexByte(line, ' ')
|
sp := bytes.IndexByte(line, ' ')
|
||||||
if sp == -1 {
|
if sp == -1 {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
ipb := line[:sp]
|
ipb := line[:sp]
|
||||||
if ip, err := netip.ParseAddr(string(ipb)); err == nil && ip.Is4() {
|
if ip, err := netip.ParseAddr(string(ipb)); err == nil && ip.Is4() {
|
||||||
ret = ip
|
ret = ip
|
||||||
log.Printf("interfaces: found Android default route %v", ip)
|
log.Printf("interfaces: found Android default route %v", ip)
|
||||||
}
|
}
|
||||||
return nil
|
}
|
||||||
})
|
|
||||||
cmd.Process.Kill()
|
cmd.Process.Kill()
|
||||||
cmd.Wait()
|
cmd.Wait()
|
||||||
return ret, netip.Addr{}, ret.IsValid()
|
return ret, netip.Addr{}, ret.IsValid()
|
||||||
|
@ -4,14 +4,13 @@
|
|||||||
package netmon
|
package netmon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go4.org/mem"
|
"go4.org/mem"
|
||||||
"tailscale.com/util/lineread"
|
"tailscale.com/util/lineiter"
|
||||||
"tailscale.com/version"
|
"tailscale.com/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -73,31 +72,34 @@ func likelyHomeRouterIPDarwinExec() (ret netip.Addr, netif string, ok bool) {
|
|||||||
defer io.Copy(io.Discard, stdout) // clear the pipe to prevent hangs
|
defer io.Copy(io.Discard, stdout) // clear the pipe to prevent hangs
|
||||||
|
|
||||||
var f []mem.RO
|
var f []mem.RO
|
||||||
lineread.Reader(stdout, func(lineb []byte) error {
|
for lr := range lineiter.Reader(stdout) {
|
||||||
|
lineb, err := lr.Value()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
line := mem.B(lineb)
|
line := mem.B(lineb)
|
||||||
if !mem.Contains(line, mem.S("default")) {
|
if !mem.Contains(line, mem.S("default")) {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
f = mem.AppendFields(f[:0], line)
|
f = mem.AppendFields(f[:0], line)
|
||||||
if len(f) < 4 || !f[0].EqualString("default") {
|
if len(f) < 4 || !f[0].EqualString("default") {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
ipm, flagsm, netifm := f[1], f[2], f[3]
|
ipm, flagsm, netifm := f[1], f[2], f[3]
|
||||||
if !mem.Contains(flagsm, mem.S("G")) {
|
if !mem.Contains(flagsm, mem.S("G")) {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
if mem.Contains(flagsm, mem.S("I")) {
|
if mem.Contains(flagsm, mem.S("I")) {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
ip, err := netip.ParseAddr(string(mem.Append(nil, ipm)))
|
ip, err := netip.ParseAddr(string(mem.Append(nil, ipm)))
|
||||||
if err == nil && ip.IsPrivate() {
|
if err == nil && ip.IsPrivate() {
|
||||||
ret = ip
|
ret = ip
|
||||||
netif = netifm.StringCopy()
|
netif = netifm.StringCopy()
|
||||||
// We've found what we're looking for.
|
// We've found what we're looking for.
|
||||||
return errStopReadingNetstatTable
|
break
|
||||||
}
|
}
|
||||||
return nil
|
}
|
||||||
})
|
|
||||||
return ret, netif, ret.IsValid()
|
return ret, netif, ret.IsValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,5 +112,3 @@ func TestFetchRoutingTable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var errStopReadingNetstatTable = errors.New("found private gateway")
|
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"go4.org/mem"
|
"go4.org/mem"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"tailscale.com/net/netaddr"
|
"tailscale.com/net/netaddr"
|
||||||
"tailscale.com/util/lineread"
|
"tailscale.com/util/lineiter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -32,11 +32,6 @@ func init() {
|
|||||||
|
|
||||||
var procNetRouteErr atomic.Bool
|
var procNetRouteErr atomic.Bool
|
||||||
|
|
||||||
// errStopReading is a sentinel error value used internally by
|
|
||||||
// lineread.File callers to stop reading. It doesn't escape to
|
|
||||||
// callers/users.
|
|
||||||
var errStopReading = errors.New("stop reading")
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Parse 10.0.0.1 out of:
|
Parse 10.0.0.1 out of:
|
||||||
|
|
||||||
@ -52,44 +47,42 @@ func likelyHomeRouterIPLinux() (ret netip.Addr, myIP netip.Addr, ok bool) {
|
|||||||
}
|
}
|
||||||
lineNum := 0
|
lineNum := 0
|
||||||
var f []mem.RO
|
var f []mem.RO
|
||||||
err := lineread.File(procNetRoutePath, func(line []byte) error {
|
for lr := range lineiter.File(procNetRoutePath) {
|
||||||
|
line, err := lr.Value()
|
||||||
|
if err != nil {
|
||||||
|
procNetRouteErr.Store(true)
|
||||||
|
log.Printf("interfaces: failed to read /proc/net/route: %v", err)
|
||||||
|
return ret, myIP, false
|
||||||
|
}
|
||||||
lineNum++
|
lineNum++
|
||||||
if lineNum == 1 {
|
if lineNum == 1 {
|
||||||
// Skip header line.
|
// Skip header line.
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
if lineNum > maxProcNetRouteRead {
|
if lineNum > maxProcNetRouteRead {
|
||||||
return errStopReading
|
break
|
||||||
}
|
}
|
||||||
f = mem.AppendFields(f[:0], mem.B(line))
|
f = mem.AppendFields(f[:0], mem.B(line))
|
||||||
if len(f) < 4 {
|
if len(f) < 4 {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
gwHex, flagsHex := f[2], f[3]
|
gwHex, flagsHex := f[2], f[3]
|
||||||
flags, err := mem.ParseUint(flagsHex, 16, 16)
|
flags, err := mem.ParseUint(flagsHex, 16, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil // ignore error, skip line and keep going
|
continue // ignore error, skip line and keep going
|
||||||
}
|
}
|
||||||
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
|
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
ipu32, err := mem.ParseUint(gwHex, 16, 32)
|
ipu32, err := mem.ParseUint(gwHex, 16, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil // ignore error, skip line and keep going
|
continue // ignore error, skip line and keep going
|
||||||
}
|
}
|
||||||
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
|
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
|
||||||
if ip.IsPrivate() {
|
if ip.IsPrivate() {
|
||||||
ret = ip
|
ret = ip
|
||||||
return errStopReading
|
break
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if errors.Is(err, errStopReading) {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
procNetRouteErr.Store(true)
|
|
||||||
log.Printf("interfaces: failed to read /proc/net/route: %v", err)
|
|
||||||
}
|
}
|
||||||
if ret.IsValid() {
|
if ret.IsValid() {
|
||||||
// Try to get the local IP of the interface associated with
|
// Try to get the local IP of the interface associated with
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
//go:build linux && !android
|
||||||
|
|
||||||
package netmon
|
package netmon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tailscale.com/util/lineread"
|
"tailscale.com/util/lineiter"
|
||||||
)
|
)
|
||||||
|
|
||||||
// These vars are overridden for tests.
|
// These vars are overridden for tests.
|
||||||
@ -76,21 +76,22 @@ func synologyProxiesFromConfig() (*url.URL, *url.URL, error) {
|
|||||||
func parseSynologyConfig(r io.Reader) (*url.URL, *url.URL, error) {
|
func parseSynologyConfig(r io.Reader) (*url.URL, *url.URL, error) {
|
||||||
cfg := map[string]string{}
|
cfg := map[string]string{}
|
||||||
|
|
||||||
if err := lineread.Reader(r, func(line []byte) error {
|
for lr := range lineiter.Reader(r) {
|
||||||
|
line, err := lr.Value()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
// accept and skip over empty lines
|
// accept and skip over empty lines
|
||||||
line = bytes.TrimSpace(line)
|
line = bytes.TrimSpace(line)
|
||||||
if len(line) == 0 {
|
if len(line) == 0 {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
key, value, ok := strings.Cut(string(line), "=")
|
key, value, ok := strings.Cut(string(line), "=")
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("missing \"=\" in proxy.conf line: %q", line)
|
return nil, nil, fmt.Errorf("missing \"=\" in proxy.conf line: %q", line)
|
||||||
}
|
}
|
||||||
cfg[string(key)] = string(value)
|
cfg[string(key)] = string(value)
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg["proxy_enabled"] != "yes" {
|
if cfg["proxy_enabled"] != "yes" {
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
"tailscale.com/types/netmap"
|
"tailscale.com/types/netmap"
|
||||||
"tailscale.com/types/ptr"
|
"tailscale.com/types/ptr"
|
||||||
"tailscale.com/util/cibuild"
|
"tailscale.com/util/cibuild"
|
||||||
"tailscale.com/util/lineread"
|
"tailscale.com/util/lineiter"
|
||||||
"tailscale.com/util/must"
|
"tailscale.com/util/must"
|
||||||
"tailscale.com/version/distro"
|
"tailscale.com/version/distro"
|
||||||
"tailscale.com/wgengine"
|
"tailscale.com/wgengine"
|
||||||
@ -1123,14 +1123,11 @@ func TestSSH(t *testing.T) {
|
|||||||
|
|
||||||
func parseEnv(out []byte) map[string]string {
|
func parseEnv(out []byte) map[string]string {
|
||||||
e := map[string]string{}
|
e := map[string]string{}
|
||||||
lineread.Reader(bytes.NewReader(out), func(line []byte) error {
|
for line := range lineiter.Bytes(out) {
|
||||||
i := bytes.IndexByte(line, '=')
|
if i := bytes.IndexByte(line, '='); i != -1 {
|
||||||
if i == -1 {
|
e[string(line[:i])] = string(line[i+1:])
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
e[string(line[:i])] = string(line[i+1:])
|
}
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
package tailssh
|
package tailssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
@ -18,7 +17,7 @@
|
|||||||
"go4.org/mem"
|
"go4.org/mem"
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/hostinfo"
|
"tailscale.com/hostinfo"
|
||||||
"tailscale.com/util/lineread"
|
"tailscale.com/util/lineiter"
|
||||||
"tailscale.com/util/osuser"
|
"tailscale.com/util/osuser"
|
||||||
"tailscale.com/version/distro"
|
"tailscale.com/version/distro"
|
||||||
)
|
)
|
||||||
@ -110,15 +109,16 @@ func defaultPathForUser(u *user.User) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func defaultPathForUserOnNixOS(u *user.User) string {
|
func defaultPathForUserOnNixOS(u *user.User) string {
|
||||||
var path string
|
for lr := range lineiter.File("/etc/pam/environment") {
|
||||||
lineread.File("/etc/pam/environment", func(lineb []byte) error {
|
lineb, err := lr.Value()
|
||||||
if v := pathFromPAMEnvLine(lineb, u); v != "" {
|
if err != nil {
|
||||||
path = v
|
return ""
|
||||||
return io.EOF // stop iteration
|
|
||||||
}
|
}
|
||||||
return nil
|
if v := pathFromPAMEnvLine(lineb, u); v != "" {
|
||||||
})
|
return v
|
||||||
return path
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func pathFromPAMEnvLine(line []byte, u *user.User) (path string) {
|
func pathFromPAMEnvLine(line []byte, u *user.User) (path string) {
|
||||||
|
49
types/result/result.go
Normal file
49
types/result/result.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Package result contains the Of result type, which is
|
||||||
|
// either a value or an error.
|
||||||
|
package result
|
||||||
|
|
||||||
|
// Of is either a T value or an error.
|
||||||
|
//
|
||||||
|
// Think of it like Rust or Swift's result types.
|
||||||
|
// It's named "Of" because the fully qualified name
|
||||||
|
// for callers reads result.Of[T].
|
||||||
|
type Of[T any] struct {
|
||||||
|
v T // valid if Err is nil; invalid if Err is non-nil
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns a new result with value v,
|
||||||
|
// without an error.
|
||||||
|
func Value[T any](v T) Of[T] {
|
||||||
|
return Of[T]{v: v}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns a new result with error err.
|
||||||
|
// If err is nil, the returned result is equivalent
|
||||||
|
// to calling Value with T's zero value.
|
||||||
|
func Error[T any](err error) Of[T] {
|
||||||
|
return Of[T]{err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustValue returns r's result value.
|
||||||
|
// It panics if r.Err returns non-nil.
|
||||||
|
func (r Of[T]) MustValue() T {
|
||||||
|
if r.err != nil {
|
||||||
|
panic(r.err)
|
||||||
|
}
|
||||||
|
return r.v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns r's result value and error.
|
||||||
|
func (r Of[T]) Value() (T, error) {
|
||||||
|
return r.v, r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns r's error, if any.
|
||||||
|
// When r.Err returns nil, it's safe to call r.MustValue without it panicking.
|
||||||
|
func (r Of[T]) Err() error {
|
||||||
|
return r.err
|
||||||
|
}
|
72
util/lineiter/lineiter.go
Normal file
72
util/lineiter/lineiter.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Package lineiter iterates over lines in things.
|
||||||
|
package lineiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"iter"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"tailscale.com/types/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File returns an iterator that reads lines from the named file.
|
||||||
|
//
|
||||||
|
// The returned substrings don't include the trailing newline.
|
||||||
|
// Lines may be empty.
|
||||||
|
func File(name string) iter.Seq[result.Of[[]byte]] {
|
||||||
|
f, err := os.Open(name)
|
||||||
|
return reader(f, f, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns an iterator over the lines in bs.
|
||||||
|
// The returned substrings don't include the trailing newline.
|
||||||
|
// Lines may be empty.
|
||||||
|
func Bytes(bs []byte) iter.Seq[[]byte] {
|
||||||
|
return func(yield func([]byte) bool) {
|
||||||
|
for len(bs) > 0 {
|
||||||
|
i := bytes.IndexByte(bs, '\n')
|
||||||
|
if i < 0 {
|
||||||
|
yield(bs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !yield(bs[:i]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bs = bs[i+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reader returns an iterator over the lines in r.
|
||||||
|
//
|
||||||
|
// The returned substrings don't include the trailing newline.
|
||||||
|
// Lines may be empty.
|
||||||
|
func Reader(r io.Reader) iter.Seq[result.Of[[]byte]] {
|
||||||
|
return reader(r, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func reader(r io.Reader, c io.Closer, err error) iter.Seq[result.Of[[]byte]] {
|
||||||
|
return func(yield func(result.Of[[]byte]) bool) {
|
||||||
|
if err != nil {
|
||||||
|
yield(result.Error[[]byte](err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
defer c.Close()
|
||||||
|
}
|
||||||
|
bs := bufio.NewScanner(r)
|
||||||
|
for bs.Scan() {
|
||||||
|
if !yield(result.Value(bs.Bytes())) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := bs.Err(); err != nil {
|
||||||
|
yield(result.Error[[]byte](err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
util/lineiter/lineiter_test.go
Normal file
32
util/lineiter/lineiter_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package lineiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBytesLines(t *testing.T) {
|
||||||
|
var got []string
|
||||||
|
for line := range Bytes([]byte("foo\n\nbar\nbaz")) {
|
||||||
|
got = append(got, string(line))
|
||||||
|
}
|
||||||
|
want := []string{"foo", "", "bar", "baz"}
|
||||||
|
if !slices.Equal(got, want) {
|
||||||
|
t.Errorf("got %q; want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReader(t *testing.T) {
|
||||||
|
var got []string
|
||||||
|
for line := range Reader(strings.NewReader("foo\n\nbar\nbaz")) {
|
||||||
|
got = append(got, string(line.MustValue()))
|
||||||
|
}
|
||||||
|
want := []string{"foo", "", "bar", "baz"}
|
||||||
|
if !slices.Equal(got, want) {
|
||||||
|
t.Errorf("got %q; want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
@ -8,26 +8,26 @@
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"tailscale.com/util/lineread"
|
"tailscale.com/util/lineiter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ownerOfPID(pid int) (userID string, err error) {
|
func ownerOfPID(pid int) (userID string, err error) {
|
||||||
file := fmt.Sprintf("/proc/%d/status", pid)
|
file := fmt.Sprintf("/proc/%d/status", pid)
|
||||||
err = lineread.File(file, func(line []byte) error {
|
for lr := range lineiter.File(file) {
|
||||||
|
line, err := lr.Value()
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return "", ErrProcessNotFound
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
if len(line) < 4 || string(line[:4]) != "Uid:" {
|
if len(line) < 4 || string(line[:4]) != "Uid:" {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
f := strings.Fields(string(line))
|
f := strings.Fields(string(line))
|
||||||
if len(f) >= 2 {
|
if len(f) >= 2 {
|
||||||
userID = f[1] // real userid
|
userID = f[1] // real userid
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return "", ErrProcessNotFound
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
return "", fmt.Errorf("missing Uid line in %s", file)
|
return "", fmt.Errorf("missing Uid line in %s", file)
|
||||||
|
@ -6,13 +6,12 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"tailscale.com/types/lazy"
|
"tailscale.com/types/lazy"
|
||||||
"tailscale.com/util/lineread"
|
"tailscale.com/util/lineiter"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Distro string
|
type Distro string
|
||||||
@ -132,18 +131,19 @@ func DSMVersion() int {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
// But when run from the command line, we have to read it from the file:
|
// But when run from the command line, we have to read it from the file:
|
||||||
lineread.File("/etc/VERSION", func(line []byte) error {
|
for lr := range lineiter.File("/etc/VERSION") {
|
||||||
|
line, err := lr.Value()
|
||||||
|
if err != nil {
|
||||||
|
break // but otherwise ignore
|
||||||
|
}
|
||||||
line = bytes.TrimSpace(line)
|
line = bytes.TrimSpace(line)
|
||||||
if string(line) == `majorversion="7"` {
|
if string(line) == `majorversion="7"` {
|
||||||
v = 7
|
return 7
|
||||||
return io.EOF
|
|
||||||
}
|
}
|
||||||
if string(line) == `majorversion="6"` {
|
if string(line) == `majorversion="6"` {
|
||||||
v = 6
|
return 6
|
||||||
return io.EOF
|
|
||||||
}
|
}
|
||||||
return nil
|
}
|
||||||
})
|
return 0
|
||||||
return v
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user