gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

errors.go (5185B)


      1 package internal
      2 
      3 import (
      4 	"bytes"
      5 	"fmt"
      6 	"io"
      7 	"strings"
      8 )
      9 
     10 // ErrorWithLog returns an error which includes logs from the kernel verifier.
     11 //
     12 // The default error output is a summary of the full log. The latter can be
     13 // accessed via VerifierError.Log or by formatting the error, see Format.
     14 //
     15 // A set of heuristics is used to determine whether the log has been truncated.
     16 func ErrorWithLog(err error, log []byte) *VerifierError {
     17 	const whitespace = "\t\r\v\n "
     18 
     19 	// Convert verifier log C string by truncating it on the first 0 byte
     20 	// and trimming trailing whitespace before interpreting as a Go string.
     21 	truncated := false
     22 	if i := bytes.IndexByte(log, 0); i != -1 {
     23 		if i == len(log)-1 && !bytes.HasSuffix(log[:i], []byte{'\n'}) {
     24 			// The null byte is at the end of the buffer and it's not preceded
     25 			// by a newline character. Most likely the buffer was too short.
     26 			truncated = true
     27 		}
     28 
     29 		log = log[:i]
     30 	} else if len(log) > 0 {
     31 		// No null byte? Dodgy!
     32 		truncated = true
     33 	}
     34 
     35 	log = bytes.Trim(log, whitespace)
     36 	logLines := bytes.Split(log, []byte{'\n'})
     37 	lines := make([]string, 0, len(logLines))
     38 	for _, line := range logLines {
     39 		// Don't remove leading white space on individual lines. We rely on it
     40 		// when outputting logs.
     41 		lines = append(lines, string(bytes.TrimRight(line, whitespace)))
     42 	}
     43 
     44 	return &VerifierError{err, lines, truncated}
     45 }
     46 
     47 // VerifierError includes information from the eBPF verifier.
     48 //
     49 // It summarises the log output, see Format if you want to output the full contents.
     50 type VerifierError struct {
     51 	// The error which caused this error.
     52 	Cause error
     53 	// The verifier output split into lines.
     54 	Log []string
     55 	// Whether the log output is truncated, based on several heuristics.
     56 	Truncated bool
     57 }
     58 
     59 func (le *VerifierError) Unwrap() error {
     60 	return le.Cause
     61 }
     62 
     63 func (le *VerifierError) Error() string {
     64 	log := le.Log
     65 	if n := len(log); n > 0 && strings.HasPrefix(log[n-1], "processed ") {
     66 		// Get rid of "processed 39 insns (limit 1000000) ..." from summary.
     67 		log = log[:n-1]
     68 	}
     69 
     70 	n := len(log)
     71 	if n == 0 {
     72 		return le.Cause.Error()
     73 	}
     74 
     75 	lines := log[n-1:]
     76 	if n >= 2 && (includePreviousLine(log[n-1]) || le.Truncated) {
     77 		// Add one more line of context if it aids understanding the error.
     78 		lines = log[n-2:]
     79 	}
     80 
     81 	var b strings.Builder
     82 	fmt.Fprintf(&b, "%s: ", le.Cause.Error())
     83 
     84 	for i, line := range lines {
     85 		b.WriteString(strings.TrimSpace(line))
     86 		if i != len(lines)-1 {
     87 			b.WriteString(": ")
     88 		}
     89 	}
     90 
     91 	omitted := len(le.Log) - len(lines)
     92 	if omitted == 0 && !le.Truncated {
     93 		return b.String()
     94 	}
     95 
     96 	b.WriteString(" (")
     97 	if le.Truncated {
     98 		b.WriteString("truncated")
     99 	}
    100 
    101 	if omitted > 0 {
    102 		if le.Truncated {
    103 			b.WriteString(", ")
    104 		}
    105 		fmt.Fprintf(&b, "%d line(s) omitted", omitted)
    106 	}
    107 	b.WriteString(")")
    108 
    109 	return b.String()
    110 }
    111 
    112 // includePreviousLine returns true if the given line likely is better
    113 // understood with additional context from the preceding line.
    114 func includePreviousLine(line string) bool {
    115 	// We need to find a good trade off between understandable error messages
    116 	// and too much complexity here. Checking the string prefix is ok, requiring
    117 	// regular expressions to do it is probably overkill.
    118 
    119 	if strings.HasPrefix(line, "\t") {
    120 		// [13] STRUCT drm_rect size=16 vlen=4
    121 		// \tx1 type_id=2
    122 		return true
    123 	}
    124 
    125 	if len(line) >= 2 && line[0] == 'R' && line[1] >= '0' && line[1] <= '9' {
    126 		// 0: (95) exit
    127 		// R0 !read_ok
    128 		return true
    129 	}
    130 
    131 	if strings.HasPrefix(line, "invalid bpf_context access") {
    132 		// 0: (79) r6 = *(u64 *)(r1 +0)
    133 		// func '__x64_sys_recvfrom' arg0 type FWD is not a struct
    134 		// invalid bpf_context access off=0 size=8
    135 		return true
    136 	}
    137 
    138 	return false
    139 }
    140 
    141 // Format the error.
    142 //
    143 // Understood verbs are %s and %v, which are equivalent to calling Error(). %v
    144 // allows outputting additional information using the following flags:
    145 //
    146 //     +   Output the first <width> lines, or all lines if no width is given.
    147 //     -   Output the last <width> lines, or all lines if no width is given.
    148 //
    149 // Use width to specify how many lines to output. Use the '-' flag to output
    150 // lines from the end of the log instead of the beginning.
    151 func (le *VerifierError) Format(f fmt.State, verb rune) {
    152 	switch verb {
    153 	case 's':
    154 		_, _ = io.WriteString(f, le.Error())
    155 
    156 	case 'v':
    157 		n, haveWidth := f.Width()
    158 		if !haveWidth || n > len(le.Log) {
    159 			n = len(le.Log)
    160 		}
    161 
    162 		if !f.Flag('+') && !f.Flag('-') {
    163 			if haveWidth {
    164 				_, _ = io.WriteString(f, "%!v(BADWIDTH)")
    165 				return
    166 			}
    167 
    168 			_, _ = io.WriteString(f, le.Error())
    169 			return
    170 		}
    171 
    172 		if f.Flag('+') && f.Flag('-') {
    173 			_, _ = io.WriteString(f, "%!v(BADFLAG)")
    174 			return
    175 		}
    176 
    177 		fmt.Fprintf(f, "%s:", le.Cause.Error())
    178 
    179 		omitted := len(le.Log) - n
    180 		lines := le.Log[:n]
    181 		if f.Flag('-') {
    182 			// Print last instead of first lines.
    183 			lines = le.Log[len(le.Log)-n:]
    184 			if omitted > 0 {
    185 				fmt.Fprintf(f, "\n\t(%d line(s) omitted)", omitted)
    186 			}
    187 		}
    188 
    189 		for _, line := range lines {
    190 			fmt.Fprintf(f, "\n\t%s", line)
    191 		}
    192 
    193 		if !f.Flag('-') {
    194 			if omitted > 0 {
    195 				fmt.Fprintf(f, "\n\t(%d line(s) omitted)", omitted)
    196 			}
    197 		}
    198 
    199 		if le.Truncated {
    200 			fmt.Fprintf(f, "\n\t(truncated)")
    201 		}
    202 
    203 	default:
    204 		fmt.Fprintf(f, "%%!%c(BADVERB)", verb)
    205 	}
    206 }