summaryrefslogtreecommitdiff
path: root/libpod/network/netavark/exec.go
blob: d6458eeb476ad85632fc47e2300fde9fde6b0c74 (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
117
118
119
120
121
122
123
124
125
126
127
128
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,
	}
}

// getRustLogEnv returns the RUST_LOG env var based on the current logrus level
func getRustLogEnv() string {
	level := logrus.GetLevel().String()
	// rust env_log uses warn instead of warning
	if level == "warning" {
		level = "warn"
	}
	// the rust netlink library is very verbose
	// make sure to only log netavark logs
	return "RUST_LOG=netavark=" + level
}

// 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(), getRustLogEnv())
	// 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
}