github.com/shiroyuki/docker@v1.9.0/pkg/symlink/fs_windows.go (about)

     1  package symlink
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"syscall"
    10  
    11  	"github.com/docker/docker/pkg/longpath"
    12  )
    13  
    14  func toShort(path string) (string, error) {
    15  	p, err := syscall.UTF16FromString(path)
    16  	if err != nil {
    17  		return "", err
    18  	}
    19  	b := p // GetShortPathName says we can reuse buffer
    20  	n, err := syscall.GetShortPathName(&p[0], &b[0], uint32(len(b)))
    21  	if err != nil {
    22  		return "", err
    23  	}
    24  	if n > uint32(len(b)) {
    25  		b = make([]uint16, n)
    26  		n, err = syscall.GetShortPathName(&p[0], &b[0], uint32(len(b)))
    27  		if err != nil {
    28  			return "", err
    29  		}
    30  	}
    31  	return syscall.UTF16ToString(b), nil
    32  }
    33  
    34  func toLong(path string) (string, error) {
    35  	p, err := syscall.UTF16FromString(path)
    36  	if err != nil {
    37  		return "", err
    38  	}
    39  	b := p // GetLongPathName says we can reuse buffer
    40  	n, err := syscall.GetLongPathName(&p[0], &b[0], uint32(len(b)))
    41  	if err != nil {
    42  		return "", err
    43  	}
    44  	if n > uint32(len(b)) {
    45  		b = make([]uint16, n)
    46  		n, err = syscall.GetLongPathName(&p[0], &b[0], uint32(len(b)))
    47  		if err != nil {
    48  			return "", err
    49  		}
    50  	}
    51  	b = b[:n]
    52  	return syscall.UTF16ToString(b), nil
    53  }
    54  
    55  func evalSymlinks(path string) (string, error) {
    56  	path, err := walkSymlinks(path)
    57  	if err != nil {
    58  		return "", err
    59  	}
    60  
    61  	p, err := toShort(path)
    62  	if err != nil {
    63  		return "", err
    64  	}
    65  	p, err = toLong(p)
    66  	if err != nil {
    67  		return "", err
    68  	}
    69  	// syscall.GetLongPathName does not change the case of the drive letter,
    70  	// but the result of EvalSymlinks must be unique, so we have
    71  	// EvalSymlinks(`c:\a`) == EvalSymlinks(`C:\a`).
    72  	// Make drive letter upper case.
    73  	if len(p) >= 2 && p[1] == ':' && 'a' <= p[0] && p[0] <= 'z' {
    74  		p = string(p[0]+'A'-'a') + p[1:]
    75  	} else if len(p) >= 6 && p[5] == ':' && 'a' <= p[4] && p[4] <= 'z' {
    76  		p = p[:3] + string(p[4]+'A'-'a') + p[5:]
    77  	}
    78  	return filepath.Clean(p), nil
    79  }
    80  
    81  const utf8RuneSelf = 0x80
    82  
    83  func walkSymlinks(path string) (string, error) {
    84  	const maxIter = 255
    85  	originalPath := path
    86  	// consume path by taking each frontmost path element,
    87  	// expanding it if it's a symlink, and appending it to b
    88  	var b bytes.Buffer
    89  	for n := 0; path != ""; n++ {
    90  		if n > maxIter {
    91  			return "", errors.New("EvalSymlinks: too many links in " + originalPath)
    92  		}
    93  
    94  		// A path beginnging with `\\?\` represents the root, so automatically
    95  		// skip that part and begin processing the next segment.
    96  		if strings.HasPrefix(path, longpath.Prefix) {
    97  			b.WriteString(longpath.Prefix)
    98  			path = path[4:]
    99  			continue
   100  		}
   101  
   102  		// find next path component, p
   103  		var i = -1
   104  		for j, c := range path {
   105  			if c < utf8RuneSelf && os.IsPathSeparator(uint8(c)) {
   106  				i = j
   107  				break
   108  			}
   109  		}
   110  		var p string
   111  		if i == -1 {
   112  			p, path = path, ""
   113  		} else {
   114  			p, path = path[:i], path[i+1:]
   115  		}
   116  
   117  		if p == "" {
   118  			if b.Len() == 0 {
   119  				// must be absolute path
   120  				b.WriteRune(filepath.Separator)
   121  			}
   122  			continue
   123  		}
   124  
   125  		// If this is the first segment after the long path prefix, accept the
   126  		// current segment as a volume root or UNC share and move on to the next.
   127  		if b.String() == longpath.Prefix {
   128  			b.WriteString(p)
   129  			b.WriteRune(filepath.Separator)
   130  			continue
   131  		}
   132  
   133  		fi, err := os.Lstat(b.String() + p)
   134  		if err != nil {
   135  			return "", err
   136  		}
   137  		if fi.Mode()&os.ModeSymlink == 0 {
   138  			b.WriteString(p)
   139  			if path != "" || (b.Len() == 2 && len(p) == 2 && p[1] == ':') {
   140  				b.WriteRune(filepath.Separator)
   141  			}
   142  			continue
   143  		}
   144  
   145  		// it's a symlink, put it at the front of path
   146  		dest, err := os.Readlink(b.String() + p)
   147  		if err != nil {
   148  			return "", err
   149  		}
   150  		if filepath.IsAbs(dest) || os.IsPathSeparator(dest[0]) {
   151  			b.Reset()
   152  		}
   153  		path = dest + string(filepath.Separator) + path
   154  	}
   155  	return filepath.Clean(b.String()), nil
   156  }