aboutsummaryrefslogtreecommitdiffhomepage
path: root/container/errors.go
blob: d7651aa9bed79ea0433f6cbf5bed662dea9137c2 (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
129
package container

import (
	"errors"
	"os"
	"syscall"

	"hakurei.app/container/check"
	"hakurei.app/container/vfs"
	"hakurei.app/message"
)

// messageFromError returns a printable error message for a supported concrete type.
func messageFromError(err error) (m string, ok bool) {
	if m, ok = messagePrefixP[MountError]("cannot ", err); ok {
		return
	}
	if m, ok = messagePrefixP[os.PathError]("cannot ", err); ok {
		return
	}
	if m, ok = messagePrefix[check.AbsoluteError](zeroString, err); ok {
		return
	}
	if m, ok = messagePrefix[OpRepeatError](zeroString, err); ok {
		return
	}
	if m, ok = messagePrefix[OpStateError](zeroString, err); ok {
		return
	}

	if m, ok = messagePrefixP[vfs.DecoderError]("cannot ", err); ok {
		return
	}
	if m, ok = messagePrefix[TmpfsSizeError](zeroString, err); ok {
		return
	}

	if m, ok = message.GetMessage(err); ok {
		return
	}

	return zeroString, false
}

// messagePrefix checks and prefixes the error message of a non-pointer error.
// While this is usable for pointer errors, such use should be avoided as nil check is omitted.
func messagePrefix[T error](prefix string, err error) (string, bool) {
	var targetError T
	if errors.As(err, &targetError) {
		return prefix + targetError.Error(), true
	}
	return zeroString, false
}

// messagePrefixP checks and prefixes the error message of a pointer error.
func messagePrefixP[V any, T interface {
	*V
	error
}](prefix string, err error) (string, bool) {
	var targetError T
	if errors.As(err, &targetError) && targetError != nil {
		return prefix + targetError.Error(), true
	}
	return zeroString, false
}

// MountError wraps errors returned by syscall.Mount.
type MountError struct {
	Source, Target, Fstype string

	Flags uintptr
	Data  string
	syscall.Errno
}

func (e *MountError) Unwrap() error {
	if e.Errno == 0 {
		return nil
	}
	return e.Errno
}

func (e *MountError) Message() string { return "cannot " + e.Error() }
func (e *MountError) Error() string {
	if e.Flags&syscall.MS_BIND != 0 {
		if e.Flags&syscall.MS_REMOUNT != 0 {
			return "remount " + e.Target + ": " + e.Errno.Error()
		}
		return "bind " + e.Source + " on " + e.Target + ": " + e.Errno.Error()
	}

	if e.Fstype != FstypeNULL {
		return "mount " + e.Fstype + " on " + e.Target + ": " + e.Errno.Error()
	}

	// fallback case: if this is reached, the conditions for it to occur should be handled above
	return "mount " + e.Target + ": " + e.Errno.Error()
}

// optionalErrorUnwrap calls [errors.Unwrap] and returns the resulting value
// if it is not nil, or the original value if it is.
func optionalErrorUnwrap(err error) error {
	if underlyingErr := errors.Unwrap(err); underlyingErr != nil {
		return underlyingErr
	}
	return err
}

// errnoFallback returns the concrete errno from an error, or a [os.PathError] fallback.
func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
	var errno syscall.Errno
	if !errors.As(err, &errno) {
		return 0, &os.PathError{Op: op, Path: path, Err: err}
	}
	return errno, nil
}

// mount wraps syscall.Mount for error handling.
func mount(source, target, fstype string, flags uintptr, data string) error {
	err := syscall.Mount(source, target, fstype, flags, data)
	if err == nil {
		return nil
	}
	if errno, pathError := errnoFallback("mount", target, err); pathError != nil {
		return pathError
	} else {
		return &MountError{source, target, fstype, flags, data, errno}
	}
}