From a22d18325a9111fb9464d8bca17566787e7f5e00 Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Wed, 16 May 2018 11:21:03 +0200 Subject: [PATCH 1/5] Add Capability function to query fs capabilities This adds the Capable interface that can be implemented in filesystems to return the features suported by it. This first iteration has the following capablities: * CapWrite * CapRead * CapReadAndWrite * CapSeek * CapTruncate * CapLock `billy.Capablilities(fs)` can be used to query a fs. The capabilities are implemented as bit flags. If a filesystem does not implement Capable interface then all capabilities will be returned as fall back. Signed-off-by: Javi Fontan --- fs.go | 39 +++++++++++++++++++++++++++++++++++++ helper/chroot/chroot.go | 5 +++++ helper/mount/mount.go | 5 +++++ helper/polyfill/polyfill.go | 5 +++++ memfs/memory.go | 11 ++++++++++- memfs/memory_test.go | 9 +++++++++ osfs/os.go | 5 +++++ osfs/os_test.go | 12 +++++++++++- 8 files changed, 89 insertions(+), 2 deletions(-) diff --git a/fs.go b/fs.go index 00bb7a1..4c57209 100644 --- a/fs.go +++ b/fs.go @@ -13,6 +13,28 @@ var ( ErrCrossedBoundary = errors.New("chroot boundary crossed") ) +// Capability holds the supported features of a filesystem. +type Capability uint64 + +const ( + // CapWrite means that the fs is writable. + CapWrite Capability = 1 << iota + // CapRead means that the fs is readable. + CapRead + // CapReadAndWrite is the ability to open a file in read and write mode. + CapReadAndWrite + // CapSeek means it is able to move position inside the file. + CapSeek + // CapTruncate means that a file can be truncated. + CapTruncate + // CapLock is the ability to lock a file. + CapLock + + // CapAll lists all capable features. + CapAll Capability = CapWrite | CapRead | CapReadAndWrite | + CapSeek | CapTruncate | CapLock +) + // Filesystem abstract the operations in a storage-agnostic interface. // Each method implementation mimics the behavior of the equivalent functions // at the os package from the standard library. @@ -143,3 +165,20 @@ type File interface { // Truncate the file. Truncate(size int64) error } + +// Capable interface can return the available features of a filesystem. +type Capable interface { + // Capabilities returns the capabilities of a filesystem in bit flags. + Capabilities() Capability +} + +// Capabilities returns the features supported by a filesystem. If the FS +// does not implement Capable interface it returns all features. +func Capabilities(fs Basic) Capability { + capable, ok := fs.(Capable) + if !ok { + return CapAll + } + + return capable.Capabilities() +} diff --git a/helper/chroot/chroot.go b/helper/chroot/chroot.go index e3e5a6d..44ddb3d 100644 --- a/helper/chroot/chroot.go +++ b/helper/chroot/chroot.go @@ -217,6 +217,11 @@ func (fs *ChrootHelper) Underlying() billy.Basic { return fs.underlying } +// Capabilities implements the Capable interface. +func (fs *ChrootHelper) Capabilities() billy.Capability { + return billy.Capabilities(fs.underlying) +} + type file struct { billy.File name string diff --git a/helper/mount/mount.go b/helper/mount/mount.go index bb13da5..956eb28 100644 --- a/helper/mount/mount.go +++ b/helper/mount/mount.go @@ -167,6 +167,11 @@ func (h *Mount) Underlying() billy.Basic { return h.underlying } +// Capabilities implements the Capable interface. +func (fs *Mount) Capabilities() billy.Capability { + return billy.Capabilities(fs.underlying) +} + func (fs *Mount) getBasicAndPath(path string) (billy.Basic, string) { path = cleanPath(path) if !fs.isMountpoint(path) { diff --git a/helper/polyfill/polyfill.go b/helper/polyfill/polyfill.go index a3d85c1..f613c25 100644 --- a/helper/polyfill/polyfill.go +++ b/helper/polyfill/polyfill.go @@ -98,3 +98,8 @@ func (h *Polyfill) Root() string { func (h *Polyfill) Underlying() billy.Basic { return h.Basic } + +// Capabilities implements the Capable interface. +func (h *Polyfill) Capabilities() billy.Capability { + return billy.Capabilities(h.Basic) +} diff --git a/memfs/memory.go b/memfs/memory.go index 2f8dcae..f1b1613 100644 --- a/memfs/memory.go +++ b/memfs/memory.go @@ -190,6 +190,15 @@ func (fs *Memory) Readlink(link string) (string, error) { return string(f.content.bytes), nil } +// Capabilities implements the Capable interface. +func (fs *Memory) Capabilities() billy.Capability { + return billy.CapWrite | + billy.CapRead | + billy.CapReadAndWrite | + billy.CapSeek | + billy.CapTruncate +} + type file struct { name string content *content @@ -273,7 +282,7 @@ func (f *file) Close() error { func (f *file) Truncate(size int64) error { if size < int64(len(f.content.bytes)) { f.content.bytes = f.content.bytes[:size] - } else if more := int(size)-len(f.content.bytes); more > 0 { + } else if more := int(size) - len(f.content.bytes); more > 0 { f.content.bytes = append(f.content.bytes, make([]byte, more)...) } diff --git a/memfs/memory_test.go b/memfs/memory_test.go index 4de996e..e0cedc5 100644 --- a/memfs/memory_test.go +++ b/memfs/memory_test.go @@ -3,6 +3,7 @@ package memfs import ( "testing" + "gopkg.in/src-d/go-billy.v4" "gopkg.in/src-d/go-billy.v4/test" . "gopkg.in/check.v1" @@ -20,3 +21,11 @@ var _ = Suite(&MemorySuite{}) func (s *MemorySuite) SetUpTest(c *C) { s.FilesystemSuite = test.NewFilesystemSuite(New()) } + +func (s *MemorySuite) TestCapabilities(c *C) { + _, ok := s.FS.(billy.Capable) + c.Assert(ok, Equals, true) + + caps := billy.Capabilities(s.FS) + c.Assert(caps, Equals, billy.CapAll&^billy.CapLock) +} diff --git a/osfs/os.go b/osfs/os.go index 348cea9..f193c74 100644 --- a/osfs/os.go +++ b/osfs/os.go @@ -127,6 +127,11 @@ func (fs *OS) Readlink(link string) (string, error) { return os.Readlink(link) } +// Capabilities implements the Capable interface. +func (fs *OS) Capabilities() billy.Capability { + return billy.CapAll +} + // file is a wrapper for an os.File which adds support for file locking. type file struct { *os.File diff --git a/osfs/os_test.go b/osfs/os_test.go index b81943f..dc22b12 100644 --- a/osfs/os_test.go +++ b/osfs/os_test.go @@ -6,8 +6,10 @@ import ( "path/filepath" "testing" - . "gopkg.in/check.v1" + "gopkg.in/src-d/go-billy.v4" "gopkg.in/src-d/go-billy.v4/test" + + . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } @@ -36,3 +38,11 @@ func (s *OSSuite) TestOpenDoesNotCreateDir(c *C) { _, err = os.Stat(filepath.Join(s.path, "dir")) c.Assert(os.IsNotExist(err), Equals, true) } + +func (s *OSSuite) TestCapabilities(c *C) { + _, ok := s.FS.(billy.Capable) + c.Assert(ok, Equals, true) + + caps := billy.Capabilities(s.FS) + c.Assert(caps, Equals, billy.CapAll) +} From d198577eb8b6c61319cd86a9add7f61a8a22ab9e Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Thu, 17 May 2018 20:46:27 +0200 Subject: [PATCH 2/5] Add CapabilityCheck helper function Signed-off-by: Javi Fontan --- fs.go | 7 +++++++ fs_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 fs_test.go diff --git a/fs.go b/fs.go index 4c57209..477c8ce 100644 --- a/fs.go +++ b/fs.go @@ -182,3 +182,10 @@ func Capabilities(fs Basic) Capability { return capable.Capabilities() } + +// CapabilityCheck tests the filesystem for the provided capabilities and +// returns true in case it supports all of them. +func CapabilityCheck(fs Basic, capabilities Capability) bool { + fsCaps := Capabilities(fs) + return fsCaps&capabilities == capabilities +} diff --git a/fs_test.go b/fs_test.go new file mode 100644 index 0000000..0711e8d --- /dev/null +++ b/fs_test.go @@ -0,0 +1,37 @@ +package billy_test + +import ( + "testing" + + . "gopkg.in/src-d/go-billy.v4" + "gopkg.in/src-d/go-billy.v4/memfs" + + . "gopkg.in/check.v1" +) + +type FSSuite struct{} + +func Test(t *testing.T) { TestingT(t) } + +var _ = Suite(&FSSuite{}) + +func (s *FSSuite) TestCapabilities(c *C) { + cases := []struct { + caps Capability + expected bool + }{ + {CapLock, false}, + {CapRead, true}, + {CapRead | CapWrite, true}, + {CapRead | CapWrite | CapReadAndWrite | CapTruncate, true}, + {CapRead | CapWrite | CapReadAndWrite | CapTruncate | CapLock, false}, + {CapTruncate | CapLock, false}, + } + + // This filesystem supports all capabilities except for CapLock + mem := memfs.New() + + for _, e := range cases { + c.Assert(CapabilityCheck(mem, e.caps), Equals, e.expected) + } +} From e2ca534568490a8296cc722515b1468fbb1e1625 Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Mon, 4 Jun 2018 17:02:19 +0200 Subject: [PATCH 3/5] More documentation and add CapDefault CapDefault has all the capabilities supported in the current release and should not be changed until a new major release. Also changed Capabilities of mount to return the common capabilities from both filesystems. Signed-off-by: Javi Fontan --- fs.go | 13 +++++++++++-- helper/mount/mount.go | 2 +- memfs/memory_test.go | 2 +- osfs/os.go | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/fs.go b/fs.go index 477c8ce..227ca90 100644 --- a/fs.go +++ b/fs.go @@ -13,7 +13,10 @@ var ( ErrCrossedBoundary = errors.New("chroot boundary crossed") ) -// Capability holds the supported features of a filesystem. +// Capability holds the supported features of a billy filesystem. This does +// not mean that the capability has to be supported by the underlying storage. +// For example, a billy filesystem may support CapWrite but the storage be +// mounted in read only mode. type Capability uint64 const ( @@ -30,6 +33,12 @@ const ( // CapLock is the ability to lock a file. CapLock + // CapDefault lists all capable features supported by filesystems without + // Capability interface. This list should not be changed until a major + // version is released. + CapDefault Capability = CapWrite | CapRead | CapReadAndWrite | + CapSeek | CapTruncate | CapLock + // CapAll lists all capable features. CapAll Capability = CapWrite | CapRead | CapReadAndWrite | CapSeek | CapTruncate | CapLock @@ -177,7 +186,7 @@ type Capable interface { func Capabilities(fs Basic) Capability { capable, ok := fs.(Capable) if !ok { - return CapAll + return CapDefault } return capable.Capabilities() diff --git a/helper/mount/mount.go b/helper/mount/mount.go index 956eb28..83f7dd5 100644 --- a/helper/mount/mount.go +++ b/helper/mount/mount.go @@ -169,7 +169,7 @@ func (h *Mount) Underlying() billy.Basic { // Capabilities implements the Capable interface. func (fs *Mount) Capabilities() billy.Capability { - return billy.Capabilities(fs.underlying) + return billy.Capabilities(fs.underlying) & billy.Capabilities(fs.source) } func (fs *Mount) getBasicAndPath(path string) (billy.Basic, string) { diff --git a/memfs/memory_test.go b/memfs/memory_test.go index e0cedc5..e0e3cbc 100644 --- a/memfs/memory_test.go +++ b/memfs/memory_test.go @@ -27,5 +27,5 @@ func (s *MemorySuite) TestCapabilities(c *C) { c.Assert(ok, Equals, true) caps := billy.Capabilities(s.FS) - c.Assert(caps, Equals, billy.CapAll&^billy.CapLock) + c.Assert(caps, Equals, billy.CapDefault&^billy.CapLock) } diff --git a/osfs/os.go b/osfs/os.go index f193c74..89b01f0 100644 --- a/osfs/os.go +++ b/osfs/os.go @@ -129,7 +129,7 @@ func (fs *OS) Readlink(link string) (string, error) { // Capabilities implements the Capable interface. func (fs *OS) Capabilities() billy.Capability { - return billy.CapAll + return billy.CapDefault } // file is a wrapper for an os.File which adds support for file locking. From 5bb98d7916f7d944463d78e61e9f5b59dd166360 Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Wed, 6 Jun 2018 16:26:43 +0200 Subject: [PATCH 4/5] Rename capability names to Capability Signed-off-by: Javi Fontan --- fs.go | 52 +++++++++++++++++++++++--------------------- fs_test.go | 14 ++++++------ memfs/memory.go | 10 ++++----- memfs/memory_test.go | 2 +- osfs/os.go | 2 +- osfs/os_test.go | 2 +- 6 files changed, 42 insertions(+), 40 deletions(-) diff --git a/fs.go b/fs.go index 227ca90..a9efccd 100644 --- a/fs.go +++ b/fs.go @@ -15,33 +15,35 @@ var ( // Capability holds the supported features of a billy filesystem. This does // not mean that the capability has to be supported by the underlying storage. -// For example, a billy filesystem may support CapWrite but the storage be -// mounted in read only mode. +// For example, a billy filesystem may support WriteCapability but the +// storage be mounted in read only mode. type Capability uint64 const ( - // CapWrite means that the fs is writable. - CapWrite Capability = 1 << iota - // CapRead means that the fs is readable. - CapRead - // CapReadAndWrite is the ability to open a file in read and write mode. - CapReadAndWrite - // CapSeek means it is able to move position inside the file. - CapSeek - // CapTruncate means that a file can be truncated. - CapTruncate - // CapLock is the ability to lock a file. - CapLock - - // CapDefault lists all capable features supported by filesystems without - // Capability interface. This list should not be changed until a major - // version is released. - CapDefault Capability = CapWrite | CapRead | CapReadAndWrite | - CapSeek | CapTruncate | CapLock - - // CapAll lists all capable features. - CapAll Capability = CapWrite | CapRead | CapReadAndWrite | - CapSeek | CapTruncate | CapLock + // WriteCapability means that the fs is writable. + WriteCapability Capability = 1 << iota + // ReadCapability means that the fs is readable. + ReadCapability + // ReadAndWriteCapability is the ability to open a file in read and write mode. + ReadAndWriteCapability + // SeekCapability means it is able to move position inside the file. + SeekCapability + // TruncateCapability means that a file can be truncated. + TruncateCapability + // LockCapability is the ability to lock a file. + LockCapability + + // DefaultCapabilities lists all capable features supported by filesystems + // without Capability interface. This list should not be changed until a + // major version is released. + DefaultCapabilities Capability = WriteCapability | ReadCapability | + ReadAndWriteCapability | SeekCapability | TruncateCapability | + LockCapability + + // AllCapabilities lists all capable features. + AllCapabilities Capability = WriteCapability | ReadCapability | + ReadAndWriteCapability | SeekCapability | TruncateCapability | + LockCapability ) // Filesystem abstract the operations in a storage-agnostic interface. @@ -186,7 +188,7 @@ type Capable interface { func Capabilities(fs Basic) Capability { capable, ok := fs.(Capable) if !ok { - return CapDefault + return DefaultCapabilities } return capable.Capabilities() diff --git a/fs_test.go b/fs_test.go index 0711e8d..ec9b831 100644 --- a/fs_test.go +++ b/fs_test.go @@ -20,15 +20,15 @@ func (s *FSSuite) TestCapabilities(c *C) { caps Capability expected bool }{ - {CapLock, false}, - {CapRead, true}, - {CapRead | CapWrite, true}, - {CapRead | CapWrite | CapReadAndWrite | CapTruncate, true}, - {CapRead | CapWrite | CapReadAndWrite | CapTruncate | CapLock, false}, - {CapTruncate | CapLock, false}, + {LockCapability, false}, + {ReadCapability, true}, + {ReadCapability | WriteCapability, true}, + {ReadCapability | WriteCapability | ReadAndWriteCapability | TruncateCapability, true}, + {ReadCapability | WriteCapability | ReadAndWriteCapability | TruncateCapability | LockCapability, false}, + {TruncateCapability | LockCapability, false}, } - // This filesystem supports all capabilities except for CapLock + // This filesystem supports all capabilities except for LockCapability mem := memfs.New() for _, e := range cases { diff --git a/memfs/memory.go b/memfs/memory.go index f1b1613..7eab699 100644 --- a/memfs/memory.go +++ b/memfs/memory.go @@ -192,11 +192,11 @@ func (fs *Memory) Readlink(link string) (string, error) { // Capabilities implements the Capable interface. func (fs *Memory) Capabilities() billy.Capability { - return billy.CapWrite | - billy.CapRead | - billy.CapReadAndWrite | - billy.CapSeek | - billy.CapTruncate + return billy.WriteCapability | + billy.ReadCapability | + billy.ReadAndWriteCapability | + billy.SeekCapability | + billy.TruncateCapability } type file struct { diff --git a/memfs/memory_test.go b/memfs/memory_test.go index e0e3cbc..4f15909 100644 --- a/memfs/memory_test.go +++ b/memfs/memory_test.go @@ -27,5 +27,5 @@ func (s *MemorySuite) TestCapabilities(c *C) { c.Assert(ok, Equals, true) caps := billy.Capabilities(s.FS) - c.Assert(caps, Equals, billy.CapDefault&^billy.CapLock) + c.Assert(caps, Equals, billy.DefaultCapabilities&^billy.LockCapability) } diff --git a/osfs/os.go b/osfs/os.go index 89b01f0..ff35a3b 100644 --- a/osfs/os.go +++ b/osfs/os.go @@ -129,7 +129,7 @@ func (fs *OS) Readlink(link string) (string, error) { // Capabilities implements the Capable interface. func (fs *OS) Capabilities() billy.Capability { - return billy.CapDefault + return billy.DefaultCapabilities } // file is a wrapper for an os.File which adds support for file locking. diff --git a/osfs/os_test.go b/osfs/os_test.go index dc22b12..3a0258b 100644 --- a/osfs/os_test.go +++ b/osfs/os_test.go @@ -44,5 +44,5 @@ func (s *OSSuite) TestCapabilities(c *C) { c.Assert(ok, Equals, true) caps := billy.Capabilities(s.FS) - c.Assert(caps, Equals, billy.CapAll) + c.Assert(caps, Equals, billy.AllCapabilities) } From c1e3d52cfca2a8c8fd45baac7c8de51445b71d92 Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Fri, 8 Jun 2018 11:04:03 +0200 Subject: [PATCH 5/5] Add capability tests to chroot, mount and polyfill Also use mock FS instead of memfs to test capabilities Signed-off-by: Javi Fontan --- fs_test.go | 9 ++++++--- helper/chroot/chroot_test.go | 15 +++++++++++++++ helper/mount/mount_test.go | 31 +++++++++++++++++++++++++++++++ helper/polyfill/polyfill_test.go | 15 +++++++++++++++ test/mock.go | 20 ++++++++++++++++++++ 5 files changed, 87 insertions(+), 3 deletions(-) diff --git a/fs_test.go b/fs_test.go index ec9b831..368eb95 100644 --- a/fs_test.go +++ b/fs_test.go @@ -4,7 +4,7 @@ import ( "testing" . "gopkg.in/src-d/go-billy.v4" - "gopkg.in/src-d/go-billy.v4/memfs" + "gopkg.in/src-d/go-billy.v4/test" . "gopkg.in/check.v1" ) @@ -29,9 +29,12 @@ func (s *FSSuite) TestCapabilities(c *C) { } // This filesystem supports all capabilities except for LockCapability - mem := memfs.New() + fs := new(test.NoLockCapFs) for _, e := range cases { - c.Assert(CapabilityCheck(mem, e.caps), Equals, e.expected) + c.Assert(CapabilityCheck(fs, e.caps), Equals, e.expected) } + + dummy := new(test.BasicMock) + c.Assert(Capabilities(dummy), Equals, DefaultCapabilities) } diff --git a/helper/chroot/chroot_test.go b/helper/chroot/chroot_test.go index 580347a..819b000 100644 --- a/helper/chroot/chroot_test.go +++ b/helper/chroot/chroot_test.go @@ -351,3 +351,18 @@ func (s *ChrootSuite) TestReadlinkWithBasic(c *C) { _, err := fs.Readlink("") c.Assert(err, Equals, billy.ErrNotSupported) } + +func (s *ChrootSuite) TestCapabilities(c *C) { + testCapabilities(c, new(test.BasicMock)) + testCapabilities(c, new(test.OnlyReadCapFs)) + testCapabilities(c, new(test.NoLockCapFs)) +} + +func testCapabilities(c *C, basic billy.Basic) { + baseCapabilities := billy.Capabilities(basic) + + fs := New(basic, "/foo") + capabilities := billy.Capabilities(fs) + + c.Assert(capabilities, Equals, baseCapabilities) +} diff --git a/helper/mount/mount_test.go b/helper/mount/mount_test.go index 8903dc9..ba558f3 100644 --- a/helper/mount/mount_test.go +++ b/helper/mount/mount_test.go @@ -337,3 +337,34 @@ func (s *MountSuite) TestSourceNotSupported(c *C) { _, err = h.Readlink("foo") c.Assert(err, Equals, billy.ErrNotSupported) } + +func (s *MountSuite) TestCapabilities(c *C) { + testCapabilities(c, new(test.BasicMock), new(test.BasicMock)) + testCapabilities(c, new(test.BasicMock), new(test.OnlyReadCapFs)) + testCapabilities(c, new(test.BasicMock), new(test.NoLockCapFs)) + testCapabilities(c, new(test.OnlyReadCapFs), new(test.BasicMock)) + testCapabilities(c, new(test.OnlyReadCapFs), new(test.OnlyReadCapFs)) + testCapabilities(c, new(test.OnlyReadCapFs), new(test.NoLockCapFs)) + testCapabilities(c, new(test.NoLockCapFs), new(test.BasicMock)) + testCapabilities(c, new(test.NoLockCapFs), new(test.OnlyReadCapFs)) + testCapabilities(c, new(test.NoLockCapFs), new(test.NoLockCapFs)) +} + +func testCapabilities(c *C, a, b billy.Basic) { + aCapabilities := billy.Capabilities(a) + bCapabilities := billy.Capabilities(b) + + fs := New(a, "/foo", b) + capabilities := billy.Capabilities(fs) + + unionCapabilities := aCapabilities & bCapabilities + + c.Assert(capabilities, Equals, unionCapabilities) + + fs = New(b, "/foo", a) + capabilities = billy.Capabilities(fs) + + unionCapabilities = aCapabilities & bCapabilities + + c.Assert(capabilities, Equals, unionCapabilities) +} diff --git a/helper/polyfill/polyfill_test.go b/helper/polyfill/polyfill_test.go index b58d19d..2579ed9 100644 --- a/helper/polyfill/polyfill_test.go +++ b/helper/polyfill/polyfill_test.go @@ -61,3 +61,18 @@ func (s *PolyfillSuite) TestChroot(c *C) { func (s *PolyfillSuite) TestRoot(c *C) { c.Assert(s.Helper.Root(), Equals, string(filepath.Separator)) } + +func (s *PolyfillSuite) TestCapabilities(c *C) { + testCapabilities(c, new(test.BasicMock)) + testCapabilities(c, new(test.OnlyReadCapFs)) + testCapabilities(c, new(test.NoLockCapFs)) +} + +func testCapabilities(c *C, basic billy.Basic) { + baseCapabilities := billy.Capabilities(basic) + + fs := New(basic) + capabilities := billy.Capabilities(fs) + + c.Assert(capabilities, Equals, baseCapabilities) +} diff --git a/test/mock.go b/test/mock.go index 2cf7397..519bba6 100644 --- a/test/mock.go +++ b/test/mock.go @@ -134,3 +134,23 @@ func (*FileMock) Unlock() error { func (*FileMock) Truncate(size int64) error { return nil } + +type OnlyReadCapFs struct { + BasicMock +} + +func (o *OnlyReadCapFs) Capabilities() billy.Capability { + return billy.ReadCapability +} + +type NoLockCapFs struct { + BasicMock +} + +func (o *NoLockCapFs) Capabilities() billy.Capability { + return billy.WriteCapability | + billy.ReadCapability | + billy.ReadAndWriteCapability | + billy.SeekCapability | + billy.TruncateCapability +}