github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/symlink/fs_windows.go (about)

     1  package symlink // import "github.com/demonoid81/moby/pkg/symlink"
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"golang.org/x/sys/windows"
    11  )
    12  
    13  func toShort(path string) (string, error) {
    14  	p, err := windows.UTF16FromString(path)
    15  	if err != nil {
    16  		return "", err
    17  	}
    18  	b := p // GetShortPathName says we can reuse buffer
    19  	n, err := windows.GetShortPathName(&p[0], &b[0], uint32(len(b)))
    20  	if err != nil {
    21  		return "", err
    22  	}
    23  	if n > uint32(len(b)) {
    24  		b = make([]uint16, n)
    25  		if _, err = windows.GetShortPathName(&p[0], &b[0], uint32(len(b))); err != nil {
    26  			return "", err
    27  		}
    28  	}
    29  	return windows.UTF16ToString(b), nil
    30  }
    31  
    32  func toLong(path string) (string, error) {
    33  	p, err := windows.UTF16FromString(path)
    34  	if err != nil {
    35  		return "", err
    36  	}
    37  	b := p // GetLongPathName says we can reuse buffer
    38  	n, err := windows.GetLongPathName(&p[0], &b[0], uint32(len(b)))
    39  	if err != nil {
    40  		return "", err
    41  	}
    42  	if n > uint32(len(b)) {
    43  		b = make([]uint16, n)
    44  		n, err = windows.GetLongPathName(&p[0], &b[0], uint32(len(b)))
    45  		if err != nil {
    46  			return "", err
    47  		}
    48  	}
    49  	b = b[:n]
    50  	return windows.UTF16ToString(b), nil
    51  }
    52  
    53  func evalSymlinks(path string) (string, error) {
    54  	path, err := walkSymlinks(path)
    55  	if err != nil {
    56  		return "", err
    57  	}
    58  
    59  	p, err := toShort(path)
    60  	if err != nil {
    61  		return "", err
    62  	}
    63  	p, err = toLong(p)
    64  	if err != nil {
    65  		return "", err
    66  	}
    67  	// windows.GetLongPathName does not change the case of the drive letter,
    68  	// but the result of EvalSymlinks must be unique, so we have
    69  	// EvalSymlinks(`c:\a`) == EvalSymlinks(`C:\a`).
    70  	// Make drive letter upper case.
    71  	if len(p) >= 2 && p[1] == ':' && 'a' <= p[0] && p[0] <= 'z' {
    72  		p = string(p[0]+'A'-'a') + p[1:]
    73  	} else if len(p) >= 6 && p[5] == ':' && 'a' <= p[4] && p[4] <= 'z' {
    74  		p = p[:3] + string(p[4]+'A'-'a') + p[5:]
    75  	}
    76  	return filepath.Clean(p), nil
    77  }
    78  
    79  const (
    80  	utf8RuneSelf   = 0x80
    81  	longPathPrefix = `\\?\`
    82  )
    83  
    84  func walkSymlinks(path string) (string, error) {
    85  	const maxIter = 255
    86  	originalPath := path
    87  	// consume path by taking each frontmost path element,
    88  	// expanding it if it's a symlink, and appending it to b
    89  	var b bytes.Buffer
    90  	for n := 0; path != ""; n++ {
    91  		if n > maxIter {
    92  			return "", errors.New("EvalSymlinks: too many links in " + originalPath)
    93  		}
    94  
    95  		// A path beginning with `\\?\` represents the root, so automatically
    96  		// skip that part and begin processing the next segment.
    97  		if strings.HasPrefix(path, longPathPrefix) {
    98  			b.WriteString(longPathPrefix)
    99  			path = path[4:]
   100  			continue
   101  		}
   102  
   103  		// find next path component, p
   104  		var i = -1
   105  		for j, c := range path {
   106  			if c < utf8RuneSelf && os.IsPathSeparator(uint8(c)) {
   107  				i = j
   108  				break
   109  			}
   110  		}
   111  		var p string
   112  		if i == -1 {
   113  			p, path = path, ""
   114  		} else {
   115  			p, path = path[:i], path[i+1:]
   116  		}
   117  
   118  		if p == "" {
   119  			if b.Len() == 0 {
   120  				// must be absolute path
   121  				b.WriteRune(filepath.Separator)
   122  			}
   123  			continue
   124  		}
   125  
   126  		// If this is the first segment after the long path prefix, accept the
   127  		// current segment as a volume root or UNC share and move on to the next.
   128  		if b.String() == longPathPrefix {
   129  			b.WriteString(p)
   130  			b.WriteRune(filepath.Separator)
   131  			continue
   132  		}
   133  
   134  		fi, err := os.Lstat(b.String() + p)
   135  		if err != nil {
   136  			return "", err
   137  		}
   138  		if fi.Mode()&os.ModeSymlink == 0 {
   139  			b.WriteString(p)
   140  			if path != "" || (b.Len() == 2 && len(p) == 2 && p[1] == ':') {
   141  				b.WriteRune(filepath.Separator)
   142  			}
   143  			continue
   144  		}
   145  
   146  		// it's a symlink, put it at the front of path
   147  		dest, err := os.Readlink(b.String() + p)
   148  		if err != nil {
   149  			return "", err
   150  		}
   151  		if isAbs(dest) {
   152  			b.Reset()
   153  		}
   154  		path = dest + string(filepath.Separator) + path
   155  	}
   156  	return filepath.Clean(b.String()), nil
   157  }
   158  
   159  func isDriveOrRoot(p string) bool {
   160  	if p == string(filepath.Separator) {
   161  		return true
   162  	}
   163  
   164  	length := len(p)
   165  	if length >= 2 {
   166  		if p[length-1] == ':' && (('a' <= p[length-2] && p[length-2] <= 'z') || ('A' <= p[length-2] && p[length-2] <= 'Z')) {
   167  			return true
   168  		}
   169  	}
   170  	return false
   171  }
   172  
   173  // isAbs is a platform-specific wrapper for filepath.IsAbs. On Windows,
   174  // golang filepath.IsAbs does not consider a path \windows\system32 as absolute
   175  // as it doesn't start with a drive-letter/colon combination. However, in
   176  // docker we need to verify things such as WORKDIR /windows/system32 in
   177  // a Dockerfile (which gets translated to \windows\system32 when being processed
   178  // by the daemon. This SHOULD be treated as absolute from a docker processing
   179  // perspective.
   180  func isAbs(path string) bool {
   181  	if filepath.IsAbs(path) || strings.HasPrefix(path, string(os.PathSeparator)) {
   182  		return true
   183  	}
   184  	return false
   185  }