Skip to content

Commit 3bf99b6

Browse files
committed
webdav: make the memFS (not the memFSNode) the canonical source of a
file name. This fixes inconsistent stat names after a file is renamed. Change-Id: Ie90f8abaa31d46a87834266053b61d7770f854e2 Reviewed-on: https://go-review.googlesource.com/3051 Reviewed-by: Nick Cooper <nmvc@google.com> Reviewed-by: Dave Cheney <dave@cheney.net>
1 parent 5058c78 commit 3bf99b6

File tree

2 files changed

+101
-51
lines changed

2 files changed

+101
-51
lines changed

webdav/file.go

+34-45
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ func (fs *memFS) walk(op, fullname string, f func(dir *memFSNode, frag string, f
194194
Err: os.ErrNotExist,
195195
}
196196
}
197-
if !child.IsDir() {
197+
if !child.mode.IsDir() {
198198
return &os.PathError{
199199
Op: op,
200200
Path: original,
@@ -245,7 +245,6 @@ func (fs *memFS) Mkdir(name string, perm os.FileMode) error {
245245
return os.ErrExist
246246
}
247247
dir.children[frag] = &memFSNode{
248-
name: frag,
249248
children: make(map[string]*memFSNode),
250249
mode: perm.Perm() | os.ModeDir,
251250
modTime: time.Now(),
@@ -267,7 +266,7 @@ func (fs *memFS) OpenFile(name string, flag int, perm os.FileMode) (File, error)
267266
if flag&(os.O_WRONLY|os.O_RDWR) != 0 {
268267
return nil, os.ErrPermission
269268
}
270-
n = &fs.root
269+
n, frag = &fs.root, "/"
271270

272271
} else {
273272
n = dir.children[frag]
@@ -281,7 +280,6 @@ func (fs *memFS) OpenFile(name string, flag int, perm os.FileMode) (File, error)
281280
}
282281
if n == nil {
283282
n = &memFSNode{
284-
name: frag,
285283
mode: perm.Perm(),
286284
}
287285
dir.children[frag] = n
@@ -298,11 +296,12 @@ func (fs *memFS) OpenFile(name string, flag int, perm os.FileMode) (File, error)
298296
}
299297

300298
children := make([]os.FileInfo, 0, len(n.children))
301-
for _, c := range n.children {
302-
children = append(children, c)
299+
for cName, c := range n.children {
300+
children = append(children, c.stat(cName))
303301
}
304302
return &memFile{
305303
n: n,
304+
nameSnapshot: frag,
306305
childrenSnapshot: children,
307306
}, nil
308307
}
@@ -359,12 +358,9 @@ func (fs *memFS) Rename(oldName, newName string) error {
359358
if !ok {
360359
return os.ErrNotExist
361360
}
362-
if oNode.IsDir() {
361+
if oNode.children != nil {
363362
if nNode, ok := nDir.children[nFrag]; ok {
364-
nNode.mu.Lock()
365-
isDir := nNode.mode.IsDir()
366-
nNode.mu.Unlock()
367-
if !isDir {
363+
if nNode.children == nil {
368364
return errNotADirectory
369365
}
370366
if len(nNode.children) != 0 {
@@ -387,64 +383,57 @@ func (fs *memFS) Stat(name string) (os.FileInfo, error) {
387383
}
388384
if dir == nil {
389385
// We're stat'ting the root.
390-
return &fs.root, nil
386+
return fs.root.stat("/"), nil
391387
}
392388
if n, ok := dir.children[frag]; ok {
393-
return n, nil
389+
return n.stat(path.Base(name)), nil
394390
}
395391
return nil, os.ErrNotExist
396392
}
397393

398394
// A memFSNode represents a single entry in the in-memory filesystem and also
399395
// implements os.FileInfo.
400396
type memFSNode struct {
401-
name string
402-
403397
// children is protected by memFS.mu.
404398
children map[string]*memFSNode
405399

406400
mu sync.Mutex
407-
modTime time.Time
408-
mode os.FileMode
409401
data []byte
402+
mode os.FileMode
403+
modTime time.Time
410404
}
411405

412-
func (n *memFSNode) Name() string {
413-
return n.name
414-
}
415-
416-
func (n *memFSNode) Size() int64 {
417-
n.mu.Lock()
418-
defer n.mu.Unlock()
419-
return int64(len(n.data))
420-
}
421-
422-
func (n *memFSNode) Mode() os.FileMode {
423-
n.mu.Lock()
424-
defer n.mu.Unlock()
425-
return n.mode
426-
}
427-
428-
func (n *memFSNode) ModTime() time.Time {
406+
func (n *memFSNode) stat(name string) *memFileInfo {
429407
n.mu.Lock()
430408
defer n.mu.Unlock()
431-
return n.modTime
409+
return &memFileInfo{
410+
name: name,
411+
size: int64(len(n.data)),
412+
mode: n.mode,
413+
modTime: n.modTime,
414+
}
432415
}
433416

434-
func (n *memFSNode) IsDir() bool {
435-
return n.Mode().IsDir()
417+
type memFileInfo struct {
418+
name string
419+
size int64
420+
mode os.FileMode
421+
modTime time.Time
436422
}
437423

438-
func (n *memFSNode) Sys() interface{} {
439-
return nil
440-
}
424+
func (f *memFileInfo) Name() string { return f.name }
425+
func (f *memFileInfo) Size() int64 { return f.size }
426+
func (f *memFileInfo) Mode() os.FileMode { return f.mode }
427+
func (f *memFileInfo) ModTime() time.Time { return f.modTime }
428+
func (f *memFileInfo) IsDir() bool { return f.mode.IsDir() }
429+
func (f *memFileInfo) Sys() interface{} { return nil }
441430

442431
// A memFile is a File implementation for a memFSNode. It is a per-file (not
443-
// per-node) read/write position, and if the node is a directory, a snapshot of
444-
// that node's children.
432+
// per-node) read/write position, and a snapshot of the memFS' tree structure
433+
// (a node's name and children) for that node.
445434
type memFile struct {
446-
n *memFSNode
447-
// childrenSnapshot is a snapshot of n.children.
435+
n *memFSNode
436+
nameSnapshot string
448437
childrenSnapshot []os.FileInfo
449438
// pos is protected by n.mu.
450439
pos int
@@ -518,7 +507,7 @@ func (f *memFile) Seek(offset int64, whence int) (int64, error) {
518507
}
519508

520509
func (f *memFile) Stat() (os.FileInfo, error) {
521-
return f.n, nil
510+
return f.n.stat(f.nameSnapshot), nil
522511
}
523512

524513
func (f *memFile) Write(p []byte) (int, error) {

webdav/file_test.go

+67-6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"os"
1212
"path"
1313
"path/filepath"
14+
"reflect"
15+
"sort"
1416
"strconv"
1517
"strings"
1618
"testing"
@@ -202,10 +204,10 @@ func TestWalk(t *testing.T) {
202204
}
203205
}
204206

205-
i := 0
207+
i, prevFrag := 0, ""
206208
err := fs.walk("test", tc.dir, func(dir *memFSNode, frag string, final bool) error {
207209
got := walkStep{
208-
name: dir.name,
210+
name: prevFrag,
209211
frag: frag,
210212
final: final,
211213
}
@@ -214,7 +216,7 @@ func TestWalk(t *testing.T) {
214216
if got != want {
215217
return fmt.Errorf("got %+v, want %+v", got, want)
216218
}
217-
i++
219+
i, prevFrag = i+1, frag
218220
return nil
219221
})
220222
if err != nil {
@@ -223,6 +225,36 @@ func TestWalk(t *testing.T) {
223225
}
224226
}
225227

228+
// find appends to ss the names of the named file and its children. It is
229+
// analogous to the Unix find command.
230+
//
231+
// The returned strings are not guaranteed to be in any particular order.
232+
func find(ss []string, fs FileSystem, name string) ([]string, error) {
233+
stat, err := fs.Stat(name)
234+
if err != nil {
235+
return nil, err
236+
}
237+
ss = append(ss, name)
238+
if stat.IsDir() {
239+
f, err := fs.OpenFile(name, os.O_RDONLY, 0)
240+
if err != nil {
241+
return nil, err
242+
}
243+
defer f.Close()
244+
children, err := f.Readdir(-1)
245+
if err != nil {
246+
return nil, err
247+
}
248+
for _, c := range children {
249+
ss, err = find(ss, fs, path.Join(name, c.Name()))
250+
if err != nil {
251+
return nil, err
252+
}
253+
}
254+
}
255+
return ss, nil
256+
}
257+
226258
func testFS(t *testing.T, fs FileSystem) {
227259
errStr := func(err error) string {
228260
switch {
@@ -236,8 +268,8 @@ func testFS(t *testing.T, fs FileSystem) {
236268
return "ok"
237269
}
238270

239-
// The non-"stat" test cases should change the file system state. The
240-
// indentation of the "stat"s helps distinguish such test cases.
271+
// The non-"find" non-"stat" test cases should change the file system state. The
272+
// indentation of the "find"s and "stat"s helps distinguish such test cases.
241273
testCases := []string{
242274
" stat / want dir",
243275
" stat /a want errNotExist",
@@ -252,6 +284,7 @@ func testFS(t *testing.T, fs FileSystem) {
252284
" stat /d want dir",
253285
"create /d/e EEE want ok",
254286
" stat /d/e want 3",
287+
" find / /a /d /d/e",
255288
"create /d/f FFFF want ok",
256289
"create /d/g GGGGGGG want ok",
257290
"mk-dir /d/m want ok",
@@ -263,6 +296,7 @@ func testFS(t *testing.T, fs FileSystem) {
263296
" stat /d/h want errNotExist",
264297
" stat /d/m want dir",
265298
" stat /d/m/p want 5",
299+
" find / /a /d /d/e /d/f /d/g /d/m /d/m/p",
266300
"rm-all /d want ok",
267301
" stat /a want 1",
268302
" stat /d want errNotExist",
@@ -271,6 +305,7 @@ func testFS(t *testing.T, fs FileSystem) {
271305
" stat /d/g want errNotExist",
272306
" stat /d/m want errNotExist",
273307
" stat /d/m/p want errNotExist",
308+
" find / /a",
274309
"mk-dir /d/m want errNotExist",
275310
"mk-dir /d want ok",
276311
"create /d/f FFFF want ok",
@@ -285,6 +320,7 @@ func testFS(t *testing.T, fs FileSystem) {
285320
" stat /c want errNotExist",
286321
" stat /d want dir",
287322
" stat /d/m want dir",
323+
" find / /a /b /d /d/m",
288324
"rename /b /b want ok",
289325
" stat /b want 2",
290326
" stat /c want errNotExist",
@@ -293,6 +329,7 @@ func testFS(t *testing.T, fs FileSystem) {
293329
" stat /c want 2",
294330
" stat /d/m want dir",
295331
" stat /d/n want errNotExist",
332+
" find / /a /c /d /d/m",
296333
"rename /d/m /d/n want ok",
297334
"create /d/n/q QQQQ want ok",
298335
" stat /d/m want errNotExist",
@@ -302,6 +339,7 @@ func testFS(t *testing.T, fs FileSystem) {
302339
"rename /c /d/n/q want ok",
303340
" stat /c want errNotExist",
304341
" stat /d/n/q want 2",
342+
" find / /a /d /d/n /d/n/q",
305343
"create /d/n/r RRRRR want ok",
306344
"mk-dir /u want ok",
307345
"mk-dir /u/v want ok",
@@ -316,10 +354,12 @@ func testFS(t *testing.T, fs FileSystem) {
316354
" stat /t want dir",
317355
" stat /t/q want 2",
318356
" stat /t/r want 5",
357+
" find / /a /d /t /t/q /t/r /u /u/v",
319358
"rename /t / want err",
320359
"rename /t /u/v want ok",
321360
" stat /u/v/r want 5",
322361
"rename / /x want err",
362+
" find / /a /d /u /u/v /u/v/q /u/v/r",
323363
}
324364

325365
for i, tc := range testCases {
@@ -352,6 +392,17 @@ func testFS(t *testing.T, fs FileSystem) {
352392
}
353393
}
354394

395+
case "find":
396+
got, err := find(nil, fs, "/")
397+
if err != nil {
398+
t.Fatalf("test case #%d %q: find: %v", i, tc, err)
399+
}
400+
sort.Strings(got)
401+
want := strings.Split(arg, " ")
402+
if !reflect.DeepEqual(got, want) {
403+
t.Fatalf("test case #%d %q:\ngot %s\nwant %s", i, tc, got, want)
404+
}
405+
355406
case "mk-dir", "rename", "rm-all", "stat":
356407
nParts := 3
357408
if op == "rename" {
@@ -372,12 +423,22 @@ func testFS(t *testing.T, fs FileSystem) {
372423
opErr = fs.RemoveAll(parts[0])
373424
case "stat":
374425
var stat os.FileInfo
375-
if stat, opErr = fs.Stat(parts[0]); opErr == nil {
426+
fileName := parts[0]
427+
if stat, opErr = fs.Stat(fileName); opErr == nil {
376428
if stat.IsDir() {
377429
got = "dir"
378430
} else {
379431
got = strconv.Itoa(int(stat.Size()))
380432
}
433+
434+
if fileName == "/" {
435+
// For a Dir FileSystem, the virtual file system root maps to a
436+
// real file system name like "/tmp/webdav-test012345", which does
437+
// not end with "/". We skip such cases.
438+
} else if statName := stat.Name(); path.Base(fileName) != statName {
439+
t.Fatalf("test case #%d %q: file name %q inconsistent with stat name %q",
440+
i, tc, fileName, statName)
441+
}
381442
}
382443
}
383444
if got == "" {

0 commit comments

Comments
 (0)