aboutsummaryrefslogtreecommitdiffhomepage
path: root/container
diff options
context:
space:
mode:
Diffstat (limited to 'container')
-rw-r--r--container/dispatcher.go2
-rw-r--r--container/errors.go60
-rw-r--r--container/errors_test.go109
3 files changed, 170 insertions, 1 deletions
diff --git a/container/dispatcher.go b/container/dispatcher.go
index ae697cef..c94d9348 100644
--- a/container/dispatcher.go
+++ b/container/dispatcher.go
@@ -225,7 +225,7 @@ func (direct) pivotRoot(newroot, putold string) (err error) {
return syscall.PivotRoot(newroot, putold)
}
func (direct) mount(source, target, fstype string, flags uintptr, data string) (err error) {
- return syscall.Mount(source, target, fstype, flags, data)
+ return mount(source, target, fstype, flags, data)
}
func (direct) unmount(target string, flags int) (err error) {
return syscall.Unmount(target, flags)
diff --git a/container/errors.go b/container/errors.go
new file mode 100644
index 00000000..12cb3b95
--- /dev/null
+++ b/container/errors.go
@@ -0,0 +1,60 @@
+package container
+
+import (
+ "errors"
+ "os"
+ "syscall"
+)
+
+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) 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()
+}
+
+// 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}
+ }
+}
diff --git a/container/errors_test.go b/container/errors_test.go
new file mode 100644
index 00000000..3d327430
--- /dev/null
+++ b/container/errors_test.go
@@ -0,0 +1,109 @@
+package container
+
+import (
+ "errors"
+ "os"
+ "reflect"
+ "syscall"
+ "testing"
+)
+
+func TestMountError(t *testing.T) {
+ testCases := []struct {
+ name string
+ err error
+ errno syscall.Errno
+ want string
+ }{
+ {"bind", &MountError{
+ Source: "/host/nix/store",
+ Target: "/sysroot/nix/store",
+ Fstype: FstypeNULL,
+ Flags: syscall.MS_SILENT | syscall.MS_BIND | syscall.MS_REC,
+ Data: zeroString,
+ Errno: syscall.ENOSYS,
+ }, syscall.ENOSYS,
+ "bind /host/nix/store on /sysroot/nix/store: function not implemented"},
+
+ {"remount", &MountError{
+ Source: SourceNone,
+ Target: "/sysroot/nix/store",
+ Fstype: FstypeNULL,
+ Flags: syscall.MS_SILENT | syscall.MS_BIND | syscall.MS_REMOUNT,
+ Data: zeroString,
+ Errno: syscall.EPERM,
+ }, syscall.EPERM,
+ "remount /sysroot/nix/store: operation not permitted"},
+
+ {"overlay", &MountError{
+ Source: SourceOverlay,
+ Target: sysrootPath,
+ Fstype: FstypeOverlay,
+ Data: `lowerdir=/host/var/lib/planterette/base/debian\:f92c9052`,
+ Errno: syscall.EINVAL,
+ }, syscall.EINVAL,
+ "mount overlay on /sysroot: invalid argument"},
+
+ {"fallback", &MountError{
+ Source: SourceNone,
+ Target: sysrootPath,
+ Fstype: FstypeNULL,
+ Errno: syscall.ENOTRECOVERABLE,
+ }, syscall.ENOTRECOVERABLE,
+ "mount /sysroot: state not recoverable"},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Run("is", func(t *testing.T) {
+ if !errors.Is(tc.err, tc.errno) {
+ t.Errorf("Is: %#v is not %v", tc.err, tc.errno)
+ }
+ })
+ t.Run("error", func(t *testing.T) {
+ if got := tc.err.Error(); got != tc.want {
+ t.Errorf("Error: %q, want %q", got, tc.want)
+ }
+ })
+ })
+ }
+
+ t.Run("zero", func(t *testing.T) {
+ if errors.Is(new(MountError), syscall.Errno(0)) {
+ t.Errorf("Is: zero MountError unexpected true")
+ }
+ })
+}
+
+func TestErrnoFallback(t *testing.T) {
+ testCases := []struct {
+ name string
+ err error
+ wantErrno syscall.Errno
+ wantPath *os.PathError
+ }{
+ {"mount", &MountError{
+ Errno: syscall.ENOTRECOVERABLE,
+ }, syscall.ENOTRECOVERABLE, nil},
+
+ {"path errno", &os.PathError{
+ Err: syscall.ETIMEDOUT,
+ }, syscall.ETIMEDOUT, nil},
+
+ {"fallback", errUnique, 0, &os.PathError{
+ Op: "fallback",
+ Path: "/proc/nonexistent",
+ Err: errUnique,
+ }},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ errno, err := errnoFallback(tc.name, Nonexistent, tc.err)
+ if errno != tc.wantErrno {
+ t.Errorf("errnoFallback: errno = %v, want %v", errno, tc.wantErrno)
+ }
+ if !reflect.DeepEqual(err, tc.wantPath) {
+ t.Errorf("errnoFallback: pathError = %#v, want %#v", err, tc.wantPath)
+ }
+ })
+ }
+}