Skip to content

Commit cadb3c8

Browse files
authored
Merge pull request #27 from ajnavarro/feature/temp-overlay
tmpfs: add temporal overlay filesystem
2 parents 99d8398 + c8ef2d3 commit cadb3c8

File tree

3 files changed

+435
-0
lines changed

3 files changed

+435
-0
lines changed

tmpoverlayfs/tmpfs.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package tmpfs
2+
3+
import (
4+
"errors"
5+
"io"
6+
"os"
7+
"path"
8+
9+
"gopkg.in/src-d/go-billy.v2"
10+
"gopkg.in/src-d/go-billy.v2/subdirfs"
11+
)
12+
13+
type tmpFs struct {
14+
fs billy.Filesystem
15+
tmp billy.Filesystem
16+
tempFiles map[string]bool
17+
}
18+
19+
// New creates a new filesystem wrapping up 'fs' and using 'tmp' for temporary
20+
// files. Any file created with TempFile will be created in 'tmp'. It will be
21+
// moved to 'fs' if Rename is called on it.
22+
//
23+
// This is particularly useful to provide TempFile for filesystems that do not
24+
// support it or where there is a performance penalty in doing so.
25+
func New(fs billy.Filesystem, tmp billy.Filesystem) billy.Filesystem {
26+
return &tmpFs{
27+
fs: fs,
28+
tmp: tmp,
29+
tempFiles: map[string]bool{},
30+
}
31+
}
32+
33+
func (t *tmpFs) Create(path string) (billy.File, error) {
34+
if t.isTmpFile(path) {
35+
return t.tmp.Create(path)
36+
}
37+
38+
return t.fs.Create(path)
39+
}
40+
41+
func (t *tmpFs) Open(path string) (billy.File, error) {
42+
if t.isTmpFile(path) {
43+
return t.tmp.Open(path)
44+
}
45+
46+
return t.fs.Open(path)
47+
48+
}
49+
50+
func (t *tmpFs) OpenFile(p string, flag int, mode os.FileMode) (
51+
billy.File, error) {
52+
if t.isTmpFile(p) {
53+
return t.tmp.OpenFile(p, flag, mode)
54+
}
55+
56+
return t.fs.OpenFile(p, flag, mode)
57+
}
58+
59+
func (t *tmpFs) ReadDir(p string) ([]billy.FileInfo, error) {
60+
return t.fs.ReadDir(p)
61+
}
62+
63+
func (t *tmpFs) Join(elem ...string) string {
64+
return t.fs.Join(elem...)
65+
}
66+
67+
func (t *tmpFs) Dir(p string) billy.Filesystem {
68+
return subdirfs.New(t, p)
69+
}
70+
71+
func (t *tmpFs) MkdirAll(filename string, perm os.FileMode) error {
72+
return t.fs.MkdirAll(filename, perm)
73+
}
74+
75+
func (t *tmpFs) Base() string {
76+
return t.fs.Base()
77+
}
78+
79+
func (t *tmpFs) TempFile(dir string, prefix string) (billy.File, error) {
80+
tmpFile, err := t.tmp.TempFile(dir, prefix)
81+
if err != nil {
82+
return nil, err
83+
}
84+
85+
t.tempFiles[tmpFile.Filename()] = true
86+
87+
return tmpFile, nil
88+
}
89+
90+
func (t *tmpFs) Rename(from, to string) error {
91+
if t.isTmpFile(to) {
92+
return errors.New("cannot rename to temp file")
93+
}
94+
95+
if t.isTmpFile(from) {
96+
err := copyPath(t.tmp, t.fs, from, to)
97+
if err != nil {
98+
return err
99+
}
100+
101+
if err := t.tmp.Remove(from); err != nil {
102+
return err
103+
}
104+
105+
t.removeTempReference(from)
106+
107+
return nil
108+
}
109+
110+
return t.fs.Rename(from, to)
111+
}
112+
113+
func (t *tmpFs) Remove(path string) error {
114+
if t.isTmpFile(path) {
115+
if err := t.tmp.Remove(path); err != nil {
116+
return err
117+
}
118+
119+
t.removeTempReference(path)
120+
121+
return nil
122+
}
123+
124+
return t.fs.Remove(path)
125+
}
126+
127+
func (t *tmpFs) Stat(path string) (billy.FileInfo, error) {
128+
if t.isTmpFile(path) {
129+
return t.tmp.Stat(path)
130+
}
131+
132+
return t.fs.Stat(path)
133+
}
134+
135+
func (t *tmpFs) isTmpFile(p string) bool {
136+
p = path.Clean(p)
137+
_, ok := t.tempFiles[p]
138+
return ok
139+
}
140+
141+
func (t *tmpFs) removeTempReference(p string) {
142+
p = path.Clean(p)
143+
delete(t.tempFiles, p)
144+
}
145+
146+
// copyPath copies a file across filesystems.
147+
func copyPath(src billy.Filesystem, dst billy.Filesystem,
148+
srcPath string, dstPath string) error {
149+
150+
dstFile, err := dst.Create(dstPath)
151+
if err != nil {
152+
return err
153+
}
154+
155+
srcFile, err := src.Open(srcPath)
156+
if err != nil {
157+
return nil
158+
}
159+
160+
_, err = io.Copy(dstFile, srcFile)
161+
if err != nil {
162+
return nil
163+
}
164+
165+
err = dstFile.Close()
166+
if err != nil {
167+
_ = srcFile.Close()
168+
return err
169+
}
170+
171+
return srcFile.Close()
172+
}

tmpoverlayfs/tmpfs_specific_test.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
package tmpfs
2+
3+
import (
4+
"io"
5+
"io/ioutil"
6+
stdos "os"
7+
"path/filepath"
8+
9+
"gopkg.in/src-d/go-billy.v2"
10+
"gopkg.in/src-d/go-billy.v2/osfs"
11+
12+
. "gopkg.in/check.v1"
13+
)
14+
15+
type SpecificFilesystemSuite struct {
16+
src billy.Filesystem
17+
tmp billy.Filesystem
18+
srcPath string
19+
tmpPath string
20+
}
21+
22+
var _ = Suite(&SpecificFilesystemSuite{})
23+
24+
func (s *SpecificFilesystemSuite) SetUpTest(c *C) {
25+
s.srcPath = c.MkDir()
26+
s.tmpPath = c.MkDir()
27+
s.src = osfs.New(s.srcPath)
28+
s.tmp = osfs.New(s.tmpPath)
29+
}
30+
31+
func (s *SpecificFilesystemSuite) TestTempFileInTmpFs(c *C) {
32+
tmpFs := New(s.src, s.tmp)
33+
c.Assert(tmpFs, NotNil)
34+
35+
f, err := tmpFs.TempFile("test-dir", "test-prefix")
36+
c.Assert(err, IsNil)
37+
c.Assert(f, NotNil)
38+
39+
filename := f.Filename()
40+
c.Assert(f.Close(), IsNil)
41+
42+
_, err = stdos.Stat(filepath.Join(s.srcPath, filename))
43+
c.Assert(stdos.IsNotExist(err), Equals, true)
44+
45+
_, err = stdos.Stat(filepath.Join(s.tmpPath, filename))
46+
c.Assert(err, IsNil)
47+
}
48+
49+
func (s *SpecificFilesystemSuite) TestNonTempFileInSrcFs(c *C) {
50+
tmpFs := New(s.src, s.tmp)
51+
c.Assert(tmpFs, NotNil)
52+
53+
f, err := tmpFs.Create("foo")
54+
c.Assert(err, IsNil)
55+
c.Assert(f, NotNil)
56+
57+
c.Assert(f.Close(), IsNil)
58+
59+
_, err = stdos.Stat(filepath.Join(s.srcPath, "foo"))
60+
c.Assert(err, IsNil)
61+
62+
_, err = stdos.Stat(filepath.Join(s.tmpPath, "foo"))
63+
c.Assert(stdos.IsNotExist(err), Equals, true)
64+
}
65+
66+
func (s *SpecificFilesystemSuite) TestTempFileCanBeReopened(c *C) {
67+
tmpFs := New(s.src, s.tmp)
68+
c.Assert(tmpFs, NotNil)
69+
70+
f, err := tmpFs.TempFile("test-dir", "test-prefix")
71+
c.Assert(err, IsNil)
72+
c.Assert(f, NotNil)
73+
74+
n, err := f.Write([]byte("foo"))
75+
c.Assert(err, IsNil)
76+
c.Assert(n, Equals, 3)
77+
78+
filename := f.Filename()
79+
c.Assert(f.Close(), IsNil)
80+
81+
f, err = tmpFs.Open(filename)
82+
c.Assert(err, IsNil)
83+
c.Assert(f.Filename(), Equals, filename)
84+
85+
content, err := ioutil.ReadAll(f)
86+
c.Assert(err, IsNil)
87+
c.Assert(string(content), Equals, "foo")
88+
89+
c.Assert(f.Close(), IsNil)
90+
}
91+
92+
func (s *SpecificFilesystemSuite) TestTempFileCanBeReopenedByOpenFile(c *C) {
93+
tmpFs := New(s.src, s.tmp)
94+
c.Assert(tmpFs, NotNil)
95+
96+
f, err := tmpFs.TempFile("test-dir", "test-prefix")
97+
c.Assert(err, IsNil)
98+
c.Assert(f, NotNil)
99+
100+
n, err := f.Write([]byte("foo"))
101+
c.Assert(err, IsNil)
102+
c.Assert(n, Equals, 3)
103+
104+
filename := f.Filename()
105+
c.Assert(f.Close(), IsNil)
106+
107+
f, err = tmpFs.OpenFile(filename, stdos.O_RDONLY, 0)
108+
c.Assert(err, IsNil)
109+
c.Assert(f.Filename(), Equals, filename)
110+
111+
content, err := ioutil.ReadAll(f)
112+
c.Assert(err, IsNil)
113+
c.Assert(string(content), Equals, "foo")
114+
115+
c.Assert(f.Close(), IsNil)
116+
}
117+
118+
func (s *SpecificFilesystemSuite) TestStatTempFile(c *C) {
119+
tmpFs := New(s.src, s.tmp)
120+
c.Assert(tmpFs, NotNil)
121+
122+
f, err := tmpFs.TempFile("test-dir", "test-prefix")
123+
c.Assert(err, IsNil)
124+
c.Assert(f, NotNil)
125+
126+
tempFilename := f.Filename()
127+
c.Assert(f.Close(), IsNil)
128+
129+
fi, err := tmpFs.Stat(tempFilename)
130+
c.Assert(err, IsNil)
131+
c.Assert(fi, NotNil)
132+
}
133+
134+
func (s *SpecificFilesystemSuite) TestRenameFromTempFile(c *C) {
135+
tmpFs := New(s.src, s.tmp)
136+
c.Assert(tmpFs, NotNil)
137+
138+
f, err := tmpFs.TempFile("test-dir", "test-prefix")
139+
c.Assert(err, IsNil)
140+
c.Assert(f, NotNil)
141+
142+
tempFilename := f.Filename()
143+
c.Assert(f.Close(), IsNil)
144+
145+
err = tmpFs.Rename(tempFilename, "foo")
146+
c.Assert(err, IsNil)
147+
148+
_, err = s.src.Stat("foo")
149+
c.Assert(err, IsNil)
150+
151+
_, err = s.tmp.Stat("foo")
152+
c.Assert(err, NotNil)
153+
154+
_, err = s.tmp.Stat(tempFilename)
155+
c.Assert(err, NotNil)
156+
}
157+
158+
func (s *SpecificFilesystemSuite) TestCannotRenameToTempFile(c *C) {
159+
tmpFs := New(s.src, s.tmp)
160+
c.Assert(tmpFs, NotNil)
161+
162+
f, err := tmpFs.TempFile("test-dir", "test-prefix")
163+
c.Assert(err, IsNil)
164+
c.Assert(f, NotNil)
165+
166+
tempFilename := f.Filename()
167+
c.Assert(f.Close(), IsNil)
168+
169+
f, err = tmpFs.Create("foo")
170+
c.Assert(err, IsNil)
171+
c.Assert(f, NotNil)
172+
c.Assert(f.Close(), IsNil)
173+
174+
err = tmpFs.Rename("foo", tempFilename)
175+
c.Assert(err, NotNil)
176+
}
177+
178+
func (s *SpecificFilesystemSuite) TestRemoveTempFile(c *C) {
179+
tmpFs := New(s.src, s.tmp)
180+
c.Assert(tmpFs, NotNil)
181+
182+
f, err := tmpFs.TempFile("test-dir", "test-prefix")
183+
c.Assert(err, IsNil)
184+
c.Assert(f, NotNil)
185+
186+
tempFilename := f.Filename()
187+
c.Assert(f.Close(), IsNil)
188+
189+
err = tmpFs.Remove(tempFilename)
190+
c.Assert(err, IsNil)
191+
192+
_, err = s.tmp.Stat(tempFilename)
193+
c.Assert(err, NotNil)
194+
}
195+
196+
func (s *SpecificFilesystemSuite) TestCreateTempFile(c *C) {
197+
tmpFs := New(s.src, s.tmp)
198+
c.Assert(tmpFs, NotNil)
199+
200+
f, err := tmpFs.TempFile("test-dir", "test-prefix")
201+
c.Assert(err, IsNil)
202+
c.Assert(f, NotNil)
203+
204+
n, err := f.Write([]byte("TEST"))
205+
206+
tempFilename := f.Filename()
207+
c.Assert(f.Close(), IsNil)
208+
209+
createdFile, err := tmpFs.Create(tempFilename)
210+
c.Assert(err, IsNil)
211+
c.Assert(createdFile, NotNil)
212+
213+
bRead := make([]byte, 4)
214+
n, err = createdFile.Read(bRead)
215+
c.Assert(n, Equals, 0)
216+
c.Assert(err, Equals, io.EOF)
217+
}

0 commit comments

Comments
 (0)