summaryrefslogtreecommitdiff
path: root/libpod/network/netavark/exec.go
blob: 700df9a69d38fb5557b3601cb6d3e9108f985eb0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package netavark

import (
	"encoding/json"
	"errors"
	"os"
	"os/exec"
	"strconv"

	"github.com/sirupsen/logrus"
)

type netavarkError struct {
	exitCode int
	// Set the json key to "error" so we can directly unmarshal into this struct
	Msg string `json:"error"`
	err error
}

func (e *netavarkError) Error() string {
	ec := ""
	// only add the exit code the the error message if we have at least info log level
	// the normal user does not need to care about the number
	if e.exitCode > 0 && logrus.IsLevelEnabled(logrus.InfoLevel) {
		ec = " (exit code " + strconv.Itoa(e.exitCode) + ")"
	}
	msg := "netavark" + ec
	if len(msg) > 0 {
		msg += ": " + e.Msg
	}
	if e.err != nil {
		msg += ": " + e.err.Error()
	}
	return msg
}

func (e *netavarkError) Unwrap() error {
	return e.err
}

func newNetavarkError(msg string, err error) error {
	return &netavarkError{
		Msg: msg,
		err: err,
	}
}

// execNetavark will execute netavark with the following arguments
// It takes the path to the binary, the list of args and an interface which is
// marshaled to json and send via stdin to netavark. The result interface is
// used to marshal the netavark output into it. This can be nil.
// All errors return by this function should be of the type netavarkError
// to provide a helpful error message.
func execNetavark(binary string, args []string, stdin, result interface{}) error {
	stdinR, stdinW, err := os.Pipe()
	if err != nil {
		return newNetavarkError("failed to create stdin pipe", err)
	}
	defer stdinR.Close()

	stdoutR, stdoutW, err := os.Pipe()
	if err != nil {
		return newNetavarkError("failed to create stdout pipe", err)
	}
	defer stdoutR.Close()
	defer stdoutW.Close()

	cmd := exec.Command(binary, args...)
	// connect the pipes to stdin and stdout
	cmd.Stdin = stdinR
	cmd.Stdout = stdoutW
	// connect stderr to the podman stderr for logging
	cmd.Stderr = os.Stderr
	// set the netavark log level to the same as the podman
	cmd.Env = append(os.Environ(), "RUST_LOG="+logrus.GetLevel().String())
	// if we run with debug log level lets also set RUST_BACKTRACE=1 so we can get the full stack trace in case of panics
	if logrus.IsLevelEnabled(logrus.DebugLevel) {
		cmd.Env = append(cmd.Env, "RUST_BACKTRACE=1")
	}

	err = cmd.Start()
	if err != nil {
		return newNetavarkError("failed to start process", err)
	}
	err = json.NewEncoder(stdinW).Encode(stdin)
	stdinW.Close()
	if err != nil {
		return newNetavarkError("failed to encode stdin data", err)
	}

	dec := json.NewDecoder(stdoutR)

	err = cmd.Wait()
	stdoutW.Close()
	if err != nil {
		exitError := &exec.ExitError{}
		if errors.As(err, &exitError) {
			ne := &netavarkError{}
			// lets disallow unknown fields to make sure we do not get some unexpected stuff
			dec.DisallowUnknownFields()
			// this will unmarshal the error message into the error struct
			ne.err = dec.Decode(ne)
			ne.exitCode = exitError.ExitCode()
			return ne
		}
		return newNetavarkError("unexpected failure during execution", err)
	}

	if result != nil {
		err = dec.Decode(result)
		if err != nil {
			return newNetavarkError("failed to decode result", err)
		}
	}
	return nil
}