From 370d97e764c4367bb94dd3bfda73f62568f0cd02 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Chigot?= <clement.chigot@atos.net>
Date: Mon, 27 Jan 2020 15:06:25 +0100
Subject: [PATCH 2/2] net: add AIX support

---
 net/net_aix.go      | 332 ++++++++++++++++++++++++++++++++++++++++++++
 net/net_fallback.go |   2 +-
 2 files changed, 341 insertions(+), 1 deletion(-)
 create mode 100644 net/net_aix.go

diff --git a/net/net_aix.go b/net/net_aix.go
new file mode 100644
index 0000000..fe16a34
--- /dev/null
+++ b/net/net_aix.go
@@ -0,0 +1,332 @@
+// +build aix
+
+package net
+
+import (
+	"context"
+	"fmt"
+	"os/exec"
+	"strconv"
+	"strings"
+	"syscall"
+
+	"github.com/shirou/gopsutil/internal/common"
+)
+
+func parseNetstatI(output string) ([]IOCountersStat, error) {
+	lines := strings.Split(string(output), "\n")
+	ret := make([]IOCountersStat, 0, len(lines)-1)
+	exists := make([]string, 0, len(ret))
+
+	// Check first line is header
+	if len(lines) > 0 && strings.Fields(lines[0])[0] != "Name" {
+		return nil, fmt.Errorf("not a 'netstat -i' output")
+	}
+
+	for _, line := range lines[1:] {
+		values := strings.Fields(line)
+		if len(values) < 1 || values[0] == "Name" {
+			continue
+		}
+		if common.StringsHas(exists, values[0]) {
+			// skip if already get
+			continue
+		}
+		exists = append(exists, values[0])
+
+		if len(values) < 9 {
+			continue
+		}
+
+		base := 1
+		// sometimes Address is omitted
+		if len(values) < 10 {
+			base = 0
+		}
+
+		parsed := make([]uint64, 0, 5)
+		vv := []string{
+			values[base+3], // Ipkts == PacketsRecv
+			values[base+4], // Ierrs == Errin
+			values[base+5], // Opkts == PacketsSent
+			values[base+6], // Oerrs == Errout
+			values[base+8], // Drops == Dropout
+		}
+
+		for _, target := range vv {
+			if target == "-" {
+				parsed = append(parsed, 0)
+				continue
+			}
+
+			t, err := strconv.ParseUint(target, 10, 64)
+			if err != nil {
+				return nil, err
+			}
+			parsed = append(parsed, t)
+		}
+
+		n := IOCountersStat{
+			Name:        values[0],
+			PacketsRecv: parsed[0],
+			Errin:       parsed[1],
+			PacketsSent: parsed[2],
+			Errout:      parsed[3],
+			Dropout:     parsed[4],
+		}
+		ret = append(ret, n)
+	}
+	return ret, nil
+}
+
+func IOCounters(pernic bool) ([]IOCountersStat, error) {
+	return IOCountersWithContext(context.Background(), pernic)
+}
+
+func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) {
+	netstat, err := exec.LookPath("netstat")
+	if err != nil {
+		return nil, err
+	}
+	out, err := invoke.CommandWithContext(ctx, netstat, "-idn")
+	if err != nil {
+		return nil, err
+	}
+
+	iocounters, err := parseNetstatI(string(out))
+	if err != nil {
+		return nil, err
+	}
+	if pernic == false {
+		return getIOCountersAll(iocounters)
+	}
+	return iocounters, nil
+
+}
+
+// NetIOCountersByFile is an method which is added just a compatibility for linux.
+func IOCountersByFile(pernic bool, filename string) ([]IOCountersStat, error) {
+	return IOCountersByFileWithContext(context.Background(), pernic, filename)
+}
+
+func IOCountersByFileWithContext(ctx context.Context, pernic bool, filename string) ([]IOCountersStat, error) {
+	return IOCounters(pernic)
+}
+
+func FilterCounters() ([]FilterStat, error) {
+	return FilterCountersWithContext(context.Background())
+}
+
+func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
+	return nil, common.ErrNotImplementedError
+}
+
+func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) {
+	return ProtoCountersWithContext(context.Background(), protocols)
+}
+
+func ProtoCountersWithContext(ctx context.Context, protocols []string) ([]ProtoCountersStat, error) {
+	return nil, common.ErrNotImplementedError
+}
+
+func parseNetstatUnixLine(f []string) (ConnectionStat, error) {
+	if len(f) < 8 {
+		return ConnectionStat{}, fmt.Errorf("wrong number of fields: expected >=8 got %d", len(f))
+	}
+
+	var netType uint32
+
+	switch f[1] {
+	case "dgram":
+		netType = syscall.SOCK_DGRAM
+	case "stream":
+		netType = syscall.SOCK_STREAM
+	default:
+		return ConnectionStat{}, fmt.Errorf("unknown type: %s", f[1])
+	}
+
+	// Some Unix Socket don't have any address associated
+	addr := ""
+	if len(f) == 9 {
+		addr = f[8]
+	}
+
+	c := ConnectionStat{
+		Fd:     uint32(0), // not supported
+		Family: uint32(syscall.AF_UNIX),
+		Type:   uint32(netType),
+		Laddr: Addr{
+			IP: addr,
+		},
+		Status: "NONE",
+		Pid:    int32(0), // not supported
+	}
+
+	return c, nil
+}
+
+// Return true if proto is the corresponding to the kind parameter
+// Only for Inet lines
+func hasCorrectInetProto(kind, proto string) bool {
+	switch kind {
+	case "all", "inet":
+		return true
+	case "unix":
+		return false
+	case "inet4":
+		return !strings.HasSuffix(proto, "6")
+	case "inet6":
+		return strings.HasSuffix(proto, "6")
+	case "tcp":
+		return proto == "tcp" || proto == "tcp4" || proto == "tcp6"
+	case "tcp4":
+		return proto == "tcp" || proto == "tcp4"
+	case "tcp6":
+		return proto == "tcp6"
+	case "udp":
+		return proto == "udp" || proto == "udp4" || proto == "udp6"
+	case "udp4":
+		return proto == "udp" || proto == "udp4"
+	case "udp6":
+		return proto == "udp6"
+	}
+	return false
+}
+
+func parseNetstatA(output string, kind string) ([]ConnectionStat, error) {
+	var ret []ConnectionStat
+	lines := strings.Split(string(output), "\n")
+
+	for _, line := range lines {
+		fields := strings.Fields(line)
+		if len(fields) < 1 {
+			continue
+		}
+
+		if strings.HasPrefix(fields[0], "f1") {
+			// Unix lines
+			if len(fields) < 2 {
+				// every unix connections have two lines
+				continue
+			}
+
+			c, err := parseNetstatUnixLine(fields)
+			if err != nil {
+				return nil, fmt.Errorf("failed to parse Unix Address (%s): %s", line, err)
+			}
+
+			ret = append(ret, c)
+
+		} else if strings.HasPrefix(fields[0], "tcp") || strings.HasPrefix(fields[0], "udp") {
+			// Inet lines
+			if !hasCorrectInetProto(kind, fields[0]) {
+				continue
+			}
+
+			// On AIX, netstat display some connections with "*.*" as local addresses
+			// Skip them as they aren't real connections.
+			if fields[3] == "*.*" {
+				continue
+			}
+
+			c, err := parseNetstatNetLine(line)
+			if err != nil {
+				return nil, fmt.Errorf("failed to parse Inet Address (%s): %s", line, err)
+			}
+
+			ret = append(ret, c)
+		} else {
+			// Header lines
+			continue
+		}
+	}
+
+	return ret, nil
+
+}
+
+func Connections(kind string) ([]ConnectionStat, error) {
+	return ConnectionsWithContext(context.Background(), kind)
+}
+
+func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
+
+	args := []string{"-na"}
+	switch strings.ToLower(kind) {
+	default:
+		fallthrough
+	case "":
+		kind = "all"
+	case "all":
+		// nothing to add
+	case "inet", "inet4", "inet6":
+		args = append(args, "-finet")
+	case "tcp", "tcp4", "tcp6":
+		args = append(args, "-finet")
+	case "udp", "udp4", "udp6":
+		args = append(args, "-finet")
+	case "unix":
+		args = append(args, "-funix")
+	}
+
+	netstat, err := exec.LookPath("netstat")
+	if err != nil {
+		return nil, err
+	}
+	out, err := invoke.CommandWithContext(ctx, netstat, args...)
+
+	if err != nil {
+		return nil, err
+	}
+
+	ret, err := parseNetstatA(string(out), kind)
+	if err != nil {
+		return nil, err
+	}
+
+	return ret, nil
+
+}
+
+func ConnectionsMax(kind string, max int) ([]ConnectionStat, error) {
+	return ConnectionsMaxWithContext(context.Background(), kind, max)
+}
+
+func ConnectionsMaxWithContext(ctx context.Context, kind string, max int) ([]ConnectionStat, error) {
+	return []ConnectionStat{}, common.ErrNotImplementedError
+}
+
+// Return a list of network connections opened, omitting `Uids`.
+// WithoutUids functions are reliant on implementation details. They may be altered to be an alias for Connections or be
+// removed from the API in the future.
+func ConnectionsWithoutUids(kind string) ([]ConnectionStat, error) {
+	return ConnectionsWithoutUidsWithContext(context.Background(), kind)
+}
+
+func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
+	return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0)
+}
+
+func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, max int) ([]ConnectionStat, error) {
+	return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, max)
+}
+
+func ConnectionsPidWithoutUids(kind string, pid int32) ([]ConnectionStat, error) {
+	return ConnectionsPidWithoutUidsWithContext(context.Background(), kind, pid)
+}
+
+func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
+	return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0)
+}
+
+func ConnectionsPidMaxWithoutUids(kind string, pid int32, max int) ([]ConnectionStat, error) {
+	return ConnectionsPidMaxWithoutUidsWithContext(context.Background(), kind, pid, max)
+}
+
+func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) {
+	return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, max)
+}
+
+func connectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) {
+	return []ConnectionStat{}, common.ErrNotImplementedError
+}
diff --git a/net/net_fallback.go b/net/net_fallback.go
index 707b80f..7d9a265 100644
--- a/net/net_fallback.go
+++ b/net/net_fallback.go
@@ -1,4 +1,4 @@
-// +build !darwin,!linux,!freebsd,!openbsd,!windows
+// +build !aix,!darwin,!linux,!freebsd,!openbsd,!windows
 
 package net
 
-- 
2.22.0

