Skip to content
This repository has been archived by the owner on Feb 17, 2024. It is now read-only.

ocifs: read-write layers #231

Merged
merged 2 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/sandbox/dirfs_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 3 additions & 4 deletions internal/sandbox/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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) {
Expand Down
195 changes: 125 additions & 70 deletions internal/sandbox/ocifs/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
})
})
}

Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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 {
Expand Down
12 changes: 9 additions & 3 deletions internal/sandbox/ocifs/ocifs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions internal/sandbox/ocifs/ocifs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion internal/sandbox/subfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading