Skip to content

Commit e22043c

Browse files
committed
walk: implement FsEval hooks
In certain circumstances (such as the manifest generation of a filesystem as an unprivileged user) it is important to provide hooks that override the default os.* implementation of filesystem-related functions. In order to avoid merging too much code from outside projects (such as umoci) this is implemented by providing FsEval hooks to Walk() and Check(). This allows for users of go-mtree to modify how filesystem checks are done, without compromising the simplicity of go-mtree's code. Signed-off-by: Aleksa Sarai <[email protected]>
1 parent 98824a8 commit e22043c

File tree

9 files changed

+110
-57
lines changed

9 files changed

+110
-57
lines changed

Diff for: check.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ package mtree
55
// If keywords is nil, the check all present in the DirectoryHierarchy
66
//
77
// This is equivalent to creating a new DirectoryHierarchy with Walk(root, nil,
8-
// keywords) and then doing a Compare(dh, newDh, keywords).
9-
func Check(root string, dh *DirectoryHierarchy, keywords []Keyword) ([]InodeDelta, error) {
8+
// keywords, fs) and then doing a Compare(dh, newDh, keywords).
9+
func Check(root string, dh *DirectoryHierarchy, keywords []Keyword, fs FsEval) ([]InodeDelta, error) {
1010
if keywords == nil {
1111
keywords = dh.UsedKeywords()
1212
}
1313

14-
newDh, err := Walk(root, nil, keywords)
14+
newDh, err := Walk(root, nil, keywords, fs)
1515
if err != nil {
1616
return nil, err
1717
}

Diff for: check_test.go

+17-17
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ import (
1212
// simple walk of current directory, and imediately check it.
1313
// may not be parallelizable.
1414
func TestCheck(t *testing.T) {
15-
dh, err := Walk(".", nil, append(DefaultKeywords, "sha1"))
15+
dh, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
1616
if err != nil {
1717
t.Fatal(err)
1818
}
1919

20-
res, err := Check(".", dh, nil)
20+
res, err := Check(".", dh, nil, nil)
2121
if err != nil {
2222
t.Fatal(err)
2323
}
@@ -43,13 +43,13 @@ func TestCheckKeywords(t *testing.T) {
4343
}
4444

4545
// Walk this tempdir
46-
dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
46+
dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
4747
if err != nil {
4848
t.Fatal(err)
4949
}
5050

5151
// Check for sanity. This ought to pass.
52-
res, err := Check(dir, dh, nil)
52+
res, err := Check(dir, dh, nil, nil)
5353
if err != nil {
5454
t.Fatal(err)
5555
}
@@ -64,7 +64,7 @@ func TestCheckKeywords(t *testing.T) {
6464
}
6565

6666
// Check again. This ought to fail.
67-
res, err = Check(dir, dh, nil)
67+
res, err = Check(dir, dh, nil, nil)
6868
if err != nil {
6969
t.Fatal(err)
7070
}
@@ -76,7 +76,7 @@ func TestCheckKeywords(t *testing.T) {
7676
}
7777

7878
// Check again, but only sha1 and mode. This ought to pass.
79-
res, err = Check(dir, dh, []Keyword{"sha1", "mode"})
79+
res, err = Check(dir, dh, []Keyword{"sha1", "mode"}, nil)
8080
if err != nil {
8181
t.Fatal(err)
8282
}
@@ -86,12 +86,12 @@ func TestCheckKeywords(t *testing.T) {
8686
}
8787

8888
func ExampleCheck() {
89-
dh, err := Walk(".", nil, append(DefaultKeywords, "sha1"))
89+
dh, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
9090
if err != nil {
9191
// handle error ...
9292
}
9393

94-
res, err := Check(".", dh, nil)
94+
res, err := Check(".", dh, nil, nil)
9595
if err != nil {
9696
// handle error ...
9797
}
@@ -103,11 +103,11 @@ func ExampleCheck() {
103103
// Tests default action for evaluating a symlink, which is just to compare the
104104
// link itself, not to follow it
105105
func TestDefaultBrokenLink(t *testing.T) {
106-
dh, err := Walk("./testdata/dirwithbrokenlink", nil, append(DefaultKeywords, "sha1"))
106+
dh, err := Walk("./testdata/dirwithbrokenlink", nil, append(DefaultKeywords, "sha1"), nil)
107107
if err != nil {
108108
t.Fatal(err)
109109
}
110-
res, err := Check("./testdata/dirwithbrokenlink", dh, nil)
110+
res, err := Check("./testdata/dirwithbrokenlink", dh, nil, nil)
111111
if err != nil {
112112
t.Fatal(err)
113113
}
@@ -155,7 +155,7 @@ func TestTimeComparison(t *testing.T) {
155155
t.Fatal(err)
156156
}
157157

158-
res, err := Check(dir, dh, nil)
158+
res, err := Check(dir, dh, nil, nil)
159159
if err != nil {
160160
t.Error(err)
161161
}
@@ -203,13 +203,13 @@ func TestTarTime(t *testing.T) {
203203
keywords := dh.UsedKeywords()
204204

205205
// make sure "time" keyword works
206-
_, err = Check(dir, dh, keywords)
206+
_, err = Check(dir, dh, keywords, nil)
207207
if err != nil {
208208
t.Error(err)
209209
}
210210

211211
// make sure tar_time wins
212-
res, err := Check(dir, dh, append(keywords, "tar_time"))
212+
res, err := Check(dir, dh, append(keywords, "tar_time"), nil)
213213
if err != nil {
214214
t.Error(err)
215215
}
@@ -254,7 +254,7 @@ func TestIgnoreComments(t *testing.T) {
254254
t.Fatal(err)
255255
}
256256

257-
res, err := Check(dir, dh, nil)
257+
res, err := Check(dir, dh, nil, nil)
258258
if err != nil {
259259
t.Error(err)
260260
}
@@ -274,7 +274,7 @@ func TestIgnoreComments(t *testing.T) {
274274
`
275275
dh, err = ParseSpec(bytes.NewBufferString(spec))
276276

277-
res, err = Check(dir, dh, nil)
277+
res, err = Check(dir, dh, nil, nil)
278278
if err != nil {
279279
t.Error(err)
280280
}
@@ -306,11 +306,11 @@ func TestCheckNeedsEncoding(t *testing.T) {
306306
t.Error(err)
307307
}
308308

309-
dh, err := Walk(dir, nil, DefaultKeywords)
309+
dh, err := Walk(dir, nil, DefaultKeywords, nil)
310310
if err != nil {
311311
t.Fatal(err)
312312
}
313-
res, err := Check(dir, dh, nil)
313+
res, err := Check(dir, dh, nil, nil)
314314
if err != nil {
315315
t.Fatal(err)
316316
}

Diff for: cmd/gomtree/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ func app() error {
236236
}
237237
} else {
238238
// with a root directory
239-
stateDh, err = mtree.Walk(rootPath, excludes, currentKeywords)
239+
stateDh, err = mtree.Walk(rootPath, excludes, currentKeywords, nil)
240240
if err != nil {
241241
return err
242242
}

Diff for: compare_test.go

+11-11
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ import (
1515
// simple walk of current directory, and imediately check it.
1616
// may not be parallelizable.
1717
func TestCompare(t *testing.T) {
18-
old, err := Walk(".", nil, append(DefaultKeywords, "sha1"))
18+
old, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
1919
if err != nil {
2020
t.Fatal(err)
2121
}
2222

23-
new, err := Walk(".", nil, append(DefaultKeywords, "sha1"))
23+
new, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
2424
if err != nil {
2525
t.Fatal(err)
2626
}
@@ -59,7 +59,7 @@ func TestCompareModified(t *testing.T) {
5959
}
6060

6161
// Walk the current state.
62-
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
62+
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
6363
if err != nil {
6464
t.Fatal(err)
6565
}
@@ -70,7 +70,7 @@ func TestCompareModified(t *testing.T) {
7070
}
7171

7272
// Walk the new state.
73-
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
73+
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
7474
if err != nil {
7575
t.Fatal(err)
7676
}
@@ -140,7 +140,7 @@ func TestCompareMissing(t *testing.T) {
140140
}
141141

142142
// Walk the current state.
143-
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
143+
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
144144
if err != nil {
145145
t.Fatal(err)
146146
}
@@ -159,7 +159,7 @@ func TestCompareMissing(t *testing.T) {
159159
}
160160

161161
// Walk the new state.
162-
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
162+
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
163163
if err != nil {
164164
t.Fatal(err)
165165
}
@@ -215,7 +215,7 @@ func TestCompareExtra(t *testing.T) {
215215
defer os.RemoveAll(dir)
216216

217217
// Walk the current state.
218-
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
218+
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
219219
if err != nil {
220220
t.Fatal(err)
221221
}
@@ -237,7 +237,7 @@ func TestCompareExtra(t *testing.T) {
237237
}
238238

239239
// Walk the new state.
240-
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
240+
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
241241
if err != nil {
242242
t.Fatal(err)
243243
}
@@ -309,7 +309,7 @@ func TestCompareKeys(t *testing.T) {
309309
}
310310

311311
// Walk the current state.
312-
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
312+
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
313313
if err != nil {
314314
t.Fatal(err)
315315
}
@@ -320,7 +320,7 @@ func TestCompareKeys(t *testing.T) {
320320
}
321321

322322
// Walk the new state.
323-
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
323+
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
324324
if err != nil {
325325
t.Fatal(err)
326326
}
@@ -382,7 +382,7 @@ func TestTarCompare(t *testing.T) {
382382
}
383383

384384
// Walk the current state.
385-
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
385+
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
386386
if err != nil {
387387
t.Fatal(err)
388388
}

Diff for: creator.go

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package mtree
33
// dhCreator is used in when building a DirectoryHierarchy
44
type dhCreator struct {
55
DH *DirectoryHierarchy
6+
fs FsEval
67
curSet *Entry
78
curDir *Entry
89
curEnt *Entry

Diff for: fseval.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package mtree
2+
3+
import "os"
4+
5+
// FsEval is a mock-friendly method of specifying to go-mtree how to carry out
6+
// filesystem operations such as opening files and the like. The semantics of
7+
// all of these wrappers MUST be identical to the semantics described here.
8+
type FsEval interface {
9+
// Open must have the same semantics as os.Open.
10+
Open(path string) (*os.File, error)
11+
12+
// Lstat must have the same semantics as os.Lstat.
13+
Lstat(path string) (os.FileInfo, error)
14+
15+
// Readdir must have the same semantics as calling os.Open on the given
16+
// path and then returning the result of (*os.File).Readdir(-1).
17+
Readdir(path string) ([]os.FileInfo, error)
18+
19+
// KeywordFunc must return a wrapper around the provided function (in other
20+
// words, the returned function must refer to the same keyword).
21+
KeywordFunc(fn KeywordFunc) KeywordFunc
22+
}
23+
24+
// DefaultFsEval is the default implementation of FsEval (and is the default
25+
// used if a nil interface is passed to any mtree function). It does not modify
26+
// or wrap any of the methods (they all just call out to os.*).
27+
type DefaultFsEval struct{}
28+
29+
// Open must have the same semantics as os.Open.
30+
func (fs DefaultFsEval) Open(path string) (*os.File, error) {
31+
return os.Open(path)
32+
}
33+
34+
// Lstat must have the same semantics as os.Lstat.
35+
func (fs DefaultFsEval) Lstat(path string) (os.FileInfo, error) {
36+
return os.Lstat(path)
37+
}
38+
39+
// Readdir must have the same semantics as calling os.Open on the given
40+
// path and then returning the result of (*os.File).Readdir(-1).
41+
func (fs DefaultFsEval) Readdir(path string) ([]os.FileInfo, error) {
42+
fh, err := os.Open(path)
43+
if err != nil {
44+
return nil, err
45+
}
46+
defer fh.Close()
47+
return fh.Readdir(-1)
48+
}
49+
50+
// KeywordFunc must return a wrapper around the provided function (in other
51+
// words, the returned function must refer to the same keyword).
52+
func (fs DefaultFsEval) KeywordFunc(fn KeywordFunc) KeywordFunc {
53+
return fn
54+
}

Diff for: tar_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func ExampleStreamer() {
2626
// handle error ...
2727
}
2828

29-
res, err := Check("/tmp/dir/", dh, nil)
29+
res, err := Check("/tmp/dir/", dh, nil, nil)
3030
if err != nil {
3131
// handle error ...
3232
}
@@ -145,7 +145,7 @@ func TestArchiveCreation(t *testing.T) {
145145
}
146146

147147
// Test the tar manifest against the actual directory
148-
res, err := Check("./testdata/collection", tdh, []Keyword{"sha1"})
148+
res, err := Check("./testdata/collection", tdh, []Keyword{"sha1"}, nil)
149149
if err != nil {
150150
t.Fatal(err)
151151
}
@@ -170,7 +170,7 @@ func TestArchiveCreation(t *testing.T) {
170170
}
171171

172172
// Validate the directory manifest against the archive
173-
dh, err := Walk("./testdata/collection", nil, []Keyword{"sha1"})
173+
dh, err := Walk("./testdata/collection", nil, []Keyword{"sha1"}, nil)
174174
if err != nil {
175175
t.Fatal(err)
176176
}
@@ -224,7 +224,7 @@ func TestTreeTraversal(t *testing.T) {
224224
}
225225

226226
// top-level "." directory will contain contents of traversal.tar
227-
res, err = Check("./testdata/.", tdh, []Keyword{"sha1"})
227+
res, err = Check("./testdata/.", tdh, []Keyword{"sha1"}, nil)
228228
if err != nil {
229229
t.Fatal(err)
230230
}
@@ -262,7 +262,7 @@ func TestTreeTraversal(t *testing.T) {
262262
}
263263

264264
// Implied top-level "." directory will contain the contents of singlefile.tar
265-
res, err = Check("./testdata/.", tdh, []Keyword{"sha1"})
265+
res, err = Check("./testdata/.", tdh, []Keyword{"sha1"}, nil)
266266
if err != nil {
267267
t.Fatal(err)
268268
}

0 commit comments

Comments
 (0)