From d6a9dff88943d2512dc4fda426b5862834aafe0b Mon Sep 17 00:00:00 2001 From: Achille Roussel Date: Mon, 28 Aug 2023 14:40:14 -0700 Subject: [PATCH 1/2] ocifs: read-write layers Signed-off-by: Achille Roussel --- internal/sandbox/dirfs_unix.go | 2 +- internal/sandbox/fs.go | 7 +- internal/sandbox/ocifs/file.go | 195 +++++++++++++++++---------- internal/sandbox/ocifs/ocifs.go | 12 +- internal/sandbox/ocifs/ocifs_test.go | 9 ++ 5 files changed, 147 insertions(+), 78 deletions(-) diff --git a/internal/sandbox/dirfs_unix.go b/internal/sandbox/dirfs_unix.go index b0b70954..6bd03996 100644 --- a/internal/sandbox/dirfs_unix.go +++ b/internal/sandbox/dirfs_unix.go @@ -143,7 +143,7 @@ func (f *dirFile) openFile(name string, flags OpenFlags, mode fs.FileMode) (File } func (f *dirFile) Open(name string, flags OpenFlags, mode fs.FileMode) (File, error) { - return FileOpen(f, name, flags, ^OpenFlags(0), mode, + return FileOpen(f, name, flags, mode, (*dirFile).openRoot, (*dirFile).openSelf, (*dirFile).openParent, diff --git a/internal/sandbox/fs.go b/internal/sandbox/fs.go index 0bf21d71..2e8c3080 100644 --- a/internal/sandbox/fs.go +++ b/internal/sandbox/fs.go @@ -523,8 +523,7 @@ type File interface { // common parts of the Open logic, for example: // // func (f *file) Open(name string, flags OpenFlags, mode fs.FileMode) (File, error) { -// const supportedFlags = ^OpenFlags(0) -// return FileOpen(f, name, flags, supportedFlags, mode, +// return FileOpen(f, name, flags, mode, // (*file).openRoot, // (*file).openSelf, // (*file).openParent, @@ -535,13 +534,13 @@ type File interface { // The function performs path resolution and invokes one of the callbacks passed // as arguments to navigate the directory tree and eventually open and return the // final target. -func FileOpen[F File](f F, name string, flags, supportedFlags OpenFlags, mode fs.FileMode, +func FileOpen[F File](f F, name string, flags OpenFlags, mode fs.FileMode, openRoot func(F) (File, error), openSelf func(F) (File, error), openParent func(F) (File, error), openFile func(F, string, OpenFlags, fs.FileMode) (File, error), ) (File, error) { - if (flags & ^supportedFlags) != 0 || name == "" { + if name == "" { return nil, EINVAL } if fspath.IsRoot(name) { diff --git a/internal/sandbox/ocifs/file.go b/internal/sandbox/ocifs/file.go index 236ff8a0..f4e1a8a1 100644 --- a/internal/sandbox/ocifs/file.go +++ b/internal/sandbox/ocifs/file.go @@ -176,11 +176,7 @@ func hasWhiteout(file sandbox.File, whiteout string) (has bool, err error) { } func (f *file) Open(name string, flags sandbox.OpenFlags, mode fs.FileMode) (sandbox.File, error) { - const unsupportedFlags = sandbox.O_CREAT | - sandbox.O_APPEND | - sandbox.O_RDWR | - sandbox.O_WRONLY - return sandbox.FileOpen(f, name, flags, ^unsupportedFlags, mode, + return sandbox.FileOpen(f, name, flags, mode, (*file).openRoot, (*file).openSelf, (*file).openParent, @@ -190,51 +186,55 @@ func (f *file) Open(name string, flags sandbox.OpenFlags, mode fs.FileMode) (san func (f *file) Stat(name string, flags sandbox.LookupFlags) (sandbox.FileInfo, error) { return sandbox.FileStat(f, name, flags, func(at *file, name string) (sandbox.FileInfo, error) { - whiteout := whiteoutPrefix + name + return withLayers2(at, func(l *fileLayers) (sandbox.FileInfo, error) { + whiteout := whiteoutPrefix + name - for _, file := range at.layers.files { - info, err := file.Stat(name, sandbox.AT_SYMLINK_NOFOLLOW) - if err != nil { - if !errors.Is(err, sandbox.ENOENT) { - return sandbox.FileInfo{}, err + for _, file := range l.files { + info, err := file.Stat(name, sandbox.AT_SYMLINK_NOFOLLOW) + if err != nil { + if !errors.Is(err, sandbox.ENOENT) { + return sandbox.FileInfo{}, err + } + } else { + return info, err } - } else { - return info, err - } - if wh, err := hasWhiteout(file, whiteout); err != nil { - return info, err - } else if wh { - break + if wh, err := hasWhiteout(file, whiteout); err != nil { + return info, err + } else if wh { + break + } } - } - return sandbox.FileInfo{}, sandbox.ENOENT + return sandbox.FileInfo{}, sandbox.ENOENT + }) }) } func (f *file) Readlink(name string, buf []byte) (int, error) { return sandbox.FileReadlink(f, name, func(at *file, name string) (int, error) { - whiteout := whiteoutPrefix + name + return withLayers2(at, func(l *fileLayers) (int, error) { + whiteout := whiteoutPrefix + name - for _, file := range at.layers.files { - n, err := file.Readlink(name, buf) - if err != nil { - if !errors.Is(err, sandbox.ENOENT) { - return 0, err + for _, file := range l.files { + n, err := file.Readlink(name, buf) + if err != nil { + if !errors.Is(err, sandbox.ENOENT) { + return 0, err + } + } else { + return n, nil } - } else { - return n, nil - } - if wh, err := hasWhiteout(file, whiteout); err != nil { - return 0, err - } else if wh { - break + if wh, err := hasWhiteout(file, whiteout); err != nil { + return 0, err + } else if wh { + break + } } - } - return 0, sandbox.ENOENT + return 0, sandbox.ENOENT + }) }) } @@ -254,7 +254,9 @@ func (f *file) Readv(iovs [][]byte) (int, error) { } func (f *file) Writev(iovs [][]byte) (int, error) { - return 0, sandbox.EBADF + return withLayers2(f, func(l *fileLayers) (int, error) { + return l.files[0].Writev(iovs) + }) } func (f *file) Preadv(iovs [][]byte, offset int64) (int, error) { @@ -264,7 +266,9 @@ func (f *file) Preadv(iovs [][]byte, offset int64) (int, error) { } func (f *file) Pwritev(iovs [][]byte, offset int64) (int, error) { - return 0, sandbox.EBADF + return withLayers2(f, func(l *fileLayers) (int, error) { + return l.files[0].Pwritev(iovs, offset) + }) } func (f *file) CopyRange(srcOffset int64, dst sandbox.File, dstOffset int64, length int) (int, error) { @@ -290,28 +294,40 @@ func (f *file) Seek(offset int64, whence int) (int64, error) { }) } -func (f *file) Allocate(int64, int64) error { - return sandbox.EBADF +func (f *file) Allocate(offset, length int64) error { + return withLayers1(f, func(l *fileLayers) error { + return l.files[0].Allocate(offset, length) + }) } -func (f *file) Truncate(int64) error { - return sandbox.EBADF +func (f *file) Truncate(size int64) error { + return withLayers1(f, func(l *fileLayers) error { + return l.files[0].Truncate(size) + }) } func (f *file) Sync() error { - return nil + return withLayers1(f, func(l *fileLayers) error { + return l.files[0].Sync() + }) } func (f *file) Datasync() error { - return nil + return withLayers1(f, func(l *fileLayers) error { + return l.files[0].Datasync() + }) } func (f *file) Flags() (sandbox.OpenFlags, error) { - return 0, nil + return withLayers2(f, func(l *fileLayers) (sandbox.OpenFlags, error) { + return l.files[0].Flags() + }) } -func (f *file) SetFlags(sandbox.OpenFlags) error { - return sandbox.EINVAL +func (f *file) SetFlags(flags sandbox.OpenFlags) error { + return withLayers1(f, func(l *fileLayers) error { + return l.files[0].SetFlags(flags) + }) } func (f *file) ReadDirent(buf []byte) (int, error) { @@ -326,45 +342,84 @@ func (f *file) ReadDirent(buf []byte) (int, error) { }) } -func (f *file) Chtimes(string, [2]sandbox.Timespec, sandbox.LookupFlags) error { - return sandbox.EPERM +func (f *file) Chtimes(name string, times [2]sandbox.Timespec, flags sandbox.LookupFlags) error { + return f.resolvePath(name, flags, func(at *file, name string) error { + return withLayers1(at, func(l *fileLayers) error { + return l.files[0].Chtimes(name, times, sandbox.AT_SYMLINK_NOFOLLOW) + }) + }) } -func (f *file) Mkdir(string, fs.FileMode) error { - return f.expectDirectory() +func (f *file) Mkdir(name string, mode fs.FileMode) error { + return f.resolvePath(name, 0, func(at *file, name string) error { + return withLayers1(at, func(l *fileLayers) error { + return l.files[0].Mkdir(name, mode) + }) + }) } -func (f *file) Rmdir(string) error { - return f.expectDirectory() +func (f *file) Rmdir(name string) error { + return f.resolvePath(name, 0, func(at *file, name string) error { + return withLayers1(at, func(l *fileLayers) error { + return l.files[0].Rmdir(name) + }) + }) } -func (f *file) Rename(string, sandbox.File, string, sandbox.RenameFlags) error { - return f.expectDirectory() +func (f *file) Rename(oldName string, newDir sandbox.File, newName string, flags sandbox.RenameFlags) error { + d, ok := newDir.(*file) + if !ok { + return sandbox.EXDEV + } + return f.resolvePath(oldName, 0, func(f1 *file, name1 string) error { + return d.resolvePath(newName, 0, func(f2 *file, name2 string) error { + return withLayers1(f1, func(l1 *fileLayers) error { + return withLayers1(f2, func(l2 *fileLayers) error { + return l1.files[0].Rename(name1, l2.files[0], name2, flags) + }) + }) + }) + }) } -func (f *file) Link(string, sandbox.File, string, sandbox.LookupFlags) error { - return f.expectDirectory() +func (f *file) Link(oldName string, newDir sandbox.File, newName string, flags sandbox.LookupFlags) error { + d, ok := newDir.(*file) + if !ok { + return sandbox.EXDEV + } + return f.resolvePath(oldName, flags, func(f1 *file, name1 string) error { + return d.resolvePath(newName, flags, func(f2 *file, name2 string) error { + return withLayers1(f1, func(l1 *fileLayers) error { + return withLayers1(f2, func(l2 *fileLayers) error { + return l1.files[0].Link(name1, l2.files[0], name2, sandbox.AT_SYMLINK_NOFOLLOW) + }) + }) + }) + }) } -func (f *file) Symlink(string, string) error { - return f.expectDirectory() +func (f *file) Symlink(oldName, newName string) error { + return f.resolvePath(newName, 0, func(at *file, name string) error { + return withLayers1(at, func(l *fileLayers) error { + return l.files[0].Symlink(oldName, name) + }) + }) } -func (f *file) Unlink(string) error { - return f.expectDirectory() +func (f *file) Unlink(name string) error { + return f.resolvePath(name, 0, func(at *file, name string) error { + return withLayers1(at, func(l *fileLayers) error { + return l.files[0].Unlink(name) + }) + }) } -func (f *file) expectDirectory() error { - return withLayers1(f, func(l *fileLayers) error { - info, err := l.files[0].Stat("", sandbox.AT_SYMLINK_NOFOLLOW) - if err != nil { - return err - } - if !info.Mode.IsDir() { - return sandbox.ENOTDIR - } - return sandbox.EROFS +func (f *file) resolvePath(name string, flags sandbox.LookupFlags, do func(*file, string) error) error { + _, err := sandbox.ResolvePath(f, name, flags, func(at *file, name string) (_ struct{}, err error) { + err = do(at, name) + return }) + return err } func withLayers1(f *file, do func(*fileLayers) error) error { diff --git a/internal/sandbox/ocifs/ocifs.go b/internal/sandbox/ocifs/ocifs.go index cd09385d..da787762 100644 --- a/internal/sandbox/ocifs/ocifs.go +++ b/internal/sandbox/ocifs/ocifs.go @@ -17,9 +17,15 @@ type FileSystem struct { // New constructs a file system which combines layers into a flattened view // which stacks layers on each other. // -// The returned file system is read-only, it does not allow modifications of -// the layers that it was constructed from. This includes writes to files as -// well as modifcations of the directory structure. +// The returned file system delegates file and directory operations to the +// layers that it is composed of. If a layer is read-only, attempting to mutate +// its directory structure of file(s) content will be forbidden, but if a layer +// allows mutations, the application may create, update, or remove entries in +// that layer. Keep in mind that mutating layers can create unexpected +// situations such as files from lower layers "reappearing" after an entry was +// deleted in an upper layer; for this reason, it is often preferrable to mask +// mutable parts of the file system using an opaque layer (such as a file system +// returned by ocifs.Opaque). // // For the OCI layer specification, see: // https://github.com/opencontainers/image-spec/blob/main/layer.md diff --git a/internal/sandbox/ocifs/ocifs_test.go b/internal/sandbox/ocifs/ocifs_test.go index b08b24ae..19301b17 100644 --- a/internal/sandbox/ocifs/ocifs_test.go +++ b/internal/sandbox/ocifs/ocifs_test.go @@ -59,6 +59,15 @@ func TestOciFS(t *testing.T) { sandboxtest.TestRootFS(t, test.makeFS) }) } + + t.Run("sandbox.FileSystem", func(t *testing.T) { + sandboxtest.TestFileSystem(t, func(t *testing.T) sandbox.FileSystem { + return ocifs.New( + sandbox.DirFS(t.TempDir()), // empty layer, should be ignored by the test + sandbox.DirFS(t.TempDir()), // layer receiving mutations during the test + ) + }) + }) } func TestOciFSLayers(t *testing.T) { From 8ff36abb5a7664e2138939dab9c1d4763e69969e Mon Sep 17 00:00:00 2001 From: Achille Roussel Date: Mon, 28 Aug 2023 14:53:38 -0700 Subject: [PATCH 2/2] fix build Signed-off-by: Achille Roussel --- internal/sandbox/subfs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/sandbox/subfs.go b/internal/sandbox/subfs.go index a51b6a04..67b334f1 100644 --- a/internal/sandbox/subfs.go +++ b/internal/sandbox/subfs.go @@ -130,7 +130,7 @@ func (f *subFile) openFile(name string, flags OpenFlags, mode fs.FileMode) (File } func (f *subFile) Open(name string, flags OpenFlags, mode fs.FileMode) (File, error) { - return FileOpen(f, name, flags, ^OpenFlags(0), mode, + return FileOpen(f, name, flags, mode, (*subFile).openRoot, (*subFile).openSelf, (*subFile).openParent,