From 3b9d7b48e86078b8be075a62dc877b0184af3a68 Mon Sep 17 00:00:00 2001 From: Gabe Cook Date: Mon, 7 Oct 2024 05:06:05 -0500 Subject: [PATCH] feat(ifdata): Add statistics flags on Linux and Darwin --- README.md | 2 +- cmd/ifdata/cmd.go | 154 ++++++++++++++----------- cmd/ifdata/statistics_darwin.go | 161 +++++++++++++++++++++++++++ cmd/ifdata/statistics_linux.go | 128 +++++++++++++++++++++ cmd/ifdata/statistics_unsupported.go | 15 +++ cmd/ifdata/usage.go | 48 ++++++-- docs/ifdata.md | 43 +++++-- go.mod | 1 + go.sum | 4 + internal/generate/docs/main.go | 38 +++++++ 10 files changed, 505 insertions(+), 89 deletions(-) create mode 100644 cmd/ifdata/statistics_darwin.go create mode 100644 cmd/ifdata/statistics_linux.go create mode 100644 cmd/ifdata/statistics_unsupported.go diff --git a/README.md b/README.md index cb36616..6eb015a 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ My goal is 100% compatability, but there are currently some differences compared | Applet | Differences | |--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **errno** | The original implementation prints some errors which are not used by the current OS. For example, code `35` could match `EAGAIN` and `EWOULDBLOCK`. Most operating systems only use one or the other, so this implementation does not print duplicates. | -| **ifdata** | The `-ph` and `-pf` flags now support all operating systems, but the other Linux-specific flags are not currently supported. | +| **ifdata** | The rewrite supports the `-ph` and `-pf` flags on all operating systems, and all statistics flags on Linux and Darwin. | | **isutf8** | Unlike moreutils, which prints the expected value range for non-UTF-8 files, the rewrite only logs the offending line, byte, and char. | | **lckdo** | Deprecated in moreutils and intentionally not implemented here. It is recommended to use `flock` as a replacement. | | **parallel** | Parallel is not symlinked by default since [GNU Parallel](https://www.gnu.org/software/parallel/) is typically preferred. | diff --git a/cmd/ifdata/cmd.go b/cmd/ifdata/cmd.go index d9648a7..8d64bf0 100644 --- a/cmd/ifdata/cmd.go +++ b/cmd/ifdata/cmd.go @@ -3,60 +3,60 @@ package ifdata import ( "errors" "fmt" + "html/template" "net" "strings" "github.com/gabe565/moreutils/internal/cmdutil" - "github.com/gabe565/moreutils/internal/util" "github.com/spf13/cobra" ) const ( Name = "ifdata" - FlagPrint = "print" - FlagExists = "exists" - FlagAddress = "address" - FlagNetmask = "netmask" - FlagNetworkAddress = "network-address" - FlagBroadcastAddress = "broadcast-address" - FlagMTU = "mtu" - FlagFlags = "flags" - FlagHardwareAddress = "hardware-addr" + FlagExists = "e" + FlagPrint = "p" + FlagPrintExists = "pe" + FlagAddress = "pa" + FlagNetmask = "pn" + FlagNetworkAddress = "pN" + FlagBroadcastAddress = "pb" + FlagMTU = "pm" + FlagFlags = "pf" + FlagHardwareAddress = "ph" + + FlagInputStatistics = "si" + FlagInputPackets = "sip" + FlagInputBytes = "sib" + FlagInputErrors = "sie" + FlagInputDropped = "sid" + FlagInputFIFO = "sif" + FlagInputCompressed = "sic" + FlagInputMulticast = "sim" + FlagInputBytesSecond = "bips" + + FlagOutputStatistics = "so" + FlagOutputPackets = "sop" + FlagOutputBytes = "sob" + FlagOutputErrors = "soe" + FlagOutputDropped = "sod" + FlagOutputFIFO = "sof" + FlagOutputCollisions = "sox" + FlagOutputCarrierLosses = "soc" + FlagOutputMulticast = "som" + FlagOutputBytesSecond = "bops" ) func New(opts ...cmdutil.Option) *cobra.Command { cmd := &cobra.Command{ - Use: Name + " interface", + Use: Name + " [flags] interface", Short: "Get network interface info without parsing ifconfig output", RunE: run, GroupID: cmdutil.Applet, } cmd.SetUsageFunc(usageFunc) - - cmd.Flags().Bool("help", false, "Print usage") - cmd.Flags().Lookup("help").Hidden = true - - cmd.Flags().BoolP(FlagPrint, "p", false, "Prints out the whole configuration of the interface") - cmd.Flags().BoolP(FlagExists, "e", false, "Test to see if the interface exists, exit nonzero if it does not") - cmd.Flags().BoolP(FlagAddress, "a", false, "Prints the IPv4 address of the interface") - cmd.Flags().BoolP(FlagNetmask, "n", false, "Prints the netmask of the interface") - cmd.Flags().BoolP(FlagNetworkAddress, "N", false, "Prints the network address of the interface") - cmd.Flags().BoolP(FlagBroadcastAddress, "b", false, "Prints the broadcast address of the interface") - cmd.Flags().BoolP(FlagMTU, "m", false, "Prints the MTU of the interface") - cmd.Flags().BoolP(FlagFlags, "f", false, "Prints the flags of the interface") - cmd.Flags().BoolP(FlagHardwareAddress, "h", false, "Prints the hardware address of the interface. Exit with a failure exit code if there is not hardware address for the given network interface") - - cmd.MarkFlagsMutuallyExclusive( - FlagExists, - FlagAddress, - FlagNetmask, - FlagNetworkAddress, - FlagBroadcastAddress, - FlagMTU, - FlagFlags, - FlagHardwareAddress, - ) + cmd.DisableFlagsInUseLine = true + cmd.DisableFlagParsing = true for _, opt := range opts { opt(cmd) @@ -64,31 +64,51 @@ func New(opts ...cmdutil.Option) *cobra.Command { return cmd } -var ErrNoOperation = errors.New("no operation was provided") +var ( + ErrNoFormatter = errors.New("no formatter was provided") + ErrUnknownFormatter = errors.New("unknown formatter") + ErrNoInterface = errors.New("no interface was provided") + ErrInterfaceMissing = errors.New("interface missing from /proc/net/dev") + ErrStatisticsUnsupported = errors.New("platform does not support interface statistics") +) func run(cmd *cobra.Command, args []string) error { - printFlag := util.Must2(cmd.Flags().GetBool(FlagPrint)) - exists := util.Must2(cmd.Flags().GetBool(FlagExists)) - address := util.Must2(cmd.Flags().GetBool(FlagAddress)) - netmask := util.Must2(cmd.Flags().GetBool(FlagNetmask)) - networkAddress := util.Must2(cmd.Flags().GetBool(FlagNetworkAddress)) - broadcastAddress := util.Must2(cmd.Flags().GetBool(FlagBroadcastAddress)) - mtu := util.Must2(cmd.Flags().GetBool(FlagMTU)) - flags := util.Must2(cmd.Flags().GetBool(FlagFlags)) - hardwareAddress := util.Must2(cmd.Flags().GetBool(FlagHardwareAddress)) - if len(args) == 0 { - // Error is suppressed when "-h" is provided with no flags - if hardwareAddress { + return cmd.Usage() + } + + var op, name string + for _, arg := range args { + switch { + case arg == "-h", arg == "--help": return cmd.Usage() + case arg == "-v", arg == "--version": + if cmd.Version != "" { + tmpl, err := template.New("").Parse(cmd.VersionTemplate()) + if err != nil { + panic(err) + } + + return tmpl.Execute(cmd.OutOrStdout(), cmd) + } + case strings.HasPrefix(arg, "-"): + op = strings.TrimPrefix(arg, "-") + default: + name = arg } - return cobra.ExactArgs(1)(cmd, args) + } + + switch { + case op == "": + return ErrNoFormatter + case name == "": + return ErrNoInterface } cmd.SilenceUsage = true - iface, err := net.InterfaceByName(args[0]) + iface, err := net.InterfaceByName(name) - if printFlag && exists { + if op == FlagPrintExists { if err != nil { _, _ = fmt.Fprintln(cmd.OutOrStdout(), "no") } else { @@ -98,23 +118,23 @@ func run(cmd *cobra.Command, args []string) error { } if err != nil { - return fmt.Errorf("%w: %s", err, args[0]) + return fmt.Errorf("%w: %s", err, name) } - if exists { + if op == FlagExists { return nil } - switch { - case mtu: + switch op { + case FlagMTU: _, _ = fmt.Fprintln(cmd.OutOrStdout(), iface.MTU) - case flags: + case FlagFlags: for _, flag := range strings.Split(iface.Flags.String(), "|") { _, _ = fmt.Fprintln(cmd.OutOrStdout(), flag) } - case hardwareAddress: + case FlagHardwareAddress: _, _ = fmt.Fprintln(cmd.OutOrStdout(), strings.ToUpper(iface.HardwareAddr.String())) - case address, netmask, networkAddress, broadcastAddress, printFlag: + case FlagAddress, FlagNetmask, FlagNetworkAddress, FlagBroadcastAddress, FlagPrint: addrs, err := iface.Addrs() if err != nil { return err @@ -122,16 +142,16 @@ func run(cmd *cobra.Command, args []string) error { for _, addr := range addrs { if addr, ok := addr.(*net.IPNet); ok && addr.IP.To4() != nil { - switch { - case address: + switch op { + case FlagAddress: _, _ = fmt.Fprintln(cmd.OutOrStdout(), addr.IP) - case netmask: + case FlagNetmask: _, _ = fmt.Fprintln(cmd.OutOrStdout(), net.IP(addr.Mask)) - case networkAddress: + case FlagNetworkAddress: _, _ = fmt.Fprintln(cmd.OutOrStdout(), addr.IP.Mask(addr.Mask)) - case broadcastAddress: + case FlagBroadcastAddress: _, _ = fmt.Fprintln(cmd.OutOrStdout(), getBroadcastAddr(addr)) - case printFlag: + case FlagPrint: _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s %s %s %d\n", addr.IP, @@ -143,8 +163,12 @@ func run(cmd *cobra.Command, args []string) error { } } default: + if statisticsSupported { + return statistics(cmd, op, iface) + } + cmd.SilenceUsage = false - return ErrNoOperation + return fmt.Errorf("%w: -%s", ErrUnknownFormatter, op) } return nil diff --git a/cmd/ifdata/statistics_darwin.go b/cmd/ifdata/statistics_darwin.go new file mode 100644 index 0000000..35b05b8 --- /dev/null +++ b/cmd/ifdata/statistics_darwin.go @@ -0,0 +1,161 @@ +package ifdata + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + "time" + + "github.com/spf13/cobra" + "golang.org/x/sys/unix" +) + +const statisticsSupported = true + +func statistics(cmd *cobra.Command, op string, iface *net.Interface) error { + handle := statsHandler(op) + if handle == nil { + cmd.SilenceUsage = false + return fmt.Errorf("%w: -%s", ErrUnknownFormatter, op) + } + + data, err := getIfaceData(iface.Index) + if err != nil { + return err + } + + _, err = handle(cmd.OutOrStdout(), data) + return err +} + +func statsHandler(op string) func(w io.Writer, d *ifMsghdr2) (int, error) { + switch op { + case FlagInputStatistics: + return func(w io.Writer, d *ifMsghdr2) (int, error) { + return fmt.Fprintf(w, "%d %d %d %d %d %d %d %d\n", + d.Data.Ibytes, d.Data.Ipackets, + d.Data.Ierrors, d.Data.Iqdrops, + 0, 0, + 0, d.Data.Imcasts, + ) + } + case FlagInputPackets: + return func(w io.Writer, d *ifMsghdr2) (int, error) { return fmt.Fprintln(w, d.Data.Ipackets) } + case FlagInputBytes: + return func(w io.Writer, d *ifMsghdr2) (int, error) { return fmt.Fprintln(w, d.Data.Ibytes) } + case FlagInputErrors: + return func(w io.Writer, d *ifMsghdr2) (int, error) { return fmt.Fprintln(w, d.Data.Ierrors) } + case FlagInputDropped: + return func(w io.Writer, d *ifMsghdr2) (int, error) { return fmt.Fprintln(w, d.Data.Iqdrops) } + case FlagInputMulticast: + return func(w io.Writer, d *ifMsghdr2) (int, error) { return fmt.Fprintln(w, d.Data.Imcasts) } + case FlagInputFIFO, FlagInputCompressed: + return func(w io.Writer, _ *ifMsghdr2) (int, error) { return fmt.Fprintln(w, 0) } + case FlagInputBytesSecond: + return func(w io.Writer, d *ifMsghdr2) (int, error) { + time.Sleep(time.Second) + + d2, err := getIfaceData(int(d.Index)) + if err != nil { + return 0, err + } + + return fmt.Fprintln(w, d2.Data.Ibytes-d.Data.Ibytes) + } + + case FlagOutputStatistics: + return func(w io.Writer, d *ifMsghdr2) (int, error) { + return fmt.Fprintf(w, "%d %d %d %d %d %d %d %d\n", + d.Data.Obytes, d.Data.Opackets, + d.Data.Oerrors, 0, + 0, d.Data.Collisions, + 0, d.Data.Omcasts, + ) + } + case FlagOutputPackets: + return func(w io.Writer, d *ifMsghdr2) (int, error) { return fmt.Fprintln(w, d.Data.Opackets) } + case FlagOutputBytes: + return func(w io.Writer, d *ifMsghdr2) (int, error) { return fmt.Fprintln(w, d.Data.Obytes) } + case FlagOutputErrors: + return func(w io.Writer, d *ifMsghdr2) (int, error) { return fmt.Fprintln(w, d.Data.Oerrors) } + case FlagOutputCollisions: + return func(w io.Writer, d *ifMsghdr2) (int, error) { return fmt.Fprintln(w, d.Data.Collisions) } + case FlagOutputMulticast: + return func(w io.Writer, d *ifMsghdr2) (int, error) { return fmt.Fprintln(w, d.Data.Omcasts) } + case FlagOutputDropped, FlagOutputFIFO, FlagOutputCarrierLosses: + return func(w io.Writer, _ *ifMsghdr2) (int, error) { return fmt.Fprintln(w, 0) } + case FlagOutputBytesSecond: + return func(w io.Writer, d *ifMsghdr2) (int, error) { + time.Sleep(time.Second) + + d2, err := getIfaceData(int(d.Index)) + if err != nil { + return 0, err + } + + return fmt.Fprintln(w, d2.Data.Obytes-d.Data.Obytes) + } + + default: + return nil + } +} + +// From https://github.com/prometheus/node_exporter/blob/master/collector/netdev_darwin.go + +func getIfaceData(index int) (*ifMsghdr2, error) { + var data ifMsghdr2 + rawData, err := unix.SysctlRaw("net", unix.AF_ROUTE, 0, 0, unix.NET_RT_IFLIST2, index) + if err != nil { + return nil, err + } + + err = binary.Read(bytes.NewReader(rawData), binary.LittleEndian, &data) + return &data, err +} + +type ifMsghdr2 struct { + Msglen uint16 + Version uint8 + Type uint8 + Addrs int32 + Flags int32 + Index uint16 + _ [2]byte + SndLen int32 + SndMaxlen int32 + SndDrops int32 + Timer int32 + Data ifData64 +} + +// https://github.com/apple/darwin-xnu/blob/main/bsd/net/if_var.h#L199-L231 +type ifData64 struct { + Type uint8 + Typelen uint8 + Physical uint8 + Addrlen uint8 + Hdrlen uint8 + Recvquota uint8 + Xmitquota uint8 + Unused1 uint8 + Mtu uint32 + Metric uint32 + Baudrate uint64 + Ipackets uint64 + Ierrors uint64 + Opackets uint64 + Oerrors uint64 + Collisions uint64 + Ibytes uint64 + Obytes uint64 + Imcasts uint64 + Omcasts uint64 + Iqdrops uint64 + Noproto uint64 + Recvtiming uint32 + Xmittiming uint32 + Lastchange unix.Timeval32 +} diff --git a/cmd/ifdata/statistics_linux.go b/cmd/ifdata/statistics_linux.go new file mode 100644 index 0000000..db470ad --- /dev/null +++ b/cmd/ifdata/statistics_linux.go @@ -0,0 +1,128 @@ +package ifdata + +import ( + "fmt" + "io" + "net" + "time" + + "github.com/prometheus/procfs" + "github.com/spf13/cobra" +) + +const statisticsSupported = true + +func statistics(cmd *cobra.Command, op string, iface *net.Interface) error { + handle := statsHandler(op) + if handle == nil { + cmd.SilenceUsage = false + return fmt.Errorf("%w: -%s", ErrUnknownFormatter, op) + } + + device, err := getNetDevLine(iface.Name) + if err != nil { + return err + } + + _, err = handle(cmd.OutOrStdout(), device) + return err +} + +func getNetDevLine(name string) (*procfs.NetDevLine, error) { + fs, err := procfs.NewDefaultFS() + if err != nil { + return nil, err + } + + entries, err := fs.NetDev() + if err != nil { + return nil, err + } + + for _, entry := range entries { + if entry.Name == name { + return &entry, nil + } + } + + return nil, fmt.Errorf("%w: %s", ErrInterfaceMissing, name) +} + +func statsHandler(op string) func(w io.Writer, d *procfs.NetDevLine) (int, error) { + switch op { + case FlagInputStatistics: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { + return fmt.Fprintf(w, "%d %d %d %d %d %d %d %d\n", + d.RxBytes, d.RxPackets, + d.RxErrors, d.RxDropped, + d.RxFIFO, d.RxFrame, + d.RxCompressed, d.RxMulticast, + ) + } + case FlagInputPackets: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { return fmt.Fprintln(w, d.RxPackets) } + case FlagInputBytes: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { return fmt.Fprintln(w, d.RxBytes) } + case FlagInputErrors: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { return fmt.Fprintln(w, d.RxErrors) } + case FlagInputDropped: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { return fmt.Fprintln(w, d.RxDropped) } + case FlagInputFIFO: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { return fmt.Fprintln(w, d.RxFIFO) } + case FlagInputCompressed: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { return fmt.Fprintln(w, d.RxCompressed) } + case FlagInputMulticast: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { return fmt.Fprintln(w, d.RxMulticast) } + case FlagInputBytesSecond: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { + time.Sleep(time.Second) + + d2, err := getNetDevLine(d.Name) + if err != nil { + return 0, err + } + + return fmt.Fprintln(w, d2.RxBytes-d.RxBytes) + } + + case FlagOutputStatistics: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { + return fmt.Fprintf(w, "%d %d %d %d %d %d %d %d\n", + d.TxBytes, d.TxPackets, + d.TxErrors, d.TxDropped, + d.TxFIFO, d.TxCollisions, + d.TxCarrier, 0, + ) + } + case FlagOutputPackets: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { return fmt.Fprintln(w, d.TxPackets) } + case FlagOutputBytes: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { return fmt.Fprintln(w, d.TxBytes) } + case FlagOutputErrors: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { return fmt.Fprintln(w, d.TxErrors) } + case FlagOutputDropped: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { return fmt.Fprintln(w, d.TxDropped) } + case FlagOutputFIFO: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { return fmt.Fprintln(w, d.TxFIFO) } + case FlagOutputCollisions: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { return fmt.Fprintln(w, d.TxCollisions) } + case FlagOutputCarrierLosses: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { return fmt.Fprintln(w, d.TxCarrier) } + case FlagOutputMulticast: + return func(w io.Writer, _ *procfs.NetDevLine) (int, error) { return fmt.Fprintln(w, 0) } + case FlagOutputBytesSecond: + return func(w io.Writer, d *procfs.NetDevLine) (int, error) { + time.Sleep(time.Second) + + d2, err := getNetDevLine(d.Name) + if err != nil { + return 0, err + } + + return fmt.Fprintln(w, d2.TxBytes-d.TxBytes) + } + + default: + return nil + } +} diff --git a/cmd/ifdata/statistics_unsupported.go b/cmd/ifdata/statistics_unsupported.go new file mode 100644 index 0000000..05eedf3 --- /dev/null +++ b/cmd/ifdata/statistics_unsupported.go @@ -0,0 +1,15 @@ +//go:build !(linux || darwin) + +package ifdata + +import ( + "net" + + "github.com/spf13/cobra" +) + +const statisticsSupported = false + +func statistics(_ *cobra.Command, _ string, _ *net.Interface) error { + return ErrStatisticsUnsupported +} diff --git a/cmd/ifdata/usage.go b/cmd/ifdata/usage.go index 9ce21b5..f722a5c 100644 --- a/cmd/ifdata/usage.go +++ b/cmd/ifdata/usage.go @@ -7,17 +7,38 @@ import ( ) const Usage = ` - -h help for ` + Name + ` - -e Test to see if the interface exists, exit nonzero if it does not - -p Prints out the whole configuration of the interface - -pe Prints "yes" or "no" if the interface exists or not. - -pa Prints the IP address of the interface - -pn Prints the netmask of the interface - -pN Prints the network address of the interface - -pb Prints the broadcast address of the interface - -pm Prints the MTU of the interface - -ph Prints the hardware address of the interface. Exit with a failure exit code if there is not hardware address for the given network interface - -pf Prints the flags of the interface` + -h help for ` + Name + ` + -e Test to see if the interface exists, exit nonzero if it does not + -p Prints out the whole configuration of the interface + -pe Prints "yes" or "no" if the interface exists or not. + -pa Prints the IP address of the interface + -pn Prints the netmask of the interface + -pN Prints the network address of the interface + -pb Prints the broadcast address of the interface + -pm Prints the MTU of the interface + -ph Prints the hardware address of the interface. Exit with a failure exit code if there is not hardware address for the given network interface + -pf Prints the flags of the interface` + +const UsageStatistics = ` + -si Prints all input statistics of the interface + -sip Prints the number of input packets + -sib Prints the number of input bytes + -sie Prints the number of input errors + -sid Prints the number of dropped input packets + -sif Prints the number of input fifo overruns + -sic Prints the number of compressed input packets + -sim Prints the number of input multicast packets + -so Prints all output statistics of the interface + -sop Prints the number of output packets + -sob Prints the number of output bytes + -soe Prints the number of output errors + -sod Prints the number of dropped output packets + -sof Prints the number of output fifo overruns + -sox Prints the number of output collisions + -soc Prints the number of output carrier losses + -som Prints the number of output multicast packets + -bips Prints the number of bytes of incoming traffic measured in one second + -bops Prints the number of bytes of outgoing traffic measured in one second` func usageFunc(cmd *cobra.Command) error { tmpl := cmd.UsageTemplate() @@ -37,8 +58,11 @@ func usageFunc(cmd *cobra.Command) error { endIdx += flagsIdx newUsage := Usage + if statisticsSupported { + newUsage += UsageStatistics + } if f := cmd.Flags().Lookup("version"); f != nil { - newUsage += "\n -" + f.Shorthand + " " + f.Usage + newUsage += "\n -" + f.Shorthand + " " + f.Usage } tmpl = tmpl[:flagsIdx] + newUsage + tmpl[endIdx:] diff --git a/docs/ifdata.md b/docs/ifdata.md index 8b2fac0..37726d0 100644 --- a/docs/ifdata.md +++ b/docs/ifdata.md @@ -3,22 +3,43 @@ Get network interface info without parsing ifconfig output ``` -ifdata interface [flags] +ifdata [flags] interface ``` ### Options ``` - -a, --address Prints the IPv4 address of the interface - -b, --broadcast-address Prints the broadcast address of the interface - -e, --exists Test to see if the interface exists, exit nonzero if it does not - -f, --flags Prints the flags of the interface - -h, --hardware-addr Prints the hardware address of the interface. Exit with a failure exit code if there is not hardware address for the given network interface - -m, --mtu Prints the MTU of the interface - -n, --netmask Prints the netmask of the interface - -N, --network-address Prints the network address of the interface - -p, --print Prints out the whole configuration of the interface - -v, --version version for ifdata + -h help for ifdata + -e Test to see if the interface exists, exit nonzero if it does not + -p Prints out the whole configuration of the interface + -pe Prints "yes" or "no" if the interface exists or not. + -pa Prints the IP address of the interface + -pn Prints the netmask of the interface + -pN Prints the network address of the interface + -pb Prints the broadcast address of the interface + -pm Prints the MTU of the interface + -ph Prints the hardware address of the interface. Exit with a failure exit code if there is not hardware address for the given network interface + -pf Prints the flags of the interface + -si Prints all input statistics of the interface + -sip Prints the number of input packets + -sib Prints the number of input bytes + -sie Prints the number of input errors + -sid Prints the number of dropped input packets + -sif Prints the number of input fifo overruns + -sic Prints the number of compressed input packets + -sim Prints the number of input multicast packets + -so Prints all output statistics of the interface + -sop Prints the number of output packets + -sob Prints the number of output bytes + -soe Prints the number of output errors + -sod Prints the number of dropped output packets + -sof Prints the number of output fifo overruns + -sox Prints the number of output collisions + -soc Prints the number of output carrier losses + -som Prints the number of output multicast packets + -bips Prints the number of bytes of incoming traffic measured in one second + -bops Prints the number of bytes of outgoing traffic measured in one second + -v version for ifdata ``` ### SEE ALSO diff --git a/go.mod b/go.mod index 3ad9381..2a22dd2 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/lestrrat-go/strftime v1.1.0 github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-tty v0.0.7 + github.com/prometheus/procfs v0.15.1 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 diff --git a/go.sum b/go.sum index 9b5206d..056cb80 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabe565/gravwell/v3 v3.0.0-20240927080051-af89d2bfd634 h1:2zl0NRR8r/Zf8k09pGV63J0b9XUiMDXcvwZGA2iG+/w= github.com/gabe565/gravwell/v3 v3.0.0-20240927080051-af89d2bfd634/go.mod h1:qD6CCGXrtpgeguxmIpBBFwKUMgewTrCTggn3uGqyA1k= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -18,6 +20,8 @@ github.com/mattn/go-tty v0.0.7 h1:KJ486B6qI8+wBO7kQxYgmmEFDaFEE96JMBQ7h400N8Q= github.com/mattn/go-tty v0.0.7/go.mod h1:f2i5ZOvXBU/tCABmLmOfzLz9azMo5wdAaElRNnJKr+k= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= diff --git a/internal/generate/docs/main.go b/internal/generate/docs/main.go index aa17cb7..c7fb58d 100644 --- a/internal/generate/docs/main.go +++ b/internal/generate/docs/main.go @@ -7,8 +7,10 @@ import ( "os" "path/filepath" "slices" + "strings" "github.com/gabe565/moreutils/cmd" + "github.com/gabe565/moreutils/cmd/ifdata" "github.com/gabe565/moreutils/internal/cmdutil" "github.com/gabe565/moreutils/internal/cmdutil/subcommands" "github.com/gabe565/moreutils/internal/generate/seealsoreplacer" @@ -62,6 +64,13 @@ func main() { w = seealsoreplacer.New(w, "### SEE ALSO\n", subcommands.All()) } + if subCmd.Name() == ifdata.Name { + w = optionsReplacer{ + w: w, + replace: ifdata.Usage + ifdata.UsageStatistics + "\n -v version for " + ifdata.Name, + } + } + if err := doc.GenMarkdown(subCmd, w); err != nil { panic(err) } @@ -109,3 +118,32 @@ func main() { panic(err) } } + +type optionsReplacer struct { + w io.Writer + replace string +} + +func (o optionsReplacer) Write(p []byte) (int, error) { + if !strings.HasSuffix(o.replace, "\n") { + o.replace += "\n" + } + + const header, footer = "\n### Options\n\n```", "```" + if bytes.Contains(p, []byte(header)) { + beforeIdx := bytes.Index(p, []byte(header)) + if beforeIdx == -1 { + panic("missing header: " + header) + } + + afterIdx := bytes.Index(p[beforeIdx+len(header):], []byte(footer)) + if afterIdx == -1 { + panic("missing footer: " + footer) + } + afterIdx += beforeIdx + len(header) + + _, err := o.w.Write(slices.Concat(p[:beforeIdx], []byte(header), []byte(o.replace), p[afterIdx:])) + return len(p), err + } + return o.w.Write(p) +}