Sfoglia il codice sorgente

lib/fs: Resolve 8.3 filenames from watcher (ref #3800) (#4975)

Simon Frei 7 anni fa
parent
commit
ee6516aa31

+ 2 - 34
lib/fs/basicfs_test.go

@@ -12,12 +12,12 @@ import (
 	"path/filepath"
 	"runtime"
 	"sort"
-	"strings"
 	"testing"
 	"time"
 )
 
-func setup(t *testing.T) (Filesystem, string) {
+func setup(t *testing.T) (*BasicFilesystem, string) {
+	t.Helper()
 	dir, err := ioutil.TempDir("", "")
 	if err != nil {
 		t.Fatal(err)
@@ -304,38 +304,6 @@ func TestUsage(t *testing.T) {
 	}
 }
 
-func TestWindowsPaths(t *testing.T) {
-	if runtime.GOOS != "windows" {
-		t.Skip("Not useful on non-Windows")
-		return
-	}
-
-	testCases := []struct {
-		input        string
-		expectedRoot string
-		expectedURI  string
-	}{
-		{`e:\`, `\\?\e:\`, `e:\`},
-		{`\\?\e:\`, `\\?\e:\`, `e:\`},
-		{`\\192.0.2.22\network\share`, `\\192.0.2.22\network\share`, `\\192.0.2.22\network\share`},
-	}
-
-	for _, testCase := range testCases {
-		fs := newBasicFilesystem(testCase.input)
-		if fs.root != testCase.expectedRoot {
-			t.Errorf("root %q != %q", fs.root, testCase.expectedRoot)
-		}
-		if fs.URI() != testCase.expectedURI {
-			t.Errorf("uri %q != %q", fs.URI(), testCase.expectedURI)
-		}
-	}
-
-	fs := newBasicFilesystem(`relative\path`)
-	if fs.root == `relative\path` || !strings.HasPrefix(fs.root, "\\\\?\\") {
-		t.Errorf("%q == %q, expected absolutification", fs.root, `relative\path`)
-	}
-}
-
 func TestRooted(t *testing.T) {
 	type testcase struct {
 		root   string

+ 4 - 0
lib/fs/basicfs_unix.go

@@ -51,3 +51,7 @@ func (f *BasicFilesystem) Hide(name string) error {
 func (f *BasicFilesystem) Roots() ([]string, error) {
 	return []string{"/"}, nil
 }
+
+func (f *BasicFilesystem) resolveWin83(absPath string) string {
+	return absPath
+}

+ 1 - 1
lib/fs/basicfs_watch.go

@@ -117,5 +117,5 @@ func (f *BasicFilesystem) unrootedChecked(absPath string) string {
 	if !strings.HasPrefix(absPath, f.rootSymlinkEvaluated) {
 		panic(fmt.Sprintf("bug: Notify backend is processing a change outside of the filesystem root: root==%v, rootSymEval==%v, path==%v", f.root, f.rootSymlinkEvaluated, absPath))
 	}
-	return f.unrootedSymlinkEvaluated(absPath)
+	return f.unrootedSymlinkEvaluated(f.resolveWin83(absPath))
 }

+ 37 - 0
lib/fs/basicfs_windows.go

@@ -14,6 +14,7 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"strings"
 	"syscall"
 	"unsafe"
 )
@@ -150,3 +151,39 @@ func (f *BasicFilesystem) Roots() ([]string, error) {
 
 	return drives, nil
 }
+
+func (f *BasicFilesystem) resolveWin83(absPath string) string {
+	if !isMaybeWin83(absPath) {
+		return absPath
+	}
+	if in, err := syscall.UTF16FromString(absPath); err == nil {
+		out := make([]uint16, 4*len(absPath)) // *2 for UTF16 and *2 to double path length
+		if n, err := syscall.GetLongPathName(&in[0], &out[0], uint32(len(out))); err == nil {
+			if n <= uint32(len(out)) {
+				return syscall.UTF16ToString(out[:n])
+			}
+			out = make([]uint16, n)
+			if _, err = syscall.GetLongPathName(&in[0], &out[0], n); err == nil {
+				return syscall.UTF16ToString(out)
+			}
+		}
+	}
+	// Failed getting the long path. Return the part of the path which is
+	// already a long path.
+	for absPath = filepath.Dir(absPath); strings.HasPrefix(absPath, f.rootSymlinkEvaluated); absPath = filepath.Dir(absPath) {
+		if !isMaybeWin83(absPath) {
+			return absPath
+		}
+	}
+	return f.rootSymlinkEvaluated
+}
+
+func isMaybeWin83(absPath string) bool {
+	if !strings.Contains(absPath, "~") {
+		return false
+	}
+	if strings.Contains(filepath.Dir(absPath), "~") {
+		return true
+	}
+	return strings.Contains(strings.TrimPrefix(filepath.Base(absPath), WindowsTempPrefix), "~")
+}

+ 92 - 0
lib/fs/basicfs_windows_test.go

@@ -0,0 +1,92 @@
+// Copyright (C) 2018 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+// +build windows
+
+package fs
+
+import (
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+)
+
+func TestWindowsPaths(t *testing.T) {
+	testCases := []struct {
+		input        string
+		expectedRoot string
+		expectedURI  string
+	}{
+		{`e:\`, `\\?\e:\`, `e:\`},
+		{`\\?\e:\`, `\\?\e:\`, `e:\`},
+		{`\\192.0.2.22\network\share`, `\\192.0.2.22\network\share`, `\\192.0.2.22\network\share`},
+	}
+
+	for _, testCase := range testCases {
+		fs := newBasicFilesystem(testCase.input)
+		if fs.root != testCase.expectedRoot {
+			t.Errorf("root %q != %q", fs.root, testCase.expectedRoot)
+		}
+		if fs.URI() != testCase.expectedURI {
+			t.Errorf("uri %q != %q", fs.URI(), testCase.expectedURI)
+		}
+	}
+
+	fs := newBasicFilesystem(`relative\path`)
+	if fs.root == `relative\path` || !strings.HasPrefix(fs.root, "\\\\?\\") {
+		t.Errorf("%q == %q, expected absolutification", fs.root, `relative\path`)
+	}
+}
+
+func TestResolveWindows83(t *testing.T) {
+	fs, dir := setup(t)
+	defer os.RemoveAll(dir)
+
+	shortAbs, _ := fs.rooted("LFDATA~1")
+	long := "LFDataTool"
+	longAbs, _ := fs.rooted(long)
+	deleted, _ := fs.rooted(filepath.Join("foo", "LFDATA~1"))
+	notShort, _ := fs.rooted(filepath.Join("foo", "bar", "baz"))
+
+	fd, err := fs.Create(long)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fd.Close()
+
+	if res := fs.resolveWin83(shortAbs); res != longAbs {
+		t.Errorf(`Resolving for 8.3 names of "%v" resulted in "%v", expected "%v"`, shortAbs, res, longAbs)
+	}
+	if res := fs.resolveWin83(deleted); res != filepath.Dir(deleted) {
+		t.Errorf(`Resolving for 8.3 names of "%v" resulted in "%v", expected "%v"`, deleted, res, filepath.Dir(deleted))
+	}
+	if res := fs.resolveWin83(notShort); res != notShort {
+		t.Errorf(`Resolving for 8.3 names of "%v" resulted in "%v", expected "%v"`, notShort, res, notShort)
+	}
+}
+
+func TestIsWindows83(t *testing.T) {
+	fs, dir := setup(t)
+	defer os.RemoveAll(dir)
+
+	tempTop, _ := fs.rooted(TempName("baz"))
+	tempBelow, _ := fs.rooted(filepath.Join("foo", "bar", TempName("baz")))
+	short, _ := fs.rooted(filepath.Join("LFDATA~1", TempName("baz")))
+	tempAndShort, _ := fs.rooted(filepath.Join("LFDATA~1", TempName("baz")))
+
+	for _, f := range []string{tempTop, tempBelow} {
+		if isMaybeWin83(f) {
+			t.Errorf(`"%v" is not a windows 8.3 path"`, f)
+		}
+	}
+
+	for _, f := range []string{short, tempAndShort} {
+		if !isMaybeWin83(f) {
+			t.Errorf(`"%v" is not a windows 8.3 path"`, f)
+		}
+	}
+}