mirror of
https://github.com/shirou/gopsutil.git
synced 2025-04-28 13:48:49 +08:00

The goal is to improve performance of connection fetching connections across all processes when some processes can have several hundred or thousands of file descriptors. Right now when you have many thousands of fds the process spends lots of time inside the syscalls from Readdir and Readlink. The public API works as before with two new functions: - `ConnectionsMax` - `ConnectionsPidMax` Each function takes an additional int argument that sets the max number of fds read per process.
724 lines
16 KiB
Go
724 lines
16 KiB
Go
// +build linux
|
|
|
|
package net
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/shirou/gopsutil/internal/common"
|
|
)
|
|
|
|
// NetIOCounters returnes network I/O statistics for every network
|
|
// interface installed on the system. If pernic argument is false,
|
|
// return only sum of all information (which name is 'all'). If true,
|
|
// every network interface installed on the system is returned
|
|
// separately.
|
|
func IOCounters(pernic bool) ([]IOCountersStat, error) {
|
|
filename := common.HostProc("net/dev")
|
|
return IOCountersByFile(pernic, filename)
|
|
}
|
|
|
|
func IOCountersByFile(pernic bool, filename string) ([]IOCountersStat, error) {
|
|
lines, err := common.ReadLines(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
statlen := len(lines) - 1
|
|
|
|
ret := make([]IOCountersStat, 0, statlen)
|
|
|
|
for _, line := range lines[2:] {
|
|
parts := strings.SplitN(line, ":", 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
interfaceName := strings.TrimSpace(parts[0])
|
|
if interfaceName == "" {
|
|
continue
|
|
}
|
|
|
|
fields := strings.Fields(strings.TrimSpace(parts[1]))
|
|
bytesRecv, err := strconv.ParseUint(fields[0], 10, 64)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
packetsRecv, err := strconv.ParseUint(fields[1], 10, 64)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
errIn, err := strconv.ParseUint(fields[2], 10, 64)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
dropIn, err := strconv.ParseUint(fields[3], 10, 64)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
fifoIn, err := strconv.ParseUint(fields[4], 10, 64)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
bytesSent, err := strconv.ParseUint(fields[8], 10, 64)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
packetsSent, err := strconv.ParseUint(fields[9], 10, 64)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
errOut, err := strconv.ParseUint(fields[10], 10, 64)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
dropOut, err := strconv.ParseUint(fields[11], 10, 64)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
fifoOut, err := strconv.ParseUint(fields[12], 10, 64)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
|
|
nic := IOCountersStat{
|
|
Name: interfaceName,
|
|
BytesRecv: bytesRecv,
|
|
PacketsRecv: packetsRecv,
|
|
Errin: errIn,
|
|
Dropin: dropIn,
|
|
Fifoin: fifoIn,
|
|
BytesSent: bytesSent,
|
|
PacketsSent: packetsSent,
|
|
Errout: errOut,
|
|
Dropout: dropOut,
|
|
Fifoout: fifoOut,
|
|
}
|
|
ret = append(ret, nic)
|
|
}
|
|
|
|
if pernic == false {
|
|
return getIOCountersAll(ret)
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
var netProtocols = []string{
|
|
"ip",
|
|
"icmp",
|
|
"icmpmsg",
|
|
"tcp",
|
|
"udp",
|
|
"udplite",
|
|
}
|
|
|
|
// NetProtoCounters returns network statistics for the entire system
|
|
// If protocols is empty then all protocols are returned, otherwise
|
|
// just the protocols in the list are returned.
|
|
// Available protocols:
|
|
// ip,icmp,icmpmsg,tcp,udp,udplite
|
|
func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) {
|
|
if len(protocols) == 0 {
|
|
protocols = netProtocols
|
|
}
|
|
|
|
stats := make([]ProtoCountersStat, 0, len(protocols))
|
|
protos := make(map[string]bool, len(protocols))
|
|
for _, p := range protocols {
|
|
protos[p] = true
|
|
}
|
|
|
|
filename := common.HostProc("net/snmp")
|
|
lines, err := common.ReadLines(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
linecount := len(lines)
|
|
for i := 0; i < linecount; i++ {
|
|
line := lines[i]
|
|
r := strings.IndexRune(line, ':')
|
|
if r == -1 {
|
|
return nil, errors.New(filename + " is not fomatted correctly, expected ':'.")
|
|
}
|
|
proto := strings.ToLower(line[:r])
|
|
if !protos[proto] {
|
|
// skip protocol and data line
|
|
i++
|
|
continue
|
|
}
|
|
|
|
// Read header line
|
|
statNames := strings.Split(line[r+2:], " ")
|
|
|
|
// Read data line
|
|
i++
|
|
statValues := strings.Split(lines[i][r+2:], " ")
|
|
if len(statNames) != len(statValues) {
|
|
return nil, errors.New(filename + " is not fomatted correctly, expected same number of columns.")
|
|
}
|
|
stat := ProtoCountersStat{
|
|
Protocol: proto,
|
|
Stats: make(map[string]int64, len(statNames)),
|
|
}
|
|
for j := range statNames {
|
|
value, err := strconv.ParseInt(statValues[j], 10, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stat.Stats[statNames[j]] = value
|
|
}
|
|
stats = append(stats, stat)
|
|
}
|
|
return stats, nil
|
|
}
|
|
|
|
// NetFilterCounters returns iptables conntrack statistics
|
|
// the currently in use conntrack count and the max.
|
|
// If the file does not exist or is invalid it will return nil.
|
|
func FilterCounters() ([]FilterStat, error) {
|
|
countfile := common.HostProc("sys/net/netfilter/nf_conntrackCount")
|
|
maxfile := common.HostProc("sys/net/netfilter/nf_conntrackMax")
|
|
|
|
count, err := common.ReadInts(countfile)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stats := make([]FilterStat, 0, 1)
|
|
|
|
max, err := common.ReadInts(maxfile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
payload := FilterStat{
|
|
ConnTrackCount: count[0],
|
|
ConnTrackMax: max[0],
|
|
}
|
|
|
|
stats = append(stats, payload)
|
|
return stats, nil
|
|
}
|
|
|
|
// http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h
|
|
var TCPStatuses = map[string]string{
|
|
"01": "ESTABLISHED",
|
|
"02": "SYN_SENT",
|
|
"03": "SYN_RECV",
|
|
"04": "FIN_WAIT1",
|
|
"05": "FIN_WAIT2",
|
|
"06": "TIME_WAIT",
|
|
"07": "CLOSE",
|
|
"08": "CLOSE_WAIT",
|
|
"09": "LAST_ACK",
|
|
"0A": "LISTEN",
|
|
"0B": "CLOSING",
|
|
}
|
|
|
|
type netConnectionKindType struct {
|
|
family uint32
|
|
sockType uint32
|
|
filename string
|
|
}
|
|
|
|
var kindTCP4 = netConnectionKindType{
|
|
family: syscall.AF_INET,
|
|
sockType: syscall.SOCK_STREAM,
|
|
filename: "tcp",
|
|
}
|
|
var kindTCP6 = netConnectionKindType{
|
|
family: syscall.AF_INET6,
|
|
sockType: syscall.SOCK_STREAM,
|
|
filename: "tcp6",
|
|
}
|
|
var kindUDP4 = netConnectionKindType{
|
|
family: syscall.AF_INET,
|
|
sockType: syscall.SOCK_DGRAM,
|
|
filename: "udp",
|
|
}
|
|
var kindUDP6 = netConnectionKindType{
|
|
family: syscall.AF_INET6,
|
|
sockType: syscall.SOCK_DGRAM,
|
|
filename: "udp6",
|
|
}
|
|
var kindUNIX = netConnectionKindType{
|
|
family: syscall.AF_UNIX,
|
|
filename: "unix",
|
|
}
|
|
|
|
var netConnectionKindMap = map[string][]netConnectionKindType{
|
|
"all": []netConnectionKindType{kindTCP4, kindTCP6, kindUDP4, kindUDP6, kindUNIX},
|
|
"tcp": []netConnectionKindType{kindTCP4, kindTCP6},
|
|
"tcp4": []netConnectionKindType{kindTCP4},
|
|
"tcp6": []netConnectionKindType{kindTCP6},
|
|
"udp": []netConnectionKindType{kindUDP4, kindUDP6},
|
|
"udp4": []netConnectionKindType{kindUDP4},
|
|
"udp6": []netConnectionKindType{kindUDP6},
|
|
"unix": []netConnectionKindType{kindUNIX},
|
|
"inet": []netConnectionKindType{kindTCP4, kindTCP6, kindUDP4, kindUDP6},
|
|
"inet4": []netConnectionKindType{kindTCP4, kindUDP4},
|
|
"inet6": []netConnectionKindType{kindTCP6, kindUDP6},
|
|
}
|
|
|
|
type inodeMap struct {
|
|
pid int32
|
|
fd uint32
|
|
}
|
|
|
|
type connTmp struct {
|
|
fd uint32
|
|
family uint32
|
|
sockType uint32
|
|
laddr Addr
|
|
raddr Addr
|
|
status string
|
|
uids []int32
|
|
pid int32
|
|
boundPid int32
|
|
path string
|
|
}
|
|
|
|
// Return a list of network connections opened.
|
|
func Connections(kind string) ([]ConnectionStat, error) {
|
|
return ConnectionsPid(kind, 0)
|
|
}
|
|
|
|
// Return a list of network connections opened returning at most `max`
|
|
// connections for each running process.
|
|
func ConnectionsMax(kind string, max int) ([]ConnectionStat, error) {
|
|
return ConnectionsPidMax(kind, 0, max)
|
|
}
|
|
|
|
// Return a list of network connections opened by a process.
|
|
func ConnectionsPid(kind string, pid int32) ([]ConnectionStat, error) {
|
|
tmap, ok := netConnectionKindMap[kind]
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid kind, %s", kind)
|
|
}
|
|
root := common.HostProc()
|
|
var err error
|
|
var inodes map[string][]inodeMap
|
|
if pid == 0 {
|
|
inodes, err = getProcInodesAll(root, 0)
|
|
} else {
|
|
inodes, err = getProcInodes(root, pid, 0)
|
|
if len(inodes) == 0 {
|
|
// no connection for the pid
|
|
return []ConnectionStat{}, nil
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cound not get pid(s), %d", pid)
|
|
}
|
|
return statsFromInodes(root, pid, tmap, inodes)
|
|
}
|
|
|
|
// Return up to `max` network connections opened by a process.
|
|
func ConnectionsPidMax(kind string, pid int32, max int) ([]ConnectionStat, error) {
|
|
tmap, ok := netConnectionKindMap[kind]
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid kind, %s", kind)
|
|
}
|
|
root := common.HostProc()
|
|
var err error
|
|
var inodes map[string][]inodeMap
|
|
if pid == 0 {
|
|
inodes, err = getProcInodesAll(root, max)
|
|
} else {
|
|
inodes, err = getProcInodes(root, pid, max)
|
|
if len(inodes) == 0 {
|
|
// no connection for the pid
|
|
return []ConnectionStat{}, nil
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cound not get pid(s), %d", pid)
|
|
}
|
|
return statsFromInodes(root, pid, tmap, inodes)
|
|
}
|
|
|
|
func statsFromInodes(root string, pid int32, tmap []netConnectionKindType, inodes map[string][]inodeMap) ([]ConnectionStat, error) {
|
|
dupCheckMap := make(map[string]bool)
|
|
var ret []ConnectionStat
|
|
|
|
var err error
|
|
for _, t := range tmap {
|
|
var path string
|
|
var ls []connTmp
|
|
path = fmt.Sprintf("%s/net/%s", root, t.filename)
|
|
switch t.family {
|
|
case syscall.AF_INET:
|
|
fallthrough
|
|
case syscall.AF_INET6:
|
|
ls, err = processInet(path, t, inodes, pid)
|
|
case syscall.AF_UNIX:
|
|
ls, err = processUnix(path, t, inodes, pid)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, c := range ls {
|
|
conn := ConnectionStat{
|
|
Fd: c.fd,
|
|
Family: c.family,
|
|
Type: c.sockType,
|
|
Laddr: c.laddr,
|
|
Raddr: c.raddr,
|
|
Uids: c.uids,
|
|
Status: c.status,
|
|
Pid: c.pid,
|
|
}
|
|
if c.pid == 0 {
|
|
conn.Pid = c.boundPid
|
|
} else {
|
|
conn.Pid = c.pid
|
|
}
|
|
|
|
// fetch process owner Real, effective, saved set, and filesystem UIDs
|
|
proc := process{Pid: conn.Pid}
|
|
conn.Uids, _ = proc.getUids()
|
|
|
|
// check duplicate using JSON format
|
|
json := conn.String()
|
|
_, exists := dupCheckMap[json]
|
|
if !exists {
|
|
ret = append(ret, conn)
|
|
dupCheckMap[json] = true
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// getProcInodes returnes fd of the pid.
|
|
func getProcInodes(root string, pid int32, max int) (map[string][]inodeMap, error) {
|
|
ret := make(map[string][]inodeMap)
|
|
|
|
dir := fmt.Sprintf("%s/%d/fd", root, pid)
|
|
f, err := os.Open(dir)
|
|
if err != nil {
|
|
return ret, nil
|
|
}
|
|
files, err := f.Readdir(max)
|
|
if err != nil {
|
|
return ret, nil
|
|
}
|
|
for _, fd := range files {
|
|
inodePath := fmt.Sprintf("%s/%d/fd/%s", root, pid, fd.Name())
|
|
|
|
inode, err := os.Readlink(inodePath)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if !strings.HasPrefix(inode, "socket:[") {
|
|
continue
|
|
}
|
|
// the process is using a socket
|
|
l := len(inode)
|
|
inode = inode[8 : l-1]
|
|
_, ok := ret[inode]
|
|
if !ok {
|
|
ret[inode] = make([]inodeMap, 0)
|
|
}
|
|
fd, err := strconv.Atoi(fd.Name())
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
i := inodeMap{
|
|
pid: pid,
|
|
fd: uint32(fd),
|
|
}
|
|
ret[inode] = append(ret[inode], i)
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
// Pids retunres all pids.
|
|
// Note: this is a copy of process_linux.Pids()
|
|
// FIXME: Import process occures import cycle.
|
|
// move to common made other platform breaking. Need consider.
|
|
func Pids() ([]int32, error) {
|
|
var ret []int32
|
|
|
|
d, err := os.Open(common.HostProc())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer d.Close()
|
|
|
|
fnames, err := d.Readdirnames(-1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, fname := range fnames {
|
|
pid, err := strconv.ParseInt(fname, 10, 32)
|
|
if err != nil {
|
|
// if not numeric name, just skip
|
|
continue
|
|
}
|
|
ret = append(ret, int32(pid))
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// Note: the following is based off process_linux structs and methods
|
|
// we need these to fetch the owner of a process ID
|
|
// FIXME: Import process occures import cycle.
|
|
// see remarks on pids()
|
|
type process struct {
|
|
Pid int32 `json:"pid"`
|
|
uids []int32
|
|
}
|
|
|
|
// Uids returns user ids of the process as a slice of the int
|
|
func (p *process) getUids() ([]int32, error) {
|
|
err := p.fillFromStatus()
|
|
if err != nil {
|
|
return []int32{}, err
|
|
}
|
|
return p.uids, nil
|
|
}
|
|
|
|
// Get status from /proc/(pid)/status
|
|
func (p *process) fillFromStatus() error {
|
|
pid := p.Pid
|
|
statPath := common.HostProc(strconv.Itoa(int(pid)), "status")
|
|
contents, err := ioutil.ReadFile(statPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
lines := strings.Split(string(contents), "\n")
|
|
for _, line := range lines {
|
|
tabParts := strings.SplitN(line, "\t", 2)
|
|
if len(tabParts) < 2 {
|
|
continue
|
|
}
|
|
value := tabParts[1]
|
|
switch strings.TrimRight(tabParts[0], ":") {
|
|
case "Uid":
|
|
p.uids = make([]int32, 0, 4)
|
|
for _, i := range strings.Split(value, "\t") {
|
|
v, err := strconv.ParseInt(i, 10, 32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.uids = append(p.uids, int32(v))
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getProcInodesAll(root string, max int) (map[string][]inodeMap, error) {
|
|
pids, err := Pids()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret := make(map[string][]inodeMap)
|
|
|
|
for _, pid := range pids {
|
|
t, err := getProcInodes(root, pid, max)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
if len(t) == 0 {
|
|
continue
|
|
}
|
|
// TODO: update ret.
|
|
ret = updateMap(ret, t)
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
// decodeAddress decode addresse represents addr in proc/net/*
|
|
// ex:
|
|
// "0500000A:0016" -> "10.0.0.5", 22
|
|
// "0085002452100113070057A13F025401:0035" -> "2400:8500:1301:1052:a157:7:154:23f", 53
|
|
func decodeAddress(family uint32, src string) (Addr, error) {
|
|
t := strings.Split(src, ":")
|
|
if len(t) != 2 {
|
|
return Addr{}, fmt.Errorf("does not contain port, %s", src)
|
|
}
|
|
addr := t[0]
|
|
port, err := strconv.ParseInt("0x"+t[1], 0, 64)
|
|
if err != nil {
|
|
return Addr{}, fmt.Errorf("invalid port, %s", src)
|
|
}
|
|
decoded, err := hex.DecodeString(addr)
|
|
if err != nil {
|
|
return Addr{}, fmt.Errorf("decode error, %s", err)
|
|
}
|
|
var ip net.IP
|
|
// Assumes this is little_endian
|
|
if family == syscall.AF_INET {
|
|
ip = net.IP(Reverse(decoded))
|
|
} else { // IPv6
|
|
ip, err = parseIPv6HexString(decoded)
|
|
if err != nil {
|
|
return Addr{}, err
|
|
}
|
|
}
|
|
return Addr{
|
|
IP: ip.String(),
|
|
Port: uint32(port),
|
|
}, nil
|
|
}
|
|
|
|
// Reverse reverses array of bytes.
|
|
func Reverse(s []byte) []byte {
|
|
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
return s
|
|
}
|
|
|
|
// parseIPv6HexString parse array of bytes to IPv6 string
|
|
func parseIPv6HexString(src []byte) (net.IP, error) {
|
|
if len(src) != 16 {
|
|
return nil, fmt.Errorf("invalid IPv6 string")
|
|
}
|
|
|
|
buf := make([]byte, 0, 16)
|
|
for i := 0; i < len(src); i += 4 {
|
|
r := Reverse(src[i : i+4])
|
|
buf = append(buf, r...)
|
|
}
|
|
return net.IP(buf), nil
|
|
}
|
|
|
|
func processInet(file string, kind netConnectionKindType, inodes map[string][]inodeMap, filterPid int32) ([]connTmp, error) {
|
|
|
|
if strings.HasSuffix(file, "6") && !common.PathExists(file) {
|
|
// IPv6 not supported, return empty.
|
|
return []connTmp{}, nil
|
|
}
|
|
lines, err := common.ReadLines(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var ret []connTmp
|
|
// skip first line
|
|
for _, line := range lines[1:] {
|
|
l := strings.Fields(line)
|
|
if len(l) < 10 {
|
|
continue
|
|
}
|
|
laddr := l[1]
|
|
raddr := l[2]
|
|
status := l[3]
|
|
inode := l[9]
|
|
pid := int32(0)
|
|
fd := uint32(0)
|
|
i, exists := inodes[inode]
|
|
if exists {
|
|
pid = i[0].pid
|
|
fd = i[0].fd
|
|
}
|
|
if filterPid > 0 && filterPid != pid {
|
|
continue
|
|
}
|
|
if kind.sockType == syscall.SOCK_STREAM {
|
|
status = TCPStatuses[status]
|
|
} else {
|
|
status = "NONE"
|
|
}
|
|
la, err := decodeAddress(kind.family, laddr)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
ra, err := decodeAddress(kind.family, raddr)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
ret = append(ret, connTmp{
|
|
fd: fd,
|
|
family: kind.family,
|
|
sockType: kind.sockType,
|
|
laddr: la,
|
|
raddr: ra,
|
|
status: status,
|
|
pid: pid,
|
|
})
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func processUnix(file string, kind netConnectionKindType, inodes map[string][]inodeMap, filterPid int32) ([]connTmp, error) {
|
|
lines, err := common.ReadLines(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var ret []connTmp
|
|
// skip first line
|
|
for _, line := range lines[1:] {
|
|
tokens := strings.Fields(line)
|
|
if len(tokens) < 6 {
|
|
continue
|
|
}
|
|
st, err := strconv.Atoi(tokens[4])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
inode := tokens[6]
|
|
|
|
var pairs []inodeMap
|
|
pairs, exists := inodes[inode]
|
|
if !exists {
|
|
pairs = []inodeMap{
|
|
inodeMap{},
|
|
}
|
|
}
|
|
for _, pair := range pairs {
|
|
if filterPid > 0 && filterPid != pair.pid {
|
|
continue
|
|
}
|
|
var path string
|
|
if len(tokens) == 8 {
|
|
path = tokens[len(tokens)-1]
|
|
}
|
|
ret = append(ret, connTmp{
|
|
fd: pair.fd,
|
|
family: kind.family,
|
|
sockType: uint32(st),
|
|
laddr: Addr{
|
|
IP: path,
|
|
},
|
|
pid: pair.pid,
|
|
status: "NONE",
|
|
path: path,
|
|
})
|
|
}
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func updateMap(src map[string][]inodeMap, add map[string][]inodeMap) map[string][]inodeMap {
|
|
for key, value := range add {
|
|
a, exists := src[key]
|
|
if !exists {
|
|
src[key] = value
|
|
continue
|
|
}
|
|
src[key] = append(a, value...)
|
|
}
|
|
return src
|
|
}
|