Skip to content

Commit

Permalink
Merge pull request #59 from jfontan/query-filesystem-capabilities
Browse files Browse the repository at this point in the history
Add Capability function to query fs capabilities
  • Loading branch information
mcuadros authored Jun 11, 2018
2 parents dc8550b + c1e3d52 commit 83cf655
Show file tree
Hide file tree
Showing 13 changed files with 228 additions and 2 deletions.
57 changes: 57 additions & 0 deletions fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,39 @@ var (
ErrCrossedBoundary = errors.New("chroot boundary crossed")
)

// 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 WriteCapability but the
// storage be mounted in read only mode.
type Capability uint64

const (
// 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.
// Each method implementation mimics the behavior of the equivalent functions
// at the os package from the standard library.
Expand Down Expand Up @@ -143,3 +176,27 @@ 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 DefaultCapabilities
}

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
}
40 changes: 40 additions & 0 deletions fs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package billy_test

import (
"testing"

. "gopkg.in/src-d/go-billy.v4"
"gopkg.in/src-d/go-billy.v4/test"

. "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
}{
{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 LockCapability
fs := new(test.NoLockCapFs)

for _, e := range cases {
c.Assert(CapabilityCheck(fs, e.caps), Equals, e.expected)
}

dummy := new(test.BasicMock)
c.Assert(Capabilities(dummy), Equals, DefaultCapabilities)
}
5 changes: 5 additions & 0 deletions helper/chroot/chroot.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions helper/chroot/chroot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
5 changes: 5 additions & 0 deletions helper/mount/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) & billy.Capabilities(fs.source)
}

func (fs *Mount) getBasicAndPath(path string) (billy.Basic, string) {
path = cleanPath(path)
if !fs.isMountpoint(path) {
Expand Down
31 changes: 31 additions & 0 deletions helper/mount/mount_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
5 changes: 5 additions & 0 deletions helper/polyfill/polyfill.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
15 changes: 15 additions & 0 deletions helper/polyfill/polyfill_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
11 changes: 10 additions & 1 deletion memfs/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.WriteCapability |
billy.ReadCapability |
billy.ReadAndWriteCapability |
billy.SeekCapability |
billy.TruncateCapability
}

type file struct {
name string
content *content
Expand Down Expand Up @@ -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)...)
}

Expand Down
9 changes: 9 additions & 0 deletions memfs/memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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.DefaultCapabilities&^billy.LockCapability)
}
5 changes: 5 additions & 0 deletions osfs/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.DefaultCapabilities
}

// file is a wrapper for an os.File which adds support for file locking.
type file struct {
*os.File
Expand Down
12 changes: 11 additions & 1 deletion osfs/os_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
Expand Down Expand Up @@ -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.AllCapabilities)
}
20 changes: 20 additions & 0 deletions test/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit 83cf655

Please sign in to comment.