New Upstream Release - golang-github-bep-overlayfs
Ready changes
Summary
Merged new upstream version: 0.8.0 (was: 0.6.0).
Resulting package
Built on 2023-01-20T11:45 (took 3m24s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases golang-github-bep-overlayfs-dev
Lintian Result
Diff
diff --git a/debian/changelog b/debian/changelog
index 492f3a5..0d240e8 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-bep-overlayfs (0.8.0-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Fri, 20 Jan 2023 11:42:51 -0000
+
golang-github-bep-overlayfs (0.6.0-2) unstable; urgency=medium
* Source-only upload for migration to testing
diff --git a/go.mod b/go.mod
index dab02d8..bb96eee 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.18
require (
github.com/frankban/quicktest v1.14.2
- github.com/spf13/afero v1.8.2
+ github.com/spf13/afero v1.9.0
golang.org/x/tools v0.1.0
)
@@ -13,6 +13,6 @@ require (
github.com/kr/pretty v0.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.6.1 // indirect
- golang.org/x/text v0.3.4 // indirect
+ golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
)
diff --git a/go.sum b/go.sum
index 68a2aa1..9c62a55 100644
--- a/go.sum
+++ b/go.sum
@@ -137,8 +137,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
-github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
+github.com/spf13/afero v1.9.0 h1:sFSLUHgxdnN32Qy38hK3QkYBFXZj9DKjVjCUCtD7juY=
+github.com/spf13/afero v1.9.0/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -283,8 +283,9 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/overlayfs.go b/overlayfs.go
index 42cca0a..f88b1e5 100644
--- a/overlayfs.go
+++ b/overlayfs.go
@@ -2,6 +2,8 @@ package overlayfs
import (
"io"
+ "io/fs"
+ iofs "io/fs"
"os"
"sync"
@@ -13,6 +15,7 @@ var (
_ afero.Fs = (*OverlayFs)(nil)
_ afero.Lstater = (*OverlayFs)(nil)
_ afero.File = (*Dir)(nil)
+ _ fs.ReadDirFile = (*Dir)(nil)
)
// FilesystemIterator is an interface for iterating over the wrapped filesystems in order.
@@ -21,6 +24,7 @@ type FilesystemIterator interface {
NumFilesystems() int
}
+// Options for the OverlayFs.
type Options struct {
// The filesystems to overlay ordered in priority from left to right.
Fss []afero.Fs
@@ -144,9 +148,9 @@ func (ofs *OverlayFs) writeFs() afero.Fs {
}
// DirsMerger is used to merge two directories.
-type DirsMerger func(lofi, bofi []os.FileInfo) []os.FileInfo
+type DirsMerger func(lofi, bofi []fs.DirEntry) []fs.DirEntry
-var defaultDirMerger = func(lofi, bofi []os.FileInfo) []os.FileInfo {
+var defaultDirMerger = func(lofi, bofi []fs.DirEntry) []fs.DirEntry {
for _, bofi := range bofi {
var found bool
for _, lofi := range lofi {
@@ -160,7 +164,6 @@ var defaultDirMerger = func(lofi, bofi []os.FileInfo) []os.FileInfo {
}
}
return lofi
-
}
var dirPool = &sync.Pool{
@@ -176,50 +179,114 @@ func getDir() *Dir {
func releaseDir(dir *Dir) {
dir.fss = dir.fss[:0]
dir.fis = dir.fis[:0]
+ dir.dirOpeners = dir.dirOpeners[:0]
dir.offset = 0
dir.name = ""
dir.err = nil
dirPool.Put(dir)
}
+// OpenDir opens a new Dir with dirs to be merged by the given merge func.
+// If merge is nil, a default DirsMerger is used.
+func OpenDir(merge DirsMerger, dirOpeners ...func() (afero.File, error)) (*Dir, error) {
+ if merge == nil {
+ merge = defaultDirMerger
+ }
+ dir := getDir()
+ dir.dirOpeners = dirOpeners
+ dir.merge = merge
+ return dir, nil
+}
+
// Dir is an afero.File that represents list of directories that will be merged in Readdir and Readdirnames.
type Dir struct {
- name string
- fss []afero.Fs
+ // It's either a named directory in a slice of filesystems or a slice of directories.
+ name string
+ fss []afero.Fs
+
+ // Set if fss is not set.
+ dirOpeners []func() (afero.File, error)
+
merge DirsMerger
err error
offset int
- fis []os.FileInfo
+ fis []fs.DirEntry
}
// Readdir implements afero.File.Readdir.
// If n > 0, Readdir returns at most n.
+// Note that Dir also implements fs.ReadDirFile, which is more efficient.
func (d *Dir) Readdir(n int) ([]os.FileInfo, error) {
+ dirEntries, err := d.ReadDir(n)
+ if err != nil {
+ return nil, err
+ }
+ fis := make([]os.FileInfo, len(dirEntries))
+ for i, dirEntry := range dirEntries {
+ fi, err := dirEntry.Info()
+ if err != nil {
+ return nil, err
+ }
+ fis[i] = fi
+ }
+ return fis, nil
+}
+
+// ReadDir implements fs.ReadDirFile.
+func (d *Dir) ReadDir(n int) ([]fs.DirEntry, error) {
if d.err != nil {
return nil, d.err
}
- if len(d.fss) == 0 {
+ if d.isClosed() {
return nil, os.ErrClosed
}
if d.offset == 0 {
- readDir := func(fs afero.Fs) error {
- f, err := fs.Open(d.name)
- if err != nil {
- return err
+ readDir := func(fs afero.Fs, f afero.File) error {
+ var err error
+ if f == nil {
+ f, err = fs.Open(d.name)
+ if err != nil {
+ return err
+ }
}
defer f.Close()
- fi, err := f.Readdir(-1)
- if err != nil {
- return err
+
+ var dirEntries []iofs.DirEntry
+
+ if rdf, ok := f.(iofs.ReadDirFile); ok {
+ dirEntries, err = rdf.ReadDir(-1)
+ if err != nil {
+ return err
+ }
+ } else {
+ var fis []os.FileInfo
+ fis, err = f.Readdir(-1)
+ if err != nil {
+ return err
+ }
+ dirEntries = make([]iofs.DirEntry, len(fis))
+ for i, fi := range fis {
+ dirEntries[i] = dirEntry{fi}
+ }
}
- d.fis = d.merge(d.fis, fi)
+
+ d.fis = d.merge(d.fis, dirEntries)
return nil
}
for _, fs := range d.fss {
- if err := readDir(fs); err != nil {
+ if err := readDir(fs, nil); err != nil {
+ return nil, err
+ }
+ }
+ for _, open := range d.dirOpeners {
+ f, err := open()
+ if err != nil {
+ return nil, err
+ }
+ if err := readDir(nil, f); err != nil {
return nil, err
}
}
@@ -232,7 +299,7 @@ func (d *Dir) Readdir(n int) ([]os.FileInfo, error) {
if d.offset > 0 && len(fis) == 0 {
return nil, d.err
}
- fisc := make([]os.FileInfo, len(fis))
+ fisc := make([]fs.DirEntry, len(fis))
copy(fisc, fis)
return fisc, nil
}
@@ -248,21 +315,20 @@ func (d *Dir) Readdir(n int) ([]os.FileInfo, error) {
defer func() { d.offset += n }()
- fisc := make([]os.FileInfo, len(fis[:n]))
+ fisc := make([]fs.DirEntry, len(fis[:n]))
copy(fisc, fis[:n])
return fisc, nil
-
}
// Readdirnames implements afero.File.Readdirnames.
// If n > 0, Readdirnames returns at most n.
func (d *Dir) Readdirnames(n int) ([]string, error) {
- if len(d.fss) == 0 {
+ if d.isClosed() {
return nil, os.ErrClosed
}
- fis, err := d.Readdir(n)
+ fis, err := d.ReadDir(n)
if err != nil {
return nil, err
}
@@ -334,3 +400,18 @@ func (d *Dir) Truncate(size int64) error {
func (d *Dir) WriteString(s string) (ret int, err error) {
panic("not supported")
}
+
+func (d *Dir) isClosed() bool {
+ return len(d.fss) == 0 && len(d.dirOpeners) == 0
+}
+
+// dirEntry is an adapter from os.FileInfo to fs.DirEntry
+type dirEntry struct {
+ fs.FileInfo
+}
+
+var _ fs.DirEntry = dirEntry{}
+
+func (d dirEntry) Type() fs.FileMode { return d.FileInfo.Mode().Type() }
+
+func (d dirEntry) Info() (fs.FileInfo, error) { return d.FileInfo, nil }
diff --git a/overlayfs_test.go b/overlayfs_test.go
index 6307dc8..75e176f 100644
--- a/overlayfs_test.go
+++ b/overlayfs_test.go
@@ -3,9 +3,11 @@ package overlayfs
import (
"bytes"
"errors"
+ "fmt"
"io"
"io/fs"
"os"
+ "path/filepath"
"sort"
"strings"
"testing"
@@ -54,15 +56,40 @@ func TestFileystemIterator(t *testing.T) {
c.Assert(ofs.Filesystem(2), qt.IsNil)
}
+func TestOpenDir(t *testing.T) {
+ c := qt.New(t)
+ fs1, fs2, fs3 := basicFs("1", "1"), basicFs("1", "2"), basicFs("2", "2")
+ dir, err := OpenDir(
+ nil,
+ func() (afero.File, error) {
+ return fs1.Open("mydir")
+ },
+ func() (afero.File, error) {
+ return fs2.Open("mydir")
+ },
+ func() (afero.File, error) {
+ return fs3.Open("mydir")
+ },
+ )
+ c.Assert(err, qt.IsNil)
+
+ dirEntries, err := dir.ReadDir(-1)
+
+ c.Assert(err, qt.IsNil)
+ c.Assert(dirEntries, qt.HasLen, 4)
+ c.Assert(dir.Close(), qt.IsNil)
+}
+
func TestReadOps(t *testing.T) {
c := qt.New(t)
- fs1, fs2 := basicFs("1", "1"), basicFs("1", "2")
+ fs1, fs2 := basicFs("1", "1"), basicFs("2", "2")
ofs := New(Options{Fss: []afero.Fs{fs1, fs2}})
c.Assert(ofs.Name(), qt.Equals, "overlayfs")
// Open
c.Assert(readFile(c, ofs, "mydir/f1-1.txt"), qt.Equals, "f1-1")
+ c.Assert(readFile(c, ofs, "mydir/f2-2.txt"), qt.Equals, "f2-2")
// Stat
fi, err := ofs.Stat("mydir/f1-1.txt")
@@ -70,6 +97,9 @@ func TestReadOps(t *testing.T) {
c.Assert(fi.Name(), qt.Equals, "f1-1.txt")
_, err = ofs.Stat("mydir/notfound.txt")
c.Assert(err, qt.ErrorIs, fs.ErrNotExist)
+ fi, err = ofs.Stat("mydir/f2-2.txt")
+ c.Assert(err, qt.IsNil)
+ c.Assert(fi.Name(), qt.Equals, "f2-2.txt")
// LstatIfPossible
fi, _, err = ofs.LstatIfPossible("mydir/f2-1.txt")
@@ -77,7 +107,6 @@ func TestReadOps(t *testing.T) {
c.Assert(fi.Name(), qt.Equals, "f2-1.txt")
_, _, err = ofs.LstatIfPossible("mydir/notfound.txt")
c.Assert(err, qt.ErrorIs, fs.ErrNotExist)
-
}
func TestReadOpsErrors(t *testing.T) {
@@ -98,7 +127,6 @@ func TestReadOpsErrors(t *testing.T) {
c.Assert(fi.Name(), qt.Equals, "f2-1.txt")
_, _, err = ofs.LstatIfPossible("mydir/notfound.txt")
c.Assert(err, qt.ErrorIs, statErr)
-
}
func TestOpenRecursive(t *testing.T) {
@@ -111,7 +139,6 @@ func TestOpenRecursive(t *testing.T) {
c.Assert(readFile(c, ofs1, "mydir/f1-1.txt"), qt.Equals, "f1-1")
c.Assert(readFile(c, ofs1, "mydir/f1-2.txt"), qt.Equals, "f1-3")
-
}
func TestWriteOpsReadonly(t *testing.T) {
@@ -122,9 +149,9 @@ func TestWriteOpsReadonly(t *testing.T) {
_, err := ofsReadOnly.Create("mydir/foo.txt")
c.Assert(err, qt.ErrorIs, fs.ErrPermission)
- _, err = ofsReadOnly.OpenFile("mydir/foo.txt", os.O_CREATE, 0777)
+ _, err = ofsReadOnly.OpenFile("mydir/foo.txt", os.O_CREATE, 0o777)
- err = ofsReadOnly.Chmod("mydir/foo.txt", 0666)
+ err = ofsReadOnly.Chmod("mydir/foo.txt", 0o666)
c.Assert(err, qt.ErrorIs, fs.ErrPermission)
err = ofsReadOnly.Chown("mydir/foo.txt", 1, 2)
@@ -133,10 +160,10 @@ func TestWriteOpsReadonly(t *testing.T) {
err = ofsReadOnly.Chtimes("mydir/foo.txt", time.Now(), time.Now())
c.Assert(err, qt.ErrorIs, fs.ErrPermission)
- err = ofsReadOnly.Mkdir("mydir", 0777)
+ err = ofsReadOnly.Mkdir("mydir", 0o777)
c.Assert(err, qt.ErrorIs, fs.ErrPermission)
- err = ofsReadOnly.MkdirAll("mydir", 0777)
+ err = ofsReadOnly.MkdirAll("mydir", 0o777)
c.Assert(err, qt.ErrorIs, fs.ErrPermission)
err = ofsReadOnly.Remove("mydir")
@@ -159,7 +186,7 @@ func TestWriteOpsFirstWriteable(t *testing.T) {
f.Close()
}
-func TestReadDir(t *testing.T) {
+func TestReaddir(t *testing.T) {
c := qt.New(t)
fs1, fs2 := basicFs("1", "1"), basicFs("1", "2")
fs3, fs4 := basicFs("2", "3"), basicFs("1", "4")
@@ -177,7 +204,7 @@ func TestReadDir(t *testing.T) {
c.Assert(dirnames, qt.DeepEquals, []string{"f1-1.txt", "f2-1.txt"})
}
-func TestReadDirN(t *testing.T) {
+func TestReaddirN(t *testing.T) {
c := qt.New(t)
// 6 files.
ofs := New(Options{Fss: []afero.Fs{basicFs("1", "1"), basicFs("2", "2"), basicFs("3", "3")}})
@@ -224,10 +251,9 @@ func TestReadDirN(t *testing.T) {
_, err = d.Readdir(-1)
c.Assert(err, qt.ErrorIs, io.EOF)
c.Assert(d.Close(), qt.IsNil)
-
}
-func TestReadDirStable(t *testing.T) {
+func TestReaddirStable(t *testing.T) {
c := qt.New(t)
// 6 files.
@@ -256,6 +282,21 @@ func TestReadDirStable(t *testing.T) {
}
checkFi()
}
+
+func TestReadDir(t *testing.T) {
+ c := qt.New(t)
+ // 6 files.
+ ofs := New(Options{Fss: []afero.Fs{basicFs("1", "1"), basicFs("2", "2"), basicFs("3", "3")}})
+
+ d, _ := ofs.Open("mydir")
+
+ dirEntries, err := d.(fs.ReadDirFile).ReadDir(-1)
+ c.Assert(err, qt.IsNil)
+ c.Assert(len(dirEntries), qt.Equals, 6)
+ c.Assert(dirEntries[0].Name(), qt.Equals, "f1-1.txt")
+
+}
+
func TestDirOps(t *testing.T) {
c := qt.New(t)
ofs := New(Options{Fss: []afero.Fs{basicFs("1", "1"), basicFs("2", "1")}})
@@ -318,10 +359,9 @@ func fsFromTxtTar(s string) afero.Fs {
data := txtar.Parse([]byte(s))
fs := afero.NewMemMapFs()
for _, f := range data.Files {
- if err := afero.WriteFile(fs, f.Name, bytes.TrimSuffix(f.Data, []byte("\n")), 0666); err != nil {
+ if err := afero.WriteFile(fs, f.Name, bytes.TrimSuffix(f.Data, []byte("\n")), 0o666); err != nil {
panic(err)
}
-
}
return fs
}
@@ -387,39 +427,83 @@ func (fs *testFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
}
func BenchmarkOverlayFs(b *testing.B) {
- fs1, fs2 := basicFs("1", "1"), basicFs("1", "2")
- ofs := New(Options{FirstWritable: true, Fss: []afero.Fs{fs1, fs2}})
- cfs := afero.NewCopyOnWriteFs(fs2, fs1)
-
- runBenchMark := func(fs afero.Fs, b *testing.B) {
- for i := 0; i < b.N; i++ {
- _, err := afero.ReadDir(fs, "mydir")
- if err != nil {
- b.Fatal(err)
- }
- f, err := fs.Open("mydir/f1-1.txt")
- if err != nil {
- b.Fatal(err)
- }
- f.Close()
- d, err := fs.Open("mydir")
- if err != nil {
- b.Fatal(err)
- }
- d.Close()
- _, err = ofs.Stat("mydir/f1-1.txt")
- if err != nil {
+ createFs := func(dir, fileID string, numFiles int) afero.Fs {
+ fs := afero.NewMemMapFs()
+ for i := 0; i < numFiles; i++ {
+ if err := afero.WriteFile(fs, filepath.Join(dir, fmt.Sprintf("f%s-%d.txt", fileID, i)), []byte("foo"), 0o666); err != nil {
b.Fatal(err)
}
}
+ return fs
+ }
+ fs1, fs2, fs3 := createFs("mydir", "1", 10), createFs("mydir", "2", 10), createFs("mydir", "3", 10)
+ fs4, fs5 := createFs("mydir", "1", 4), createFs("myotherdir", "1", 4)
+ ofs := New(Options{FirstWritable: true, Fss: []afero.Fs{fs1, fs2, fs3, fs4, fs5}})
+
+ runBenchMark := func(name string, fn func(b *testing.B)) {
+ b.Run(name, func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ fn(b)
+ }
+ })
}
- b.Run("OverlayFs", func(b *testing.B) {
- runBenchMark(ofs, b)
+ runBenchMark("Stat", func(b *testing.B) {
+ _, err := ofs.Stat("mydir/f2-2.txt")
+ if err != nil {
+ b.Fatal(err)
+ }
})
- b.Run("CopyOnWriteFs", func(b *testing.B) {
- runBenchMark(cfs, b)
+ runBenchMark("Open file", func(b *testing.B) {
+ f, err := ofs.Open("mydir/f2-2.txt")
+ if err != nil {
+ b.Fatal(err)
+ }
+ f.Close()
})
+ runBenchMark("Open dir", func(b *testing.B) {
+ f, err := ofs.Open("mydir")
+ if err != nil {
+ b.Fatal(err)
+ }
+ f.Close()
+ })
+
+ runBenchMark("Readdir all", func(b *testing.B) {
+ f, err := ofs.Open("mydir")
+ if err != nil {
+ b.Fatal(err)
+ }
+ _, err = f.Readdir(-1)
+ f.Close()
+ })
+
+ runBenchMark("Readdir in one fs all", func(b *testing.B) {
+ f, err := ofs.Open("myotherdir")
+ if err != nil {
+ b.Fatal(err)
+ }
+ _, err = f.Readdir(-1)
+ f.Close()
+ })
+
+ runBenchMark("Readdir some", func(b *testing.B) {
+ f, err := ofs.Open("mydir")
+ if err != nil {
+ b.Fatal(err)
+ }
+ _, err = f.Readdir(2)
+ f.Close()
+ })
+
+ runBenchMark("Readdir in one fs some", func(b *testing.B) {
+ f, err := ofs.Open("myotherdir")
+ if err != nil {
+ b.Fatal(err)
+ }
+ _, err = f.Readdir(2)
+ f.Close()
+ })
}
Debdiff
File lists identical (after any substitutions)
No differences were encountered in the control files