Skip to content

Commit 83cf655

Browse files
authored
Merge pull request #59 from jfontan/query-filesystem-capabilities
Add Capability function to query fs capabilities
2 parents dc8550b + c1e3d52 commit 83cf655

File tree

13 files changed

+228
-2
lines changed

13 files changed

+228
-2
lines changed

fs.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,39 @@ var (
1313
ErrCrossedBoundary = errors.New("chroot boundary crossed")
1414
)
1515

16+
// Capability holds the supported features of a billy filesystem. This does
17+
// not mean that the capability has to be supported by the underlying storage.
18+
// For example, a billy filesystem may support WriteCapability but the
19+
// storage be mounted in read only mode.
20+
type Capability uint64
21+
22+
const (
23+
// WriteCapability means that the fs is writable.
24+
WriteCapability Capability = 1 << iota
25+
// ReadCapability means that the fs is readable.
26+
ReadCapability
27+
// ReadAndWriteCapability is the ability to open a file in read and write mode.
28+
ReadAndWriteCapability
29+
// SeekCapability means it is able to move position inside the file.
30+
SeekCapability
31+
// TruncateCapability means that a file can be truncated.
32+
TruncateCapability
33+
// LockCapability is the ability to lock a file.
34+
LockCapability
35+
36+
// DefaultCapabilities lists all capable features supported by filesystems
37+
// without Capability interface. This list should not be changed until a
38+
// major version is released.
39+
DefaultCapabilities Capability = WriteCapability | ReadCapability |
40+
ReadAndWriteCapability | SeekCapability | TruncateCapability |
41+
LockCapability
42+
43+
// AllCapabilities lists all capable features.
44+
AllCapabilities Capability = WriteCapability | ReadCapability |
45+
ReadAndWriteCapability | SeekCapability | TruncateCapability |
46+
LockCapability
47+
)
48+
1649
// Filesystem abstract the operations in a storage-agnostic interface.
1750
// Each method implementation mimics the behavior of the equivalent functions
1851
// at the os package from the standard library.
@@ -143,3 +176,27 @@ type File interface {
143176
// Truncate the file.
144177
Truncate(size int64) error
145178
}
179+
180+
// Capable interface can return the available features of a filesystem.
181+
type Capable interface {
182+
// Capabilities returns the capabilities of a filesystem in bit flags.
183+
Capabilities() Capability
184+
}
185+
186+
// Capabilities returns the features supported by a filesystem. If the FS
187+
// does not implement Capable interface it returns all features.
188+
func Capabilities(fs Basic) Capability {
189+
capable, ok := fs.(Capable)
190+
if !ok {
191+
return DefaultCapabilities
192+
}
193+
194+
return capable.Capabilities()
195+
}
196+
197+
// CapabilityCheck tests the filesystem for the provided capabilities and
198+
// returns true in case it supports all of them.
199+
func CapabilityCheck(fs Basic, capabilities Capability) bool {
200+
fsCaps := Capabilities(fs)
201+
return fsCaps&capabilities == capabilities
202+
}

fs_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package billy_test
2+
3+
import (
4+
"testing"
5+
6+
. "gopkg.in/src-d/go-billy.v4"
7+
"gopkg.in/src-d/go-billy.v4/test"
8+
9+
. "gopkg.in/check.v1"
10+
)
11+
12+
type FSSuite struct{}
13+
14+
func Test(t *testing.T) { TestingT(t) }
15+
16+
var _ = Suite(&FSSuite{})
17+
18+
func (s *FSSuite) TestCapabilities(c *C) {
19+
cases := []struct {
20+
caps Capability
21+
expected bool
22+
}{
23+
{LockCapability, false},
24+
{ReadCapability, true},
25+
{ReadCapability | WriteCapability, true},
26+
{ReadCapability | WriteCapability | ReadAndWriteCapability | TruncateCapability, true},
27+
{ReadCapability | WriteCapability | ReadAndWriteCapability | TruncateCapability | LockCapability, false},
28+
{TruncateCapability | LockCapability, false},
29+
}
30+
31+
// This filesystem supports all capabilities except for LockCapability
32+
fs := new(test.NoLockCapFs)
33+
34+
for _, e := range cases {
35+
c.Assert(CapabilityCheck(fs, e.caps), Equals, e.expected)
36+
}
37+
38+
dummy := new(test.BasicMock)
39+
c.Assert(Capabilities(dummy), Equals, DefaultCapabilities)
40+
}

helper/chroot/chroot.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@ func (fs *ChrootHelper) Underlying() billy.Basic {
217217
return fs.underlying
218218
}
219219

220+
// Capabilities implements the Capable interface.
221+
func (fs *ChrootHelper) Capabilities() billy.Capability {
222+
return billy.Capabilities(fs.underlying)
223+
}
224+
220225
type file struct {
221226
billy.File
222227
name string

helper/chroot/chroot_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,18 @@ func (s *ChrootSuite) TestReadlinkWithBasic(c *C) {
351351
_, err := fs.Readlink("")
352352
c.Assert(err, Equals, billy.ErrNotSupported)
353353
}
354+
355+
func (s *ChrootSuite) TestCapabilities(c *C) {
356+
testCapabilities(c, new(test.BasicMock))
357+
testCapabilities(c, new(test.OnlyReadCapFs))
358+
testCapabilities(c, new(test.NoLockCapFs))
359+
}
360+
361+
func testCapabilities(c *C, basic billy.Basic) {
362+
baseCapabilities := billy.Capabilities(basic)
363+
364+
fs := New(basic, "/foo")
365+
capabilities := billy.Capabilities(fs)
366+
367+
c.Assert(capabilities, Equals, baseCapabilities)
368+
}

helper/mount/mount.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ func (h *Mount) Underlying() billy.Basic {
167167
return h.underlying
168168
}
169169

170+
// Capabilities implements the Capable interface.
171+
func (fs *Mount) Capabilities() billy.Capability {
172+
return billy.Capabilities(fs.underlying) & billy.Capabilities(fs.source)
173+
}
174+
170175
func (fs *Mount) getBasicAndPath(path string) (billy.Basic, string) {
171176
path = cleanPath(path)
172177
if !fs.isMountpoint(path) {

helper/mount/mount_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,34 @@ func (s *MountSuite) TestSourceNotSupported(c *C) {
337337
_, err = h.Readlink("foo")
338338
c.Assert(err, Equals, billy.ErrNotSupported)
339339
}
340+
341+
func (s *MountSuite) TestCapabilities(c *C) {
342+
testCapabilities(c, new(test.BasicMock), new(test.BasicMock))
343+
testCapabilities(c, new(test.BasicMock), new(test.OnlyReadCapFs))
344+
testCapabilities(c, new(test.BasicMock), new(test.NoLockCapFs))
345+
testCapabilities(c, new(test.OnlyReadCapFs), new(test.BasicMock))
346+
testCapabilities(c, new(test.OnlyReadCapFs), new(test.OnlyReadCapFs))
347+
testCapabilities(c, new(test.OnlyReadCapFs), new(test.NoLockCapFs))
348+
testCapabilities(c, new(test.NoLockCapFs), new(test.BasicMock))
349+
testCapabilities(c, new(test.NoLockCapFs), new(test.OnlyReadCapFs))
350+
testCapabilities(c, new(test.NoLockCapFs), new(test.NoLockCapFs))
351+
}
352+
353+
func testCapabilities(c *C, a, b billy.Basic) {
354+
aCapabilities := billy.Capabilities(a)
355+
bCapabilities := billy.Capabilities(b)
356+
357+
fs := New(a, "/foo", b)
358+
capabilities := billy.Capabilities(fs)
359+
360+
unionCapabilities := aCapabilities & bCapabilities
361+
362+
c.Assert(capabilities, Equals, unionCapabilities)
363+
364+
fs = New(b, "/foo", a)
365+
capabilities = billy.Capabilities(fs)
366+
367+
unionCapabilities = aCapabilities & bCapabilities
368+
369+
c.Assert(capabilities, Equals, unionCapabilities)
370+
}

helper/polyfill/polyfill.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,8 @@ func (h *Polyfill) Root() string {
9898
func (h *Polyfill) Underlying() billy.Basic {
9999
return h.Basic
100100
}
101+
102+
// Capabilities implements the Capable interface.
103+
func (h *Polyfill) Capabilities() billy.Capability {
104+
return billy.Capabilities(h.Basic)
105+
}

helper/polyfill/polyfill_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,18 @@ func (s *PolyfillSuite) TestChroot(c *C) {
6161
func (s *PolyfillSuite) TestRoot(c *C) {
6262
c.Assert(s.Helper.Root(), Equals, string(filepath.Separator))
6363
}
64+
65+
func (s *PolyfillSuite) TestCapabilities(c *C) {
66+
testCapabilities(c, new(test.BasicMock))
67+
testCapabilities(c, new(test.OnlyReadCapFs))
68+
testCapabilities(c, new(test.NoLockCapFs))
69+
}
70+
71+
func testCapabilities(c *C, basic billy.Basic) {
72+
baseCapabilities := billy.Capabilities(basic)
73+
74+
fs := New(basic)
75+
capabilities := billy.Capabilities(fs)
76+
77+
c.Assert(capabilities, Equals, baseCapabilities)
78+
}

memfs/memory.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,15 @@ func (fs *Memory) Readlink(link string) (string, error) {
190190
return string(f.content.bytes), nil
191191
}
192192

193+
// Capabilities implements the Capable interface.
194+
func (fs *Memory) Capabilities() billy.Capability {
195+
return billy.WriteCapability |
196+
billy.ReadCapability |
197+
billy.ReadAndWriteCapability |
198+
billy.SeekCapability |
199+
billy.TruncateCapability
200+
}
201+
193202
type file struct {
194203
name string
195204
content *content
@@ -273,7 +282,7 @@ func (f *file) Close() error {
273282
func (f *file) Truncate(size int64) error {
274283
if size < int64(len(f.content.bytes)) {
275284
f.content.bytes = f.content.bytes[:size]
276-
} else if more := int(size)-len(f.content.bytes); more > 0 {
285+
} else if more := int(size) - len(f.content.bytes); more > 0 {
277286
f.content.bytes = append(f.content.bytes, make([]byte, more)...)
278287
}
279288

memfs/memory_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package memfs
33
import (
44
"testing"
55

6+
"gopkg.in/src-d/go-billy.v4"
67
"gopkg.in/src-d/go-billy.v4/test"
78

89
. "gopkg.in/check.v1"
@@ -20,3 +21,11 @@ var _ = Suite(&MemorySuite{})
2021
func (s *MemorySuite) SetUpTest(c *C) {
2122
s.FilesystemSuite = test.NewFilesystemSuite(New())
2223
}
24+
25+
func (s *MemorySuite) TestCapabilities(c *C) {
26+
_, ok := s.FS.(billy.Capable)
27+
c.Assert(ok, Equals, true)
28+
29+
caps := billy.Capabilities(s.FS)
30+
c.Assert(caps, Equals, billy.DefaultCapabilities&^billy.LockCapability)
31+
}

0 commit comments

Comments
 (0)