Skip to content

Commit

Permalink
Add a summary of stderr to the dialog displayed when tshark fails
Browse files Browse the repository at this point in the history
If a tshark process - run by termshark - returns a non-zero exit code,
termshark will send that code and an error string to a UI dialog. But
often this is not enough to help you diagnose the problem, and you have
to dig through termshark's log file for more info. This change adds a
summary of stderr to the error dialog displayed, specifically the first
two and last two lines read from the stderr of the failed tshark
process.
  • Loading branch information
gcla committed Jul 9, 2022
1 parent d72d5ad commit b9aefce
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 40 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

### Changed

- Added a summary of standard error to the error dialogs displayed when a tshark process fails to run
correctly (has a non-zero exit code).
- Fixed a race condition that caused extcap captures (e.g. randpkt) to sporadically fail.
- Dark-mode is now the default in the absence of a specific user-setting.
- Fixed a bug that caused mouse-clicks within the hex view to not function correctly if the viewport was not
Expand Down
6 changes: 1 addition & 5 deletions capinfo/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,7 @@ func (c *Loader) loadCapinfoAsync(pcapf string, app gowid.IApp, cb ICapinfoCallb
state = pcap.Terminated
if !c.SuppressErrors && err != nil {
if _, ok := err.(*exec.ExitError); ok {
cerr := gowid.WithKVs(termshark.BadCommand, map[string]interface{}{
"command": c.capinfoCmd.String(),
"error": err,
})
pcap.HandleError(pcap.CapinfoCode, app, cerr, cb)
pcap.HandleError(pcap.CapinfoCode, app, pcap.MakeUsefulError(c.capinfoCmd, err), cb)
}
}

Expand Down
2 changes: 2 additions & 0 deletions cmd/termshark/termshark.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/gcla/termshark/v2/configs/profiles"
"github.com/gcla/termshark/v2/convs"
"github.com/gcla/termshark/v2/pcap"
"github.com/gcla/termshark/v2/pkg/summary"
"github.com/gcla/termshark/v2/shark"
"github.com/gcla/termshark/v2/streams"
"github.com/gcla/termshark/v2/system"
Expand Down Expand Up @@ -62,6 +63,7 @@ func main() {
convs.Goroutinewg = &ensureGoroutinesStopWG
ui.Goroutinewg = &ensureGoroutinesStopWG
wormhole.Goroutinewg = &ensureGoroutinesStopWG
summary.Goroutinewg = &ensureGoroutinesStopWG

res := cmain()
ensureGoroutinesStopWG.Wait()
Expand Down
6 changes: 1 addition & 5 deletions convs/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,7 @@ func (c *Loader) loadConvAsync(pcapf string, convs []string, filter string, abs
state = pcap.Terminated
if !c.SuppressErrors && err != nil {
if _, ok := err.(*exec.ExitError); ok {
cerr := gowid.WithKVs(termshark.BadCommand, map[string]interface{}{
"command": c.convsCmd.String(),
"error": err,
})
pcap.HandleError(pcap.ConvCode, app, cerr, cb)
pcap.HandleError(pcap.ConvCode, app, pcap.MakeUsefulError(c.convsCmd, err), cb)
}
}

Expand Down
21 changes: 19 additions & 2 deletions pcap/cmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/gcla/termshark/v2"
"github.com/gcla/termshark/v2/configs/profiles"
"github.com/gcla/termshark/v2/pkg/summary"
"github.com/gcla/termshark/v2/shark"
"github.com/kballard/go-shellquote"
)
Expand All @@ -35,6 +36,8 @@ func (e ProcessNotStarted) Error() string {
type Command struct {
sync.Mutex
*exec.Cmd
summaryReader *summary.Reader
summaryWriter io.Closer
}

func (c *Command) String() string {
Expand All @@ -46,14 +49,21 @@ func (c *Command) String() string {
func (c *Command) Start() error {
c.Lock()
defer c.Unlock()
c.Cmd.Stderr = termshark.ErrLogger("cmd", c.Path)
pr, pw := io.Pipe()
c.summaryWriter = pw
c.summaryReader = summary.New(pr)
c.Cmd.Stderr = io.MultiWriter(pw, termshark.ErrLogger("cmd", c.Path))
c.PutInNewGroupOnUnix()
res := c.Cmd.Start()
return res
}

func (c *Command) Wait() error {
return c.Cmd.Wait()
err := c.Cmd.Wait()
c.Lock()
c.summaryWriter.Close()
c.Unlock()
return err
}

func (c *Command) StdoutReader() (io.ReadCloser, error) {
Expand All @@ -62,6 +72,13 @@ func (c *Command) StdoutReader() (io.ReadCloser, error) {
return c.Cmd.StdoutPipe()
}

func (c *Command) StderrSummary() []string {
c.Lock()
defer c.Unlock()

return c.summaryReader.Summary()
}

func (c *Command) SetStdout(w io.Writer) {
c.Lock()
defer c.Unlock()
Expand Down
27 changes: 12 additions & 15 deletions pcap/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ type IBasicCommand interface {
Wait() error
Pid() int
Kill() error
StderrSummary() []string
}

func MakeUsefulError(cmd IBasicCommand, err error) gowid.KeyValueError {
return gowid.WithKVs(termshark.BadCommand, map[string]interface{}{
"command": cmd.String(),
"error": err,
"stderr": strings.Join(cmd.StderrSummary(), "\n"),
})
}

type ITailCommand interface {
Expand Down Expand Up @@ -1548,11 +1557,7 @@ func (p *PsmlLoader) loadPsmlSync(iloader *InterfaceLoader, e iPsmlLoaderEnv, cb
if !p.psmlStoppedDeliberately_ {
if err != nil {
if _, ok := err.(*exec.ExitError); ok {
cerr := gowid.WithKVs(termshark.BadCommand, map[string]interface{}{
"command": psmlCmd.String(),
"error": err,
})
HandleError(PsmlCode, app, cerr, cb)
HandleError(PsmlCode, app, MakeUsefulError(psmlCmd, err), cb)
}
}
}
Expand Down Expand Up @@ -1676,11 +1681,7 @@ func (p *PsmlLoader) loadPsmlSync(iloader *InterfaceLoader, e iPsmlLoaderEnv, cb
if !p.psmlStoppedDeliberately_ && !e.TailStoppedDeliberately() {
if err != nil {
if _, ok := err.(*exec.ExitError); ok {
cerr := gowid.WithKVs(termshark.BadCommand, map[string]interface{}{
"command": tailCmd.String(),
"error": err,
})
HandleError(PsmlCode, app, cerr, cb)
HandleError(PsmlCode, app, MakeUsefulError(tailCmd, err), cb)
}
}
}
Expand Down Expand Up @@ -2144,11 +2145,7 @@ func (i *InterfaceLoader) loadIfacesSync(e iIfaceLoaderEnv, cb interface{}, app
// This could be if termshark is started like this: cat nosuchfile.pcap | termshark -i -
// Then dumpcap will be started with /dev/fd/3 as its stdin, but will fail with EOF and
// exit status 1.
cerr := gowid.WithKVs(termshark.BadCommand, map[string]interface{}{
"command": ifaceCmd.String(),
"error": err,
})
HandleError(IfaceCode, app, cerr, cb)
HandleError(IfaceCode, app, MakeUsefulError(ifaceCmd, err), cb)
}
}

Expand Down
95 changes: 95 additions & 0 deletions pkg/summary/summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.

package summary

import (
"bufio"
"io"
"sync"

"github.com/gcla/termshark/v2"
)

//======================================================================

// Reader maintains the first two and last two lines read from a source io.Reader.
// At any point, the Summary() function can be called to extract a summary of
// what's been read so far. I'm using this to create a summary of the stderr of
// a termshark command.
type Reader struct {
source io.Reader
first *string
second *string
penultimate *string
last *string
num int
lock sync.Mutex
}

func New(source io.Reader) *Reader {
res := &Reader{
source: source,
}

termshark.TrackedGo(func() {
res.start()
}, Goroutinewg)

return res
}

func (h *Reader) Summary() []string {
h.lock.Lock()
defer h.lock.Unlock()

res := make([]string, 0, 5)
if h.num >= 1 {
res = append(res, *h.first)
}
if h.num >= 2 {
res = append(res, *h.second)
}
if h.num >= 5 {
res = append(res, "...")
}
if h.num >= 4 {
res = append(res, *h.penultimate)
}
if h.num >= 3 {
res = append(res, *h.last)
}

return res
}

func (h *Reader) start() {
scanner := bufio.NewScanner(h.source)
for scanner.Scan() {
line := scanner.Text()
h.lock.Lock()
h.num += 1
if h.first == nil {
h.first = &line
} else if h.second == nil {
h.second = &line
}

h.penultimate = h.last
h.last = &line
h.lock.Unlock()
}
}

//======================================================================

// This is a debugging aid - I use it to ensure goroutines stop as expected. If they don't
// the main program will hang at termination.
var Goroutinewg *sync.WaitGroup

//======================================================================
// Local Variables:
// mode: Go
// fill-column: 78
// End:
12 changes: 2 additions & 10 deletions streams/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,7 @@ func (c *Loader) loadStreamReassemblyAsync(pcapf string, proto string, idx int,
state = pcap.Terminated
if !c.SuppressErrors && err != nil {
if _, ok := err.(*exec.ExitError); ok {
cerr := gowid.WithKVs(termshark.BadCommand, map[string]interface{}{
"command": c.streamCmd.String(),
"error": err,
})
pcap.HandleError(pcap.StreamCode, app, cerr, cb)
pcap.HandleError(pcap.StreamCode, app, pcap.MakeUsefulError(c.streamCmd, err), cb)
}
}

Expand Down Expand Up @@ -274,11 +270,7 @@ func (c *Loader) startStreamIndexerAsync(pcapf string, proto string, idx int, ap
state = pcap.Terminated
if !c.SuppressErrors && err != nil {
if _, ok := err.(*exec.ExitError); ok {
cerr := gowid.WithKVs(termshark.BadCommand, map[string]interface{}{
"command": c.indexerCmd.String(),
"error": err,
})
pcap.HandleError(pcap.StreamCode, app, cerr, cb)
pcap.HandleError(pcap.StreamCode, app, pcap.MakeUsefulError(c.indexerCmd, err), cb)
}
}
streamOut.Close()
Expand Down
12 changes: 9 additions & 3 deletions ui/prochandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package ui
import (
"fmt"
"os"
"sort"
"strings"
"time"

Expand Down Expand Up @@ -126,10 +127,15 @@ func (t updatePacketViews) OnError(code pcap.HandlerCode, app gowid.IApp, err er
if kverr, ok := err.(gowid.KeyValueError); ok {
errstr = fmt.Sprintf("%v\n\n", kverr.Cause())
kvs := make([]string, 0, len(kverr.KeyVals))
for k, v := range kverr.KeyVals {
kvs = append(kvs, fmt.Sprintf("%v: %v", k, v))
ks := make([]string, 0, len(kverr.KeyVals))
for k := range kverr.KeyVals {
ks = append(ks, k)
}
errstr = errstr + strings.Join(kvs, "\n")
sort.Sort(sort.StringSlice(ks))
for _, k := range ks {
kvs = append(kvs, fmt.Sprintf("%v: %v", k, kverr.KeyVals[k]))
}
errstr = errstr + strings.Join(kvs, "\n\n")
} else {
errstr = fmt.Sprintf("%v", err)
}
Expand Down

0 comments on commit b9aefce

Please sign in to comment.