diff --git a/envbuilder.go b/envbuilder.go index 5538ea9c..10311df7 100644 --- a/envbuilder.go +++ b/envbuilder.go @@ -438,7 +438,7 @@ ENTRYPOINT [%q]`, exePath, exePath, exePath) tempRemountDest := filepath.Join("/", MagicDir, "mnt") // ignorePrefixes is a superset of ignorePaths that we pass to kaniko's // IgnoreList. - ignorePrefixes := append([]string{"/proc", "/sys"}, ignorePaths...) + ignorePrefixes := append([]string{"/dev", "/proc", "/sys"}, ignorePaths...) restoreMounts, err := ebutil.TempRemount(options.Logger, tempRemountDest, ignorePrefixes...) defer func() { // restoreMounts should never be nil if err := restoreMounts(); err != nil { diff --git a/internal/ebutil/libs.go b/internal/ebutil/libs.go new file mode 100644 index 00000000..58206c0c --- /dev/null +++ b/internal/ebutil/libs.go @@ -0,0 +1,86 @@ +package ebutil + +import ( + "errors" + "fmt" + "os" + "path/filepath" +) + +// Container runtimes like NVIDIA mount individual libraries into the container +// (e.g. `.so.`) and create symlinks for them +// (e.g. `.so.1`). This code helps with finding the right library +// directory for the target Linux distribution as well as locating the symlinks. +// +// Please see [#143 (comment)] for further details. +// +// [#143 (comment)]: https://github.com/coder/envbuilder/issues/143#issuecomment-2192405828 + +// Based on https://github.com/NVIDIA/libnvidia-container/blob/v1.15.0/src/common.h#L29 +const usrLibDir = "/usr/lib64" + +const debianVersionFile = "/etc/debian_version" + +// libraryDirectoryPath returns the library directory. It returns a multiarch +// directory if the distribution is Debian or a derivative. +// +// Based on https://github.com/NVIDIA/libnvidia-container/blob/v1.15.0/src/nvc_container.c#L152-L165 +func libraryDirectoryPath(m mounter) (string, error) { + // Debian and its derivatives use a multiarch directory scheme. + if _, err := m.Stat(debianVersionFile); err != nil && !errors.Is(err, os.ErrNotExist) { + return "", fmt.Errorf("check if debian: %w", err) + } else if err == nil { + return usrLibMultiarchDir, nil + } + + return usrLibDir, nil +} + +// libraryDirectorySymlinks returns a mapping of each library (basename) with a +// list of their symlinks (basename). Libraries with no symlinks do not appear +// in the mapping. +func libraryDirectorySymlinks(m mounter, libDir string) (map[string][]string, error) { + des, err := m.ReadDir(libDir) + if err != nil { + return nil, fmt.Errorf("read directory %s: %w", libDir, err) + } + + libsSymlinks := make(map[string][]string) + for _, de := range des { + if de.IsDir() { + continue + } + + if de.Type()&os.ModeSymlink != os.ModeSymlink { + // Not a symlink. Skip. + continue + } + + symlink := filepath.Join(libDir, de.Name()) + path, err := m.EvalSymlinks(symlink) + if err != nil { + return nil, fmt.Errorf("eval symlink %s: %w", symlink, err) + } + + path = filepath.Base(path) + if _, ok := libsSymlinks[path]; !ok { + libsSymlinks[path] = make([]string, 0, 1) + } + + libsSymlinks[path] = append(libsSymlinks[path], de.Name()) + } + + return libsSymlinks, nil +} + +// moveLibSymlinks moves a list of symlinks from source to destination directory. +func moveLibSymlinks(m mounter, symlinks []string, srcDir, destDir string) error { + for _, l := range symlinks { + oldpath := filepath.Join(srcDir, l) + newpath := filepath.Join(destDir, l) + if err := m.Rename(oldpath, newpath); err != nil { + return fmt.Errorf("move symlink %s => %s: %w", oldpath, newpath, err) + } + } + return nil +} diff --git a/internal/ebutil/libs_amd64.go b/internal/ebutil/libs_amd64.go new file mode 100644 index 00000000..b3f8230b --- /dev/null +++ b/internal/ebutil/libs_amd64.go @@ -0,0 +1,7 @@ +//go:build amd64 + +package ebutil + +// Based on https://github.com/NVIDIA/libnvidia-container/blob/v1.15.0/src/common.h#L36 + +const usrLibMultiarchDir = "/usr/lib/x86_64-linux-gnu" diff --git a/internal/ebutil/libs_arm64.go b/internal/ebutil/libs_arm64.go new file mode 100644 index 00000000..c76fb834 --- /dev/null +++ b/internal/ebutil/libs_arm64.go @@ -0,0 +1,7 @@ +//go:build arm64 + +package ebutil + +// Based on https://github.com/NVIDIA/libnvidia-container/blob/v1.15.0/src/common.h#L52 + +const usrLibMultiarchDir = "/usr/lib/aarch64-linux-gnu" diff --git a/internal/ebutil/mock_mounter_test.go b/internal/ebutil/mock_mounter_test.go index 7445376a..4e664f4c 100644 --- a/internal/ebutil/mock_mounter_test.go +++ b/internal/ebutil/mock_mounter_test.go @@ -42,6 +42,21 @@ func (m *Mockmounter) EXPECT() *MockmounterMockRecorder { return m.recorder } +// EvalSymlinks mocks base method. +func (m *Mockmounter) EvalSymlinks(arg0 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EvalSymlinks", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EvalSymlinks indicates an expected call of EvalSymlinks. +func (mr *MockmounterMockRecorder) EvalSymlinks(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EvalSymlinks", reflect.TypeOf((*Mockmounter)(nil).EvalSymlinks), arg0) +} + // GetMounts mocks base method. func (m *Mockmounter) GetMounts() ([]*procfs.MountInfo, error) { m.ctrl.T.Helper() @@ -100,6 +115,35 @@ func (mr *MockmounterMockRecorder) OpenFile(arg0, arg1, arg2 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenFile", reflect.TypeOf((*Mockmounter)(nil).OpenFile), arg0, arg1, arg2) } +// ReadDir mocks base method. +func (m *Mockmounter) ReadDir(arg0 string) ([]os.DirEntry, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReadDir", arg0) + ret0, _ := ret[0].([]os.DirEntry) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReadDir indicates an expected call of ReadDir. +func (mr *MockmounterMockRecorder) ReadDir(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadDir", reflect.TypeOf((*Mockmounter)(nil).ReadDir), arg0) +} + +// Rename mocks base method. +func (m *Mockmounter) Rename(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Rename", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Rename indicates an expected call of Rename. +func (mr *MockmounterMockRecorder) Rename(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Rename", reflect.TypeOf((*Mockmounter)(nil).Rename), arg0, arg1) +} + // Stat mocks base method. func (m *Mockmounter) Stat(arg0 string) (os.FileInfo, error) { m.ctrl.T.Helper() diff --git a/internal/ebutil/remount.go b/internal/ebutil/remount.go index f4a2b416..0ae7b135 100644 --- a/internal/ebutil/remount.go +++ b/internal/ebutil/remount.go @@ -1,6 +1,7 @@ package ebutil import ( + "errors" "fmt" "os" "path/filepath" @@ -44,6 +45,16 @@ func tempRemount(m mounter, logf func(notcodersdk.LogLevel, string, ...any), bas return func() error { return nil }, fmt.Errorf("get mounts: %w", err) } + libDir, err := libraryDirectoryPath(m) + if err != nil { + return func() error { return nil }, fmt.Errorf("get lib directory: %w", err) + } + + libsSymlinks, err := libraryDirectorySymlinks(m, libDir) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return func() error { return nil }, fmt.Errorf("read lib symlinks: %w", err) + } + // temp move of all ro mounts mounts := map[string]string{} var restoreOnce sync.Once @@ -51,8 +62,19 @@ func tempRemount(m mounter, logf func(notcodersdk.LogLevel, string, ...any), bas // closer to attempt to restore original mount points restore = func() error { restoreOnce.Do(func() { + if len(mounts) == 0 { + return + } + + newLibDir, err := libraryDirectoryPath(m) + if err != nil { + merr = multierror.Append(merr, fmt.Errorf("get new lib directory: %w", err)) + return + } + for orig, moved := range mounts { - if err := remount(m, moved, orig); err != nil { + logf(notcodersdk.LogLevelTrace, "restore mount %s", orig) + if err := remount(m, moved, orig, newLibDir, libsSymlinks); err != nil { merr = multierror.Append(merr, fmt.Errorf("restore mount: %w", err)) } } @@ -77,7 +99,8 @@ outer: src := mountInfo.MountPoint dest := filepath.Join(base, src) - if err := remount(m, src, dest); err != nil { + logf(notcodersdk.LogLevelTrace, "temp remount %s", src) + if err := remount(m, src, dest, libDir, libsSymlinks); err != nil { return restore, fmt.Errorf("temp remount: %w", err) } @@ -87,30 +110,48 @@ outer: return restore, nil } -func remount(m mounter, src, dest string) error { +func remount(m mounter, src, dest, libDir string, libsSymlinks map[string][]string) error { stat, err := m.Stat(src) if err != nil { return fmt.Errorf("stat %s: %w", src, err) } + var destDir string if stat.IsDir() { destDir = dest } else { destDir = filepath.Dir(dest) + if destDir == usrLibDir || destDir == usrLibMultiarchDir { + // Restore mount to libDir + destDir = libDir + dest = filepath.Join(destDir, stat.Name()) + } } + if err := m.MkdirAll(destDir, 0o750); err != nil { return fmt.Errorf("ensure path: %w", err) } + if !stat.IsDir() { f, err := m.OpenFile(dest, os.O_CREATE, 0o640) if err != nil { return fmt.Errorf("ensure file path: %w", err) } - defer f.Close() + // This ensure the file is created, it will not be used. It can be closed immediately. + f.Close() + + if symlinks, ok := libsSymlinks[stat.Name()]; ok { + srcDir := filepath.Dir(src) + if err := moveLibSymlinks(m, symlinks, srcDir, destDir); err != nil { + return err + } + } } + if err := m.Mount(src, dest, "bind", syscall.MS_BIND, ""); err != nil { return fmt.Errorf("bind mount %s => %s: %w", src, dest, err) } + if err := m.Unmount(src, 0); err != nil { return fmt.Errorf("unmount orig src %s: %w", src, err) } @@ -131,6 +172,12 @@ type mounter interface { Mount(string, string, string, uintptr, string) error // Unmount wraps syscall.Unmount Unmount(string, int) error + // ReadDir wraps os.ReadDir + ReadDir(string) ([]os.DirEntry, error) + // EvalSymlinks wraps filepath.EvalSymlinks + EvalSymlinks(string) (string, error) + // Rename wraps os.Rename + Rename(string, string) error } // realMounter implements mounter and actually does the thing. @@ -161,3 +208,15 @@ func (m *realMounter) OpenFile(name string, flag int, perm os.FileMode) (*os.Fil func (m *realMounter) Stat(path string) (os.FileInfo, error) { return os.Stat(path) } + +func (m *realMounter) ReadDir(name string) ([]os.DirEntry, error) { + return os.ReadDir(name) +} + +func (m *realMounter) EvalSymlinks(path string) (string, error) { + return filepath.EvalSymlinks(path) +} + +func (m *realMounter) Rename(oldpath, newpath string) error { + return os.Rename(oldpath, newpath) +} diff --git a/internal/ebutil/remount_internal_test.go b/internal/ebutil/remount_internal_test.go index 41036177..ceb0bff3 100644 --- a/internal/ebutil/remount_internal_test.go +++ b/internal/ebutil/remount_internal_test.go @@ -2,6 +2,7 @@ package ebutil import ( "os" + "runtime" "strings" "syscall" "testing" @@ -15,6 +16,11 @@ import ( "github.com/prometheus/procfs" ) +var expectedLibMultiarchDir = map[string]string{ + "amd64": "/usr/lib/x86_64-linux-gnu", + "arm64": "/usr/lib/aarch64-linux-gnu", +} + func Test_tempRemount(t *testing.T) { t.Parallel() @@ -26,11 +32,14 @@ func Test_tempRemount(t *testing.T) { mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys") mm.EXPECT().GetMounts().Return(mounts, nil) - mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{name: "modules", isDir: true}, nil) mm.EXPECT().MkdirAll("/.test/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil) mm.EXPECT().Mount("/var/lib/modules", "/.test/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) mm.EXPECT().Unmount("/var/lib/modules", 0).Times(1).Return(nil) - mm.EXPECT().Stat("/.test/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/.test/var/lib/modules").Return(&fakeFileInfo{name: "modules", isDir: true}, nil) mm.EXPECT().MkdirAll("/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil) mm.EXPECT().Mount("/.test/var/lib/modules", "/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) mm.EXPECT().Unmount("/.test/var/lib/modules", 0).Times(1).Return(nil) @@ -51,12 +60,15 @@ func Test_tempRemount(t *testing.T) { mounts := fakeMounts("/home", "/usr/bin/utility:ro", "/proc", "/sys") mm.EXPECT().GetMounts().Return(mounts, nil) - mm.EXPECT().Stat("/usr/bin/utility").Return(&fakeFileInfo{isDir: false}, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/usr/bin/utility").Return(&fakeFileInfo{name: "modules", isDir: false}, nil) mm.EXPECT().MkdirAll("/.test/usr/bin", os.FileMode(0o750)).Times(1).Return(nil) mm.EXPECT().OpenFile("/.test/usr/bin/utility", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil) mm.EXPECT().Mount("/usr/bin/utility", "/.test/usr/bin/utility", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) mm.EXPECT().Unmount("/usr/bin/utility", 0).Times(1).Return(nil) - mm.EXPECT().Stat("/.test/usr/bin/utility").Return(&fakeFileInfo{isDir: false}, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/.test/usr/bin/utility").Return(&fakeFileInfo{name: "modules", isDir: false}, nil) mm.EXPECT().MkdirAll("/usr/bin", os.FileMode(0o750)).Times(1).Return(nil) mm.EXPECT().OpenFile("/usr/bin/utility", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil) mm.EXPECT().Mount("/.test/usr/bin/utility", "/usr/bin/utility", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) @@ -70,6 +82,202 @@ func Test_tempRemount(t *testing.T) { _ = remount() }) + t.Run("OKLib", func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + mm := NewMockmounter(ctrl) + mounts := fakeMounts("/home", "/usr/lib64/lib.so.1:ro", "/proc", "/sys") + + mm.EXPECT().GetMounts().Return(mounts, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return([]os.DirEntry{ + &fakeDirEntry{ + name: "lib.so", + mode: os.ModeSymlink, + }, + &fakeDirEntry{ + name: "lib.so.1", + }, + &fakeDirEntry{ + name: "lib-other.so", + mode: os.ModeSymlink, + }, + &fakeDirEntry{ + name: "lib-other.so.1", + }, + &fakeDirEntry{ + name: "something.d", + isDir: true, + mode: os.ModeDir, + }, + }, nil) + mm.EXPECT().EvalSymlinks("/usr/lib64/lib.so").Return("/usr/lib64/lib.so.1", nil) + mm.EXPECT().EvalSymlinks("/usr/lib64/lib-other.so").Return("/usr/lib64/lib-other.so.1", nil) + mm.EXPECT().Stat("/usr/lib64/lib.so.1").Return(&fakeFileInfo{name: "lib.so.1", isDir: false}, nil) + mm.EXPECT().MkdirAll("/.test/usr/lib64", os.FileMode(0o750)).Times(1).Return(nil) + mm.EXPECT().OpenFile("/.test/usr/lib64/lib.so.1", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil) + mm.EXPECT().Rename("/usr/lib64/lib.so", "/.test/usr/lib64/lib.so").Return(nil) + mm.EXPECT().Mount("/usr/lib64/lib.so.1", "/.test/usr/lib64/lib.so.1", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) + mm.EXPECT().Unmount("/usr/lib64/lib.so.1", 0).Times(1).Return(nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/.test/usr/lib64/lib.so.1").Return(&fakeFileInfo{name: "lib.so.1", isDir: false}, nil) + mm.EXPECT().MkdirAll("/usr/lib64", os.FileMode(0o750)).Times(1).Return(nil) + mm.EXPECT().OpenFile("/usr/lib64/lib.so.1", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil) + mm.EXPECT().Rename("/.test/usr/lib64/lib.so", "/usr/lib64/lib.so").Return(nil) + mm.EXPECT().Mount("/.test/usr/lib64/lib.so.1", "/usr/lib64/lib.so.1", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) + mm.EXPECT().Unmount("/.test/usr/lib64/lib.so.1", 0).Times(1).Return(nil) + + remount, err := tempRemount(mm, fakeLog(t), "/.test") + require.NoError(t, err) + err = remount() + require.NoError(t, err) + // sync.Once should handle multiple remount calls + _ = remount() + }) + + t.Run("OKLibDebian", func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + mm := NewMockmounter(ctrl) + mounts := fakeMounts("/home", "/usr/lib64/lib.so.1:ro", "/proc", "/sys") + + mm.EXPECT().GetMounts().Return(mounts, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return([]os.DirEntry{ + &fakeDirEntry{ + name: "lib.so", + mode: os.ModeSymlink, + }, + &fakeDirEntry{ + name: "lib.so.1", + }, + &fakeDirEntry{ + name: "lib-other.so", + mode: os.ModeSymlink, + }, + &fakeDirEntry{ + name: "lib-other.so.1", + }, + &fakeDirEntry{ + name: "something.d", + isDir: true, + mode: os.ModeDir, + }, + }, nil) + mm.EXPECT().EvalSymlinks("/usr/lib64/lib.so").Return("lib.so.1", nil) + mm.EXPECT().EvalSymlinks("/usr/lib64/lib-other.so").Return("lib-other.so.1", nil) + mm.EXPECT().Stat("/usr/lib64/lib.so.1").Return(&fakeFileInfo{name: "lib.so.1", isDir: false}, nil) + mm.EXPECT().MkdirAll("/.test/usr/lib64", os.FileMode(0o750)).Times(1).Return(nil) + mm.EXPECT().OpenFile("/.test/usr/lib64/lib.so.1", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil) + mm.EXPECT().Rename("/usr/lib64/lib.so", "/.test/usr/lib64/lib.so").Return(nil) + mm.EXPECT().Mount("/usr/lib64/lib.so.1", "/.test/usr/lib64/lib.so.1", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) + mm.EXPECT().Unmount("/usr/lib64/lib.so.1", 0).Times(1).Return(nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, nil) + mm.EXPECT().Stat("/.test/usr/lib64/lib.so.1").Return(&fakeFileInfo{name: "lib.so.1", isDir: false}, nil) + mm.EXPECT().MkdirAll(expectedLibMultiarchDir[runtime.GOARCH], os.FileMode(0o750)).Times(1).Return(nil) + mm.EXPECT().OpenFile(expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so.1", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil) + mm.EXPECT().Rename("/.test/usr/lib64/lib.so", expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so").Return(nil) + mm.EXPECT().Mount("/.test/usr/lib64/lib.so.1", expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so.1", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) + mm.EXPECT().Unmount("/.test/usr/lib64/lib.so.1", 0).Times(1).Return(nil) + + remount, err := tempRemount(mm, fakeLog(t), "/.test") + require.NoError(t, err) + err = remount() + require.NoError(t, err) + // sync.Once should handle multiple remount calls + _ = remount() + }) + + t.Run("OKLibFromDebianToNotDebian", func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + mm := NewMockmounter(ctrl) + mounts := fakeMounts("/home", expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so.1:ro", "/proc", "/sys") + + mm.EXPECT().GetMounts().Return(mounts, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, nil) + mm.EXPECT().ReadDir(expectedLibMultiarchDir[runtime.GOARCH]).Return([]os.DirEntry{ + &fakeDirEntry{ + name: "lib.so", + mode: os.ModeSymlink, + }, + &fakeDirEntry{ + name: "lib.so.1", + }, + &fakeDirEntry{ + name: "lib-other.so", + mode: os.ModeSymlink, + }, + &fakeDirEntry{ + name: "lib-other.so.1", + }, + &fakeDirEntry{ + name: "something.d", + isDir: true, + mode: os.ModeDir, + }, + }, nil) + mm.EXPECT().EvalSymlinks(expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so").Return(expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so.1", nil) + mm.EXPECT().EvalSymlinks(expectedLibMultiarchDir[runtime.GOARCH]+"/lib-other.so").Return(expectedLibMultiarchDir[runtime.GOARCH]+"/usr/lib64/lib-other.so.1", nil) + mm.EXPECT().Stat(expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so.1").Return(&fakeFileInfo{name: "lib.so.1", isDir: false}, nil) + mm.EXPECT().MkdirAll("/.test"+expectedLibMultiarchDir[runtime.GOARCH], os.FileMode(0o750)).Times(1).Return(nil) + mm.EXPECT().OpenFile("/.test"+expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so.1", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil) + mm.EXPECT().Rename(expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so", "/.test"+expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so").Return(nil) + mm.EXPECT().Mount(expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so.1", "/.test"+expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so.1", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) + mm.EXPECT().Unmount(expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so.1", 0).Times(1).Return(nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/.test"+expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so.1").Return(&fakeFileInfo{name: "lib.so.1", isDir: false}, nil) + mm.EXPECT().MkdirAll("/usr/lib64", os.FileMode(0o750)).Times(1).Return(nil) + mm.EXPECT().OpenFile("/usr/lib64/lib.so.1", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil) + mm.EXPECT().Rename("/.test"+expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so", "/usr/lib64/lib.so").Return(nil) + mm.EXPECT().Mount("/.test"+expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so.1", "/usr/lib64/lib.so.1", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) + mm.EXPECT().Unmount("/.test"+expectedLibMultiarchDir[runtime.GOARCH]+"/lib.so.1", 0).Times(1).Return(nil) + + remount, err := tempRemount(mm, fakeLog(t), "/.test") + require.NoError(t, err) + err = remount() + require.NoError(t, err) + // sync.Once should handle multiple remount calls + _ = remount() + }) + + t.Run("OKLibNoSymlink", func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + mm := NewMockmounter(ctrl) + mounts := fakeMounts("/home", "/usr/lib64/lib.so.1:ro", "/proc", "/sys") + + mm.EXPECT().GetMounts().Return(mounts, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return([]os.DirEntry{ + &fakeDirEntry{ + name: "lib.so.1", + }, + }, nil) + mm.EXPECT().Stat("/usr/lib64/lib.so.1").Return(&fakeFileInfo{name: "lib.so.1", isDir: false}, nil) + mm.EXPECT().MkdirAll("/.test/usr/lib64", os.FileMode(0o750)).Times(1).Return(nil) + mm.EXPECT().OpenFile("/.test/usr/lib64/lib.so.1", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil) + mm.EXPECT().Mount("/usr/lib64/lib.so.1", "/.test/usr/lib64/lib.so.1", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) + mm.EXPECT().Unmount("/usr/lib64/lib.so.1", 0).Times(1).Return(nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/.test/usr/lib64/lib.so.1").Return(&fakeFileInfo{name: "lib.so.1", isDir: false}, nil) + mm.EXPECT().MkdirAll("/usr/lib64", os.FileMode(0o750)).Times(1).Return(nil) + mm.EXPECT().OpenFile("/usr/lib64/lib.so.1", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil) + mm.EXPECT().Mount("/.test/usr/lib64/lib.so.1", "/usr/lib64/lib.so.1", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) + mm.EXPECT().Unmount("/.test/usr/lib64/lib.so.1", 0).Times(1).Return(nil) + + remount, err := tempRemount(mm, fakeLog(t), "/.test") + require.NoError(t, err) + err = remount() + require.NoError(t, err) + // sync.Once should handle multiple remount calls + _ = remount() + }) + t.Run("IgnorePrefixes", func(t *testing.T) { t.Parallel() @@ -78,6 +286,8 @@ func Test_tempRemount(t *testing.T) { mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys") mm.EXPECT().GetMounts().Return(mounts, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return(nil, os.ErrNotExist) remount, err := tempRemount(mm, fakeLog(t), "/.test", "/var/lib") require.NoError(t, err) @@ -97,6 +307,39 @@ func Test_tempRemount(t *testing.T) { require.NoError(t, err) }) + t.Run("ErrStatDebianVersion", func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + mm := NewMockmounter(ctrl) + mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys") + + mm.EXPECT().GetMounts().Return(mounts, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, assert.AnError) + + remount, err := tempRemount(mm, fakeLog(t), "/.test") + require.ErrorContains(t, err, assert.AnError.Error()) + err = remount() + require.NoError(t, err) + }) + + t.Run("ErrReadLibDir", func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + mm := NewMockmounter(ctrl) + mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys") + + mm.EXPECT().GetMounts().Return(mounts, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return(nil, assert.AnError) + + remount, err := tempRemount(mm, fakeLog(t), "/.test") + require.ErrorContains(t, err, assert.AnError.Error()) + err = remount() + require.NoError(t, err) + }) + t.Run("ErrMkdirAll", func(t *testing.T) { t.Parallel() @@ -105,7 +348,9 @@ func Test_tempRemount(t *testing.T) { mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys") mm.EXPECT().GetMounts().Return(mounts, nil) - mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{name: "modules", isDir: true}, nil) mm.EXPECT().MkdirAll("/.test/var/lib/modules", os.FileMode(0o750)).Times(1).Return(assert.AnError) remount, err := tempRemount(mm, fakeLog(t), "/.test") @@ -114,6 +359,69 @@ func Test_tempRemount(t *testing.T) { require.NoError(t, err) }) + t.Run("ErrOpenFile", func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + mm := NewMockmounter(ctrl) + mounts := fakeMounts("/home", "/usr/bin/utility:ro", "/proc", "/sys") + + mm.EXPECT().GetMounts().Return(mounts, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/usr/bin/utility").Return(&fakeFileInfo{name: "modules", isDir: false}, nil) + mm.EXPECT().MkdirAll("/.test/usr/bin", os.FileMode(0o750)).Times(1).Return(nil) + mm.EXPECT().OpenFile("/.test/usr/bin/utility", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(nil, assert.AnError) + + remount, err := tempRemount(mm, fakeLog(t), "/.test") + require.ErrorContains(t, err, assert.AnError.Error()) + err = remount() + require.NoError(t, err) + }) + + t.Run("ErrMoveSymlink", func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + mm := NewMockmounter(ctrl) + mounts := fakeMounts("/home", "/usr/lib64/lib.so.1:ro", "/proc", "/sys") + + mm.EXPECT().GetMounts().Return(mounts, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return([]os.DirEntry{ + &fakeDirEntry{ + name: "lib.so", + mode: os.ModeSymlink, + }, + &fakeDirEntry{ + name: "lib.so.1", + }, + &fakeDirEntry{ + name: "lib-other.so", + mode: os.ModeSymlink, + }, + &fakeDirEntry{ + name: "lib-other.so.1", + }, + &fakeDirEntry{ + name: "something.d", + isDir: true, + mode: os.ModeDir, + }, + }, nil) + mm.EXPECT().EvalSymlinks("/usr/lib64/lib.so").Return("lib.so.1", nil) + mm.EXPECT().EvalSymlinks("/usr/lib64/lib-other.so").Return("lib-other.so.1", nil) + mm.EXPECT().Stat("/usr/lib64/lib.so.1").Return(&fakeFileInfo{name: "lib.so.1", isDir: false}, nil) + mm.EXPECT().MkdirAll("/.test/usr/lib64", os.FileMode(0o750)).Times(1).Return(nil) + mm.EXPECT().OpenFile("/.test/usr/lib64/lib.so.1", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil) + mm.EXPECT().Rename("/usr/lib64/lib.so", "/.test/usr/lib64/lib.so").Return(assert.AnError) + + remount, err := tempRemount(mm, fakeLog(t), "/.test") + require.ErrorContains(t, err, assert.AnError.Error()) + err = remount() + require.NoError(t, err) + }) + t.Run("ErrMountBind", func(t *testing.T) { t.Parallel() @@ -122,7 +430,9 @@ func Test_tempRemount(t *testing.T) { mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys") mm.EXPECT().GetMounts().Return(mounts, nil) - mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{name: "modules", isDir: true}, nil) mm.EXPECT().MkdirAll("/.test/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil) mm.EXPECT().Mount("/var/lib/modules", "/.test/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(assert.AnError) @@ -140,7 +450,9 @@ func Test_tempRemount(t *testing.T) { mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys") mm.EXPECT().GetMounts().Return(mounts, nil) - mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{name: "modules", isDir: true}, nil) mm.EXPECT().MkdirAll("/.test/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil) mm.EXPECT().Mount("/var/lib/modules", "/.test/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) mm.EXPECT().Unmount("/var/lib/modules", 0).Times(1).Return(assert.AnError) @@ -151,6 +463,28 @@ func Test_tempRemount(t *testing.T) { require.NoError(t, err) }) + t.Run("ErrRemountStatDebianVersion", func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + mm := NewMockmounter(ctrl) + mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys") + + mm.EXPECT().GetMounts().Return(mounts, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{name: "modules", isDir: true}, nil) + mm.EXPECT().MkdirAll("/.test/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil) + mm.EXPECT().Mount("/var/lib/modules", "/.test/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) + mm.EXPECT().Unmount("/var/lib/modules", 0).Times(1).Return(nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, assert.AnError) + + remount, err := tempRemount(mm, fakeLog(t), "/.test") + require.NoError(t, err) + err = remount() + require.ErrorContains(t, err, assert.AnError.Error()) + }) + t.Run("ErrRemountMkdirAll", func(t *testing.T) { t.Parallel() @@ -159,11 +493,14 @@ func Test_tempRemount(t *testing.T) { mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys") mm.EXPECT().GetMounts().Return(mounts, nil) - mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{name: "modules", isDir: true}, nil) mm.EXPECT().MkdirAll("/.test/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil) mm.EXPECT().Mount("/var/lib/modules", "/.test/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) mm.EXPECT().Unmount("/var/lib/modules", 0).Times(1).Return(nil) - mm.EXPECT().Stat("/.test/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/.test/var/lib/modules").Return(&fakeFileInfo{name: "modules", isDir: true}, nil) mm.EXPECT().MkdirAll("/var/lib/modules", os.FileMode(0o750)).Times(1).Return(assert.AnError) remount, err := tempRemount(mm, fakeLog(t), "/.test") @@ -172,6 +509,82 @@ func Test_tempRemount(t *testing.T) { require.ErrorContains(t, err, assert.AnError.Error()) }) + t.Run("ErrRemountOpenFile", func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + mm := NewMockmounter(ctrl) + mounts := fakeMounts("/home", "/usr/bin/utility:ro", "/proc", "/sys") + + mm.EXPECT().GetMounts().Return(mounts, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/usr/bin/utility").Return(&fakeFileInfo{name: "modules", isDir: false}, nil) + mm.EXPECT().MkdirAll("/.test/usr/bin", os.FileMode(0o750)).Times(1).Return(nil) + mm.EXPECT().OpenFile("/.test/usr/bin/utility", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil) + mm.EXPECT().Mount("/usr/bin/utility", "/.test/usr/bin/utility", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) + mm.EXPECT().Unmount("/usr/bin/utility", 0).Times(1).Return(nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/.test/usr/bin/utility").Return(&fakeFileInfo{name: "modules", isDir: false}, nil) + mm.EXPECT().MkdirAll("/usr/bin", os.FileMode(0o750)).Times(1).Return(nil) + mm.EXPECT().OpenFile("/usr/bin/utility", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(nil, assert.AnError) + + remount, err := tempRemount(mm, fakeLog(t), "/.test") + require.NoError(t, err) + err = remount() + require.ErrorContains(t, err, assert.AnError.Error()) + }) + + t.Run("ErrRemountMoveSymlink", func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + mm := NewMockmounter(ctrl) + mounts := fakeMounts("/home", "/usr/lib64/lib.so.1:ro", "/proc", "/sys") + + mm.EXPECT().GetMounts().Return(mounts, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return([]os.DirEntry{ + &fakeDirEntry{ + name: "lib.so", + mode: os.ModeSymlink, + }, + &fakeDirEntry{ + name: "lib.so.1", + }, + &fakeDirEntry{ + name: "lib-other.so", + mode: os.ModeSymlink, + }, + &fakeDirEntry{ + name: "lib-other.so.1", + }, + &fakeDirEntry{ + name: "something.d", + isDir: true, + mode: os.ModeDir, + }, + }, nil) + mm.EXPECT().EvalSymlinks("/usr/lib64/lib.so").Return("/usr/lib64/lib.so.1", nil) + mm.EXPECT().EvalSymlinks("/usr/lib64/lib-other.so").Return("/usr/lib64/lib-other.so.1", nil) + mm.EXPECT().Stat("/usr/lib64/lib.so.1").Return(&fakeFileInfo{name: "lib.so.1", isDir: false}, nil) + mm.EXPECT().MkdirAll("/.test/usr/lib64", os.FileMode(0o750)).Times(1).Return(nil) + mm.EXPECT().OpenFile("/.test/usr/lib64/lib.so.1", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil) + mm.EXPECT().Rename("/usr/lib64/lib.so", "/.test/usr/lib64/lib.so").Return(nil) + mm.EXPECT().Mount("/usr/lib64/lib.so.1", "/.test/usr/lib64/lib.so.1", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) + mm.EXPECT().Unmount("/usr/lib64/lib.so.1", 0).Times(1).Return(nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/.test/usr/lib64/lib.so.1").Return(&fakeFileInfo{name: "lib.so.1", isDir: false}, nil) + mm.EXPECT().MkdirAll("/usr/lib64", os.FileMode(0o750)).Times(1).Return(nil) + mm.EXPECT().OpenFile("/usr/lib64/lib.so.1", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil) + mm.EXPECT().Rename("/.test/usr/lib64/lib.so", "/usr/lib64/lib.so").Return(assert.AnError) + + remount, err := tempRemount(mm, fakeLog(t), "/.test") + require.NoError(t, err) + err = remount() + require.ErrorContains(t, err, assert.AnError.Error()) + }) + t.Run("ErrRemountMountBind", func(t *testing.T) { t.Parallel() @@ -180,11 +593,14 @@ func Test_tempRemount(t *testing.T) { mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys") mm.EXPECT().GetMounts().Return(mounts, nil) - mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{name: "modules", isDir: true}, nil) mm.EXPECT().MkdirAll("/.test/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil) mm.EXPECT().Mount("/var/lib/modules", "/.test/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) mm.EXPECT().Unmount("/var/lib/modules", 0).Times(1).Return(nil) - mm.EXPECT().Stat("/.test/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/.test/var/lib/modules").Return(&fakeFileInfo{name: "modules", isDir: true}, nil) mm.EXPECT().MkdirAll("/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil) mm.EXPECT().Mount("/.test/var/lib/modules", "/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(assert.AnError) @@ -202,11 +618,14 @@ func Test_tempRemount(t *testing.T) { mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys") mm.EXPECT().GetMounts().Return(mounts, nil) - mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().ReadDir("/usr/lib64").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{name: "modules", isDir: true}, nil) mm.EXPECT().MkdirAll("/.test/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil) mm.EXPECT().Mount("/var/lib/modules", "/.test/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) mm.EXPECT().Unmount("/var/lib/modules", 0).Times(1).Return(nil) - mm.EXPECT().Stat("/.test/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil) + mm.EXPECT().Stat("/etc/debian_version").Return(nil, os.ErrNotExist) + mm.EXPECT().Stat("/.test/var/lib/modules").Return(&fakeFileInfo{name: "modules", isDir: true}, nil) mm.EXPECT().MkdirAll("/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil) mm.EXPECT().Mount("/.test/var/lib/modules", "/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil) mm.EXPECT().Unmount("/.test/var/lib/modules", 0).Times(1).Return(assert.AnError) @@ -241,10 +660,11 @@ func fakeLog(t *testing.T) func(notcodersdk.LogLevel, string, ...any) { } type fakeFileInfo struct { + name string isDir bool } -func (fi *fakeFileInfo) Name() string { return "" } +func (fi *fakeFileInfo) Name() string { return fi.name } func (fi *fakeFileInfo) Size() int64 { return 0 } func (fi *fakeFileInfo) Mode() os.FileMode { return 0 } func (fi *fakeFileInfo) ModTime() time.Time { return time.Time{} } @@ -252,3 +672,16 @@ func (fi *fakeFileInfo) IsDir() bool { return fi.isDir } func (fi *fakeFileInfo) Sys() any { return nil } var _ os.FileInfo = &fakeFileInfo{} + +type fakeDirEntry struct { + name string + isDir bool + mode os.FileMode +} + +func (de *fakeDirEntry) Name() string { return de.name } +func (de *fakeDirEntry) IsDir() bool { return de.isDir } +func (de *fakeDirEntry) Type() os.FileMode { return de.mode } +func (de *fakeDirEntry) Info() (os.FileInfo, error) { return nil, nil } + +var _ os.DirEntry = &fakeDirEntry{}