1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package os
6
7import (
8	"internal/filepathlite"
9	"internal/syscall/windows"
10	"syscall"
11)
12
13const (
14	PathSeparator     = '\\' // OS-specific path separator
15	PathListSeparator = ';'  // OS-specific path list separator
16)
17
18// IsPathSeparator reports whether c is a directory separator character.
19func IsPathSeparator(c uint8) bool {
20	// NOTE: Windows accepts / as path separator.
21	return c == '\\' || c == '/'
22}
23
24func dirname(path string) string {
25	vol := filepathlite.VolumeName(path)
26	i := len(path) - 1
27	for i >= len(vol) && !IsPathSeparator(path[i]) {
28		i--
29	}
30	dir := path[len(vol) : i+1]
31	last := len(dir) - 1
32	if last > 0 && IsPathSeparator(dir[last]) {
33		dir = dir[:last]
34	}
35	if dir == "" {
36		dir = "."
37	}
38	return vol + dir
39}
40
41// fixLongPath returns the extended-length (\\?\-prefixed) form of
42// path when needed, in order to avoid the default 260 character file
43// path limit imposed by Windows. If the path is short enough or already
44// has the extended-length prefix, fixLongPath returns path unmodified.
45// If the path is relative and joining it with the current working
46// directory results in a path that is too long, fixLongPath returns
47// the absolute path with the extended-length prefix.
48//
49// See https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation
50func fixLongPath(path string) string {
51	if windows.CanUseLongPaths {
52		return path
53	}
54	return addExtendedPrefix(path)
55}
56
57// addExtendedPrefix adds the extended path prefix (\\?\) to path.
58func addExtendedPrefix(path string) string {
59	if len(path) >= 4 {
60		if path[:4] == `\??\` {
61			// Already extended with \??\
62			return path
63		}
64		if IsPathSeparator(path[0]) && IsPathSeparator(path[1]) && path[2] == '?' && IsPathSeparator(path[3]) {
65			// Already extended with \\?\ or any combination of directory separators.
66			return path
67		}
68	}
69
70	// Do nothing (and don't allocate) if the path is "short".
71	// Empirically (at least on the Windows Server 2013 builder),
72	// the kernel is arbitrarily okay with < 248 bytes. That
73	// matches what the docs above say:
74	// "When using an API to create a directory, the specified
75	// path cannot be so long that you cannot append an 8.3 file
76	// name (that is, the directory name cannot exceed MAX_PATH
77	// minus 12)." Since MAX_PATH is 260, 260 - 12 = 248.
78	//
79	// The MSDN docs appear to say that a normal path that is 248 bytes long
80	// will work; empirically the path must be less then 248 bytes long.
81	pathLength := len(path)
82	if !filepathlite.IsAbs(path) {
83		// If the path is relative, we need to prepend the working directory
84		// plus a separator to the path before we can determine if it's too long.
85		// We don't want to call syscall.Getwd here, as that call is expensive to do
86		// every time fixLongPath is called with a relative path, so we use a cache.
87		// Note that getwdCache might be outdated if the working directory has been
88		// changed without using os.Chdir, i.e. using syscall.Chdir directly or cgo.
89		// This is fine, as the worst that can happen is that we fail to fix the path.
90		getwdCache.Lock()
91		if getwdCache.dir == "" {
92			// Init the working directory cache.
93			getwdCache.dir, _ = syscall.Getwd()
94		}
95		pathLength += len(getwdCache.dir) + 1
96		getwdCache.Unlock()
97	}
98
99	if pathLength < 248 {
100		// Don't fix. (This is how Go 1.7 and earlier worked,
101		// not automatically generating the \\?\ form)
102		return path
103	}
104
105	var isUNC, isDevice bool
106	if len(path) >= 2 && IsPathSeparator(path[0]) && IsPathSeparator(path[1]) {
107		if len(path) >= 4 && path[2] == '.' && IsPathSeparator(path[3]) {
108			// Starts with //./
109			isDevice = true
110		} else {
111			// Starts with //
112			isUNC = true
113		}
114	}
115	var prefix []uint16
116	if isUNC {
117		// UNC path, prepend the \\?\UNC\ prefix.
118		prefix = []uint16{'\\', '\\', '?', '\\', 'U', 'N', 'C', '\\'}
119	} else if isDevice {
120		// Don't add the extended prefix to device paths, as it would
121		// change its meaning.
122	} else {
123		prefix = []uint16{'\\', '\\', '?', '\\'}
124	}
125
126	p, err := syscall.UTF16FromString(path)
127	if err != nil {
128		return path
129	}
130	// Estimate the required buffer size using the path length plus the null terminator.
131	// pathLength includes the working directory. This should be accurate unless
132	// the working directory has changed without using os.Chdir.
133	n := uint32(pathLength) + 1
134	var buf []uint16
135	for {
136		buf = make([]uint16, n+uint32(len(prefix)))
137		n, err = syscall.GetFullPathName(&p[0], n, &buf[len(prefix)], nil)
138		if err != nil {
139			return path
140		}
141		if n <= uint32(len(buf)-len(prefix)) {
142			buf = buf[:n+uint32(len(prefix))]
143			break
144		}
145	}
146	if isUNC {
147		// Remove leading \\.
148		buf = buf[2:]
149	}
150	copy(buf, prefix)
151	return syscall.UTF16ToString(buf)
152}
153