github.com/kdevb0x/go@v0.0.0-20180115030120-39687051e9e7/src/path/filepath/symlink_windows.go (about)

     1  // Copyright 2012 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  
     5  package filepath
     6  
     7  import (
     8  	"errors"
     9  	"internal/syscall/windows"
    10  	"os"
    11  	"strings"
    12  	"syscall"
    13  )
    14  
    15  // normVolumeName is like VolumeName, but makes drive letter upper case.
    16  // result of EvalSymlinks must be unique, so we have
    17  // EvalSymlinks(`c:\a`) == EvalSymlinks(`C:\a`).
    18  func normVolumeName(path string) string {
    19  	volume := VolumeName(path)
    20  
    21  	if len(volume) > 2 { // isUNC
    22  		return volume
    23  	}
    24  
    25  	return strings.ToUpper(volume)
    26  }
    27  
    28  // normBase returns the last element of path with correct case.
    29  func normBase(path string) (string, error) {
    30  	p, err := syscall.UTF16PtrFromString(path)
    31  	if err != nil {
    32  		return "", err
    33  	}
    34  
    35  	var data syscall.Win32finddata
    36  
    37  	h, err := syscall.FindFirstFile(p, &data)
    38  	if err != nil {
    39  		return "", err
    40  	}
    41  	syscall.FindClose(h)
    42  
    43  	return syscall.UTF16ToString(data.FileName[:]), nil
    44  }
    45  
    46  // baseIsDotDot returns whether the last element of path is "..".
    47  // The given path should be 'Clean'-ed in advance.
    48  func baseIsDotDot(path string) bool {
    49  	i := strings.LastIndexByte(path, Separator)
    50  	return path[i+1:] == ".."
    51  }
    52  
    53  // toNorm returns the normalized path that is guaranteed to be unique.
    54  // It should accept the following formats:
    55  //   * UNC paths                              (e.g \\server\share\foo\bar)
    56  //   * absolute paths                         (e.g C:\foo\bar)
    57  //   * relative paths begin with drive letter (e.g C:foo\bar, C:..\foo\bar, C:.., C:.)
    58  //   * relative paths begin with '\'          (e.g \foo\bar)
    59  //   * relative paths begin without '\'       (e.g foo\bar, ..\foo\bar, .., .)
    60  // The returned normalized path will be in the same form (of 5 listed above) as the input path.
    61  // If two paths A and B are indicating the same file with the same format, toNorm(A) should be equal to toNorm(B).
    62  // The normBase parameter should be equal to the normBase func, except for in tests.  See docs on the normBase func.
    63  func toNorm(path string, normBase func(string) (string, error)) (string, error) {
    64  	if path == "" {
    65  		return path, nil
    66  	}
    67  
    68  	path = Clean(path)
    69  
    70  	volume := normVolumeName(path)
    71  	path = path[len(volume):]
    72  
    73  	// skip special cases
    74  	if path == "." || path == `\` {
    75  		return volume + path, nil
    76  	}
    77  
    78  	var normPath string
    79  
    80  	for {
    81  		if baseIsDotDot(path) {
    82  			normPath = path + `\` + normPath
    83  
    84  			break
    85  		}
    86  
    87  		name, err := normBase(volume + path)
    88  		if err != nil {
    89  			return "", err
    90  		}
    91  
    92  		normPath = name + `\` + normPath
    93  
    94  		i := strings.LastIndexByte(path, Separator)
    95  		if i == -1 {
    96  			break
    97  		}
    98  		if i == 0 { // `\Go` or `C:\Go`
    99  			normPath = `\` + normPath
   100  
   101  			break
   102  		}
   103  
   104  		path = path[:i]
   105  	}
   106  
   107  	normPath = normPath[:len(normPath)-1] // remove trailing '\'
   108  
   109  	return volume + normPath, nil
   110  }
   111  
   112  // evalSymlinksUsingGetFinalPathNameByHandle uses Windows
   113  // GetFinalPathNameByHandle API to retrieve the final
   114  // path for the specified file.
   115  func evalSymlinksUsingGetFinalPathNameByHandle(path string) (string, error) {
   116  	err := windows.LoadGetFinalPathNameByHandle()
   117  	if err != nil {
   118  		// we must be using old version of Windows
   119  		return "", err
   120  	}
   121  
   122  	if path == "" {
   123  		return path, nil
   124  	}
   125  
   126  	// Use Windows I/O manager to dereference the symbolic link, as per
   127  	// https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/
   128  	p, err := syscall.UTF16PtrFromString(path)
   129  	if err != nil {
   130  		return "", err
   131  	}
   132  	h, err := syscall.CreateFile(p, 0, 0, nil,
   133  		syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
   134  	if err != nil {
   135  		return "", err
   136  	}
   137  	defer syscall.CloseHandle(h)
   138  
   139  	buf := make([]uint16, 100)
   140  	for {
   141  		n, err := windows.GetFinalPathNameByHandle(h, &buf[0], uint32(len(buf)), windows.VOLUME_NAME_DOS)
   142  		if err != nil {
   143  			return "", err
   144  		}
   145  		if n < uint32(len(buf)) {
   146  			break
   147  		}
   148  		buf = make([]uint16, n)
   149  	}
   150  	s := syscall.UTF16ToString(buf)
   151  	if len(s) > 4 && s[:4] == `\\?\` {
   152  		s = s[4:]
   153  		if len(s) > 3 && s[:3] == `UNC` {
   154  			// return path like \\server\share\...
   155  			return `\` + s[3:], nil
   156  		}
   157  		return s, nil
   158  	}
   159  	return "", errors.New("GetFinalPathNameByHandle returned unexpected path=" + s)
   160  }
   161  
   162  func samefile(path1, path2 string) bool {
   163  	fi1, err := os.Lstat(path1)
   164  	if err != nil {
   165  		return false
   166  	}
   167  	fi2, err := os.Lstat(path2)
   168  	if err != nil {
   169  		return false
   170  	}
   171  	return os.SameFile(fi1, fi2)
   172  }
   173  
   174  func evalSymlinks(path string) (string, error) {
   175  	newpath, err := walkSymlinks(path)
   176  	if err != nil {
   177  		newpath2, err2 := evalSymlinksUsingGetFinalPathNameByHandle(path)
   178  		if err2 == nil {
   179  			return toNorm(newpath2, normBase)
   180  		}
   181  		return "", err
   182  	}
   183  	newpath, err = toNorm(newpath, normBase)
   184  	if err != nil {
   185  		newpath2, err2 := evalSymlinksUsingGetFinalPathNameByHandle(path)
   186  		if err2 == nil {
   187  			return toNorm(newpath2, normBase)
   188  		}
   189  		return "", err
   190  	}
   191  	if strings.ToUpper(newpath) == strings.ToUpper(path) {
   192  		// walkSymlinks did not actually walk any symlinks,
   193  		// so we don't need to try GetFinalPathNameByHandle.
   194  		return newpath, nil
   195  	}
   196  	newpath2, err2 := evalSymlinksUsingGetFinalPathNameByHandle(path)
   197  	if err2 != nil {
   198  		return newpath, nil
   199  	}
   200  	newpath2, err2 = toNorm(newpath2, normBase)
   201  	if err2 != nil {
   202  		return newpath, nil
   203  	}
   204  	if samefile(newpath, newpath2) {
   205  		return newpath, nil
   206  	}
   207  	return newpath2, nil
   208  }