Skip to content

Commit 2925427

Browse files
committed
os: on Windows, don't fix long paths that aren't long
Notably, don't allocate. Follow-up to https://golang.org/cl/32451 which added long path cleaning. Updates #3358 Change-Id: I89c59cbd660d0a030f31b6acd070fa9f3250683b Reviewed-on: https://go-review.googlesource.com/32886 Run-TryBot: Brad Fitzpatrick <[email protected]> Reviewed-by: Russ Cox <[email protected]>
1 parent 76d8e60 commit 2925427

File tree

2 files changed

+50
-19
lines changed

2 files changed

+50
-19
lines changed

Diff for: src/os/path_windows.go

+23-9
Original file line numberDiff line numberDiff line change
@@ -129,26 +129,40 @@ func dirname(path string) string {
129129
}
130130

131131
// fixLongPath returns the extended-length (\\?\-prefixed) form of
132-
// path if possible, in order to avoid the default 260 character file
133-
// path limit imposed by Windows. If path is not easily converted to
132+
// path when needed, in order to avoid the default 260 character file
133+
// path limit imposed by Windows. If path is not easily converted to
134134
// the extended-length form (for example, if path is a relative path
135-
// or contains .. elements), fixLongPath returns path unmodified.
135+
// or contains .. elements), or is short enough, fixLongPath returns
136+
// path unmodified.
137+
//
138+
// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
136139
func fixLongPath(path string) string {
140+
// Do nothing (and don't allocate) if the path is "short".
141+
// Empirically (at least on the Windows Server 2013 builder),
142+
// the kernel is arbitrarily okay with <= 248 bytes. That
143+
// matches what the docs above say:
144+
// "When using an API to create a directory, the specified
145+
// path cannot be so long that you cannot append an 8.3 file
146+
// name (that is, the directory name cannot exceed MAX_PATH
147+
// minus 12)." Since MAX_PATH is 260, 260 - 12 = 248.
148+
if len(path) <= 248 {
149+
// Don't fix. (This is how Go 1.7 and earlier worked,
150+
// not automatically generating the \\?\ form)
151+
return path
152+
}
153+
137154
// The extended form begins with \\?\, as in
138155
// \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt.
139156
// The extended form disables evaluation of . and .. path
140157
// elements and disables the interpretation of / as equivalent
141-
// to \. The conversion here rewrites / to \ and elides
158+
// to \. The conversion here rewrites / to \ and elides
142159
// . elements as well as trailing or duplicate separators. For
143160
// simplicity it avoids the conversion entirely for relative
144-
// paths or paths containing .. elements. For now,
161+
// paths or paths containing .. elements. For now,
145162
// \\server\share paths are not converted to
146163
// \\?\UNC\server\share paths because the rules for doing so
147164
// are less well-specified.
148-
//
149-
// For details of \\?\ paths, see:
150-
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
151-
if len(path) == 0 || (len(path) >= 2 && path[:2] == `\\`) {
165+
if len(path) >= 2 && path[:2] == `\\` {
152166
// Don't canonicalize UNC paths.
153167
return path
154168
}

Diff for: src/os/path_windows_test.go

+27-10
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,40 @@ package os_test
66

77
import (
88
"os"
9+
"strings"
910
"testing"
1011
)
1112

1213
func TestFixLongPath(t *testing.T) {
14+
// 248 is long enough to trigger the longer-than-248 checks in
15+
// fixLongPath, but short enough not to make a path component
16+
// longer than 255, which is illegal on Windows. (which
17+
// doesn't really matter anyway, since this is purely a string
18+
// function we're testing, and it's not actually being used to
19+
// do a system call)
20+
veryLong := "l" + strings.Repeat("o", 248) + "ng"
1321
for _, test := range []struct{ in, want string }{
14-
{`C:\foo.txt`, `\\?\C:\foo.txt`},
15-
{`C:/foo.txt`, `\\?\C:\foo.txt`},
16-
{`C:\foo\\bar\.\baz\\`, `\\?\C:\foo\bar\baz`},
17-
{`C:\`, `\\?\C:\`}, // drives must have a trailing slash
22+
// Short; unchanged:
23+
{`C:\short.txt`, `C:\short.txt`},
24+
{`C:\`, `C:\`},
25+
{`C:`, `C:`},
26+
// The "long" substring is replaced by a looooooong
27+
// string which triggers the rewriting. Except in the
28+
// cases below where it doesn't.
29+
{`C:\long\foo.txt`, `\\?\C:\long\foo.txt`},
30+
{`C:/long/foo.txt`, `\\?\C:\long\foo.txt`},
31+
{`C:\long\foo\\bar\.\baz\\`, `\\?\C:\long\foo\bar\baz`},
1832
{`\\unc\path`, `\\unc\path`},
19-
{`foo.txt`, `foo.txt`},
20-
{`C:foo.txt`, `C:foo.txt`},
21-
{`c:\foo\..\bar\baz`, `c:\foo\..\bar\baz`},
22-
{`\\?\c:\windows\foo.txt`, `\\?\c:\windows\foo.txt`},
23-
{`\\?\c:\windows/foo.txt`, `\\?\c:\windows/foo.txt`},
33+
{`long.txt`, `long.txt`},
34+
{`C:long.txt`, `C:long.txt`},
35+
{`c:\long\..\bar\baz`, `c:\long\..\bar\baz`},
36+
{`\\?\c:\long\foo.txt`, `\\?\c:\long\foo.txt`},
37+
{`\\?\c:\long/foo.txt`, `\\?\c:\long/foo.txt`},
2438
} {
25-
if got := os.FixLongPath(test.in); got != test.want {
39+
in := strings.Replace(test.in, "long", veryLong, -1)
40+
want := strings.Replace(test.want, "long", veryLong, -1)
41+
if got := os.FixLongPath(in); got != want {
42+
got = strings.Replace(got, veryLong, "long", -1)
2643
t.Errorf("fixLongPath(%q) = %q; want %q", test.in, got, test.want)
2744
}
2845
}

0 commit comments

Comments
 (0)