Windows: Fix long path handling for docker build
Signed-off-by: Stefan J. Wernli <swernli@microsoft.com>
This commit is contained in:
		
							parent
							
								
									5bebf3cbac
								
							
						
					
					
						commit
						fe637416e9
					
				
					 9 changed files with 214 additions and 10 deletions
				
			
		|  | @ -8,15 +8,14 @@ import ( | |||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/docker/docker/pkg/longpath" | ||||
| ) | ||||
| 
 | ||||
| // fixVolumePathPrefix does platform specific processing to ensure that if | ||||
| // the path being passed in is not in a volume path format, convert it to one. | ||||
| func fixVolumePathPrefix(srcPath string) string { | ||||
| 	if !strings.HasPrefix(srcPath, `\\?\`) { | ||||
| 		srcPath = `\\?\` + srcPath | ||||
| 	} | ||||
| 	return srcPath | ||||
| 	return longpath.AddPrefix(srcPath) | ||||
| } | ||||
| 
 | ||||
| // getWalkRoot calculates the root path when performing a TarWithOptions. | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import ( | |||
| 	"io" | ||||
| 
 | ||||
| 	"github.com/docker/docker/pkg/archive" | ||||
| 	"github.com/docker/docker/pkg/longpath" | ||||
| ) | ||||
| 
 | ||||
| // chroot is not supported by Windows | ||||
|  | @ -17,5 +18,5 @@ func invokeUnpack(decompressedArchive io.ReadCloser, | |||
| 	// Windows is different to Linux here because Windows does not support | ||||
| 	// chroot. Hence there is no point sandboxing a chrooted process to | ||||
| 	// do the unpack. We call inline instead within the daemon process. | ||||
| 	return archive.Unpack(decompressedArchive, dest, options) | ||||
| 	return archive.Unpack(decompressedArchive, longpath.AddPrefix(dest), options) | ||||
| } | ||||
|  |  | |||
|  | @ -5,9 +5,9 @@ import ( | |||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/docker/docker/pkg/archive" | ||||
| 	"github.com/docker/docker/pkg/longpath" | ||||
| ) | ||||
| 
 | ||||
| // applyLayerHandler parses a diff in the standard layer format from `layer`, and | ||||
|  | @ -17,9 +17,7 @@ func applyLayerHandler(dest string, layer archive.Reader, decompress bool) (size | |||
| 	dest = filepath.Clean(dest) | ||||
| 
 | ||||
| 	// Ensure it is a Windows-style volume path | ||||
| 	if !strings.HasPrefix(dest, `\\?\`) { | ||||
| 		dest = `\\?\` + dest | ||||
| 	} | ||||
| 	dest = longpath.AddPrefix(dest) | ||||
| 
 | ||||
| 	if decompress { | ||||
| 		decompressed, err := archive.DecompressStream(layer) | ||||
|  |  | |||
|  | @ -5,10 +5,18 @@ package directory | |||
| import ( | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/docker/docker/pkg/longpath" | ||||
| ) | ||||
| 
 | ||||
| // Size walks a directory tree and returns its total size in bytes. | ||||
| func Size(dir string) (size int64, err error) { | ||||
| 	fixedPath, err := filepath.Abs(dir) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	fixedPath = longpath.AddPrefix(fixedPath) | ||||
| 	err = filepath.Walk(dir, func(d string, fileInfo os.FileInfo, e error) error { | ||||
| 		// Ignore directory sizes | ||||
| 		if fileInfo == nil { | ||||
|  |  | |||
							
								
								
									
										21
									
								
								longpath/longpath.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								longpath/longpath.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| // longpath introduces some constants and helper functions for handling long paths | ||||
| // in Windows, which are expected to be prepended with `\\?\` and followed by either | ||||
| // a drive letter, a UNC server\share, or a volume identifier. | ||||
| 
 | ||||
| package longpath | ||||
| 
 | ||||
| import ( | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // Prefix is the longpath prefix for Windows file paths. | ||||
| const Prefix = `\\?\` | ||||
| 
 | ||||
| // AddPrefix will add the Windows long path prefix to the path provided if | ||||
| // it does not already have it. | ||||
| func AddPrefix(path string) string { | ||||
| 	if !strings.HasPrefix(path, Prefix) { | ||||
| 		path = Prefix + path | ||||
| 	} | ||||
| 	return path | ||||
| } | ||||
|  | @ -1,4 +1,5 @@ | |||
| Package symlink implements EvalSymlinksInScope which is an extension of filepath.EvalSymlinks | ||||
| Package symlink implements EvalSymlinksInScope which is an extension of filepath.EvalSymlinks, | ||||
| as well as a Windows long-path aware version of filepath.EvalSymlinks | ||||
| from the [Go standard library](https://golang.org/pkg/path/filepath). | ||||
| 
 | ||||
| The code from filepath.EvalSymlinks has been adapted in fs.go. | ||||
|  |  | |||
|  | @ -132,3 +132,12 @@ func evalSymlinksInScope(path, root string) (string, error) { | |||
| 	// what's happening here | ||||
| 	return filepath.Clean(root + filepath.Clean(string(filepath.Separator)+b.String())), nil | ||||
| } | ||||
| 
 | ||||
| // EvalSymlinks returns the path name after the evaluation of any symbolic | ||||
| // links. | ||||
| // If path is relative the result will be relative to the current directory, | ||||
| // unless one of the components is an absolute symbolic link. | ||||
| // This version has been updated to support long paths prepended with `\\?\`. | ||||
| func EvalSymlinks(path string) (string, error) { | ||||
| 	return evalSymlinks(path) | ||||
| } | ||||
|  |  | |||
							
								
								
									
										11
									
								
								symlink/fs_unix.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								symlink/fs_unix.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| // +build !windows | ||||
| 
 | ||||
| package symlink | ||||
| 
 | ||||
| import ( | ||||
| 	"path/filepath" | ||||
| ) | ||||
| 
 | ||||
| func evalSymlinks(path string) (string, error) { | ||||
| 	return filepath.EvalSymlinks(path) | ||||
| } | ||||
							
								
								
									
										156
									
								
								symlink/fs_windows.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								symlink/fs_windows.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,156 @@ | |||
| package symlink | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"github.com/docker/docker/pkg/longpath" | ||||
| ) | ||||
| 
 | ||||
| func toShort(path string) (string, error) { | ||||
| 	p, err := syscall.UTF16FromString(path) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	b := p // GetShortPathName says we can reuse buffer | ||||
| 	n, err := syscall.GetShortPathName(&p[0], &b[0], uint32(len(b))) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	if n > uint32(len(b)) { | ||||
| 		b = make([]uint16, n) | ||||
| 		n, err = syscall.GetShortPathName(&p[0], &b[0], uint32(len(b))) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 	} | ||||
| 	return syscall.UTF16ToString(b), nil | ||||
| } | ||||
| 
 | ||||
| func toLong(path string) (string, error) { | ||||
| 	p, err := syscall.UTF16FromString(path) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	b := p // GetLongPathName says we can reuse buffer | ||||
| 	n, err := syscall.GetLongPathName(&p[0], &b[0], uint32(len(b))) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	if n > uint32(len(b)) { | ||||
| 		b = make([]uint16, n) | ||||
| 		n, err = syscall.GetLongPathName(&p[0], &b[0], uint32(len(b))) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 	} | ||||
| 	b = b[:n] | ||||
| 	return syscall.UTF16ToString(b), nil | ||||
| } | ||||
| 
 | ||||
| func evalSymlinks(path string) (string, error) { | ||||
| 	path, err := walkSymlinks(path) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 
 | ||||
| 	p, err := toShort(path) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	p, err = toLong(p) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	// syscall.GetLongPathName does not change the case of the drive letter, | ||||
| 	// but the result of EvalSymlinks must be unique, so we have | ||||
| 	// EvalSymlinks(`c:\a`) == EvalSymlinks(`C:\a`). | ||||
| 	// Make drive letter upper case. | ||||
| 	if len(p) >= 2 && p[1] == ':' && 'a' <= p[0] && p[0] <= 'z' { | ||||
| 		p = string(p[0]+'A'-'a') + p[1:] | ||||
| 	} else if len(p) >= 6 && p[5] == ':' && 'a' <= p[4] && p[4] <= 'z' { | ||||
| 		p = p[:3] + string(p[4]+'A'-'a') + p[5:] | ||||
| 	} | ||||
| 	return filepath.Clean(p), nil | ||||
| } | ||||
| 
 | ||||
| const utf8RuneSelf = 0x80 | ||||
| 
 | ||||
| func walkSymlinks(path string) (string, error) { | ||||
| 	const maxIter = 255 | ||||
| 	originalPath := path | ||||
| 	// consume path by taking each frontmost path element, | ||||
| 	// expanding it if it's a symlink, and appending it to b | ||||
| 	var b bytes.Buffer | ||||
| 	for n := 0; path != ""; n++ { | ||||
| 		if n > maxIter { | ||||
| 			return "", errors.New("EvalSymlinks: too many links in " + originalPath) | ||||
| 		} | ||||
| 
 | ||||
| 		// A path beginnging with `\\?\` represents the root, so automatically | ||||
| 		// skip that part and begin processing the next segment. | ||||
| 		if strings.HasPrefix(path, longpath.Prefix) { | ||||
| 			b.WriteString(longpath.Prefix) | ||||
| 			path = path[4:] | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// find next path component, p | ||||
| 		var i = -1 | ||||
| 		for j, c := range path { | ||||
| 			if c < utf8RuneSelf && os.IsPathSeparator(uint8(c)) { | ||||
| 				i = j | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		var p string | ||||
| 		if i == -1 { | ||||
| 			p, path = path, "" | ||||
| 		} else { | ||||
| 			p, path = path[:i], path[i+1:] | ||||
| 		} | ||||
| 
 | ||||
| 		if p == "" { | ||||
| 			if b.Len() == 0 { | ||||
| 				// must be absolute path | ||||
| 				b.WriteRune(filepath.Separator) | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// If this is the first segment after the long path prefix, accept the | ||||
| 		// current segment as a volume root or UNC share and move on to the next. | ||||
| 		if b.String() == longpath.Prefix { | ||||
| 			b.WriteString(p) | ||||
| 			b.WriteRune(filepath.Separator) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		fi, err := os.Lstat(b.String() + p) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		if fi.Mode()&os.ModeSymlink == 0 { | ||||
| 			b.WriteString(p) | ||||
| 			if path != "" || (b.Len() == 2 && len(p) == 2 && p[1] == ':') { | ||||
| 				b.WriteRune(filepath.Separator) | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// it's a symlink, put it at the front of path | ||||
| 		dest, err := os.Readlink(b.String() + p) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		if filepath.IsAbs(dest) || os.IsPathSeparator(dest[0]) { | ||||
| 			b.Reset() | ||||
| 		} | ||||
| 		path = dest + string(filepath.Separator) + path | ||||
| 	} | ||||
| 	return filepath.Clean(b.String()), nil | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue