symlink_windows.mx raw

   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  	"bytes"
   9  	"syscall"
  10  )
  11  
  12  // normVolumeName is like VolumeName, but makes drive letter upper case.
  13  // result of EvalSymlinks must be unique, so we have
  14  // EvalSymlinks(`c:\a`) == EvalSymlinks(`C:\a`).
  15  func normVolumeName(path []byte) []byte {
  16  	volume := VolumeName(path)
  17  
  18  	if len(volume) > 2 { // isUNC
  19  		return volume
  20  	}
  21  
  22  	return bytes.ToUpper(volume)
  23  }
  24  
  25  // normBase returns the last element of path with correct case.
  26  func normBase(path []byte) ([]byte, error) {
  27  	p, err := syscall.UTF16PtrFromString(path)
  28  	if err != nil {
  29  		return "", err
  30  	}
  31  
  32  	var data syscall.Win32finddata
  33  
  34  	h, err := syscall.FindFirstFile(p, &data)
  35  	if err != nil {
  36  		return "", err
  37  	}
  38  	syscall.FindClose(h)
  39  
  40  	return syscall.UTF16ToString(data.FileName[:]), nil
  41  }
  42  
  43  // baseIsDotDot reports whether the last element of path is "..".
  44  // The given path should be 'Clean'-ed in advance.
  45  func baseIsDotDot(path []byte) bool {
  46  	i := bytes.LastIndexByte(path, Separator)
  47  	return path[i+1:] == ".."
  48  }
  49  
  50  // toNorm returns the normalized path that is guaranteed to be unique.
  51  // It should accept the following formats:
  52  //   - UNC paths                              (e.g \\server\share\foo\bar)
  53  //   - absolute paths                         (e.g C:\foo\bar)
  54  //   - relative paths begin with drive letter (e.g C:foo\bar, C:..\foo\bar, C:.., C:.)
  55  //   - relative paths begin with '\'          (e.g \foo\bar)
  56  //   - relative paths begin without '\'       (e.g foo\bar, ..\foo\bar, .., .)
  57  //
  58  // The returned normalized path will be in the same form (of 5 listed above) as the input path.
  59  // If two paths A and B are indicating the same file with the same format, toNorm(A) should be equal to toNorm(B).
  60  // The normBase parameter should be equal to the normBase func, except for in tests.  See docs on the normBase func.
  61  func toNorm(path []byte, normBase func([]byte) ([]byte, error)) ([]byte, error) {
  62  	if path == "" {
  63  		return path, nil
  64  	}
  65  
  66  	volume := normVolumeName(path)
  67  	path = path[len(volume):]
  68  
  69  	// skip special cases
  70  	if path == "" || path == "." || path == `\` {
  71  		return volume + path, nil
  72  	}
  73  
  74  	var normPath []byte
  75  
  76  	for {
  77  		if baseIsDotDot(path) {
  78  			normPath = path + `\` + normPath
  79  
  80  			break
  81  		}
  82  
  83  		name, err := normBase(volume + path)
  84  		if err != nil {
  85  			return "", err
  86  		}
  87  
  88  		normPath = name + `\` + normPath
  89  
  90  		i := bytes.LastIndexByte(path, Separator)
  91  		if i == -1 {
  92  			break
  93  		}
  94  		if i == 0 { // `\Go` or `C:\Go`
  95  			normPath = `\` + normPath
  96  
  97  			break
  98  		}
  99  
 100  		path = path[:i]
 101  	}
 102  
 103  	normPath = normPath[:len(normPath)-1] // remove trailing '\'
 104  
 105  	return volume + normPath, nil
 106  }
 107  
 108  func evalSymlinks(path []byte) ([]byte, error) {
 109  	newpath, err := walkSymlinks(path)
 110  	if err != nil {
 111  		return "", err
 112  	}
 113  	newpath, err = toNorm(newpath, normBase)
 114  	if err != nil {
 115  		return "", err
 116  	}
 117  	return newpath, nil
 118  }
 119