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