github.com/golang/dep@v0.5.4/internal/fs/fs.go (about)

     1  // Copyright 2016 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 fs
     6  
     7  import (
     8  	"io"
     9  	"io/ioutil"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strings"
    14  	"syscall"
    15  	"unicode"
    16  
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  // HasFilepathPrefix will determine if "path" starts with "prefix" from
    21  // the point of view of a filesystem.
    22  //
    23  // Unlike filepath.HasPrefix, this function is path-aware, meaning that
    24  // it knows that two directories /foo and /foobar are not the same
    25  // thing, and therefore HasFilepathPrefix("/foobar", "/foo") will return
    26  // false.
    27  //
    28  // This function also handles the case where the involved filesystems
    29  // are case-insensitive, meaning /foo/bar and /Foo/Bar correspond to the
    30  // same file. In that situation HasFilepathPrefix("/Foo/Bar", "/foo")
    31  // will return true. The implementation is *not* OS-specific, so a FAT32
    32  // filesystem mounted on Linux will be handled correctly.
    33  func HasFilepathPrefix(path, prefix string) (bool, error) {
    34  	// this function is more convoluted then ideal due to need for special
    35  	// handling of volume name/drive letter on Windows. vnPath and vnPrefix
    36  	// are first compared, and then used to initialize initial values of p and
    37  	// d which will be appended to for incremental checks using
    38  	// IsCaseSensitiveFilesystem and then equality.
    39  
    40  	// no need to check IsCaseSensitiveFilesystem because VolumeName return
    41  	// empty string on all non-Windows machines
    42  	vnPath := strings.ToLower(filepath.VolumeName(path))
    43  	vnPrefix := strings.ToLower(filepath.VolumeName(prefix))
    44  	if vnPath != vnPrefix {
    45  		return false, nil
    46  	}
    47  
    48  	// Because filepath.Join("c:","dir") returns "c:dir", we have to manually
    49  	// add path separator to drive letters. Also, we need to set the path root
    50  	// on *nix systems, since filepath.Join("", "dir") returns a relative path.
    51  	vnPath += string(os.PathSeparator)
    52  	vnPrefix += string(os.PathSeparator)
    53  
    54  	var dn string
    55  
    56  	if isDir, err := IsDir(path); err != nil {
    57  		return false, errors.Wrap(err, "failed to check filepath prefix")
    58  	} else if isDir {
    59  		dn = path
    60  	} else {
    61  		dn = filepath.Dir(path)
    62  	}
    63  
    64  	dn = filepath.Clean(dn)
    65  	prefix = filepath.Clean(prefix)
    66  
    67  	// [1:] in the lines below eliminates empty string on *nix and volume name on Windows
    68  	dirs := strings.Split(dn, string(os.PathSeparator))[1:]
    69  	prefixes := strings.Split(prefix, string(os.PathSeparator))[1:]
    70  
    71  	if len(prefixes) > len(dirs) {
    72  		return false, nil
    73  	}
    74  
    75  	// d,p are initialized with "/" on *nix and volume name on Windows
    76  	d := vnPath
    77  	p := vnPrefix
    78  
    79  	for i := range prefixes {
    80  		// need to test each component of the path for
    81  		// case-sensitiveness because on Unix we could have
    82  		// something like ext4 filesystem mounted on FAT
    83  		// mountpoint, mounted on ext4 filesystem, i.e. the
    84  		// problematic filesystem is not the last one.
    85  		caseSensitive, err := IsCaseSensitiveFilesystem(filepath.Join(d, dirs[i]))
    86  		if err != nil {
    87  			return false, errors.Wrap(err, "failed to check filepath prefix")
    88  		}
    89  		if caseSensitive {
    90  			d = filepath.Join(d, dirs[i])
    91  			p = filepath.Join(p, prefixes[i])
    92  		} else {
    93  			d = filepath.Join(d, strings.ToLower(dirs[i]))
    94  			p = filepath.Join(p, strings.ToLower(prefixes[i]))
    95  		}
    96  
    97  		if p != d {
    98  			return false, nil
    99  		}
   100  	}
   101  
   102  	return true, nil
   103  }
   104  
   105  // EquivalentPaths compares the paths passed to check if they are equivalent.
   106  // It respects the case-sensitivity of the underlying filesysyems.
   107  func EquivalentPaths(p1, p2 string) (bool, error) {
   108  	p1 = filepath.Clean(p1)
   109  	p2 = filepath.Clean(p2)
   110  
   111  	fi1, err := os.Stat(p1)
   112  	if err != nil {
   113  		return false, errors.Wrapf(err, "could not check for path equivalence")
   114  	}
   115  	fi2, err := os.Stat(p2)
   116  	if err != nil {
   117  		return false, errors.Wrapf(err, "could not check for path equivalence")
   118  	}
   119  
   120  	p1Filename, p2Filename := "", ""
   121  
   122  	if !fi1.IsDir() {
   123  		p1, p1Filename = filepath.Split(p1)
   124  	}
   125  	if !fi2.IsDir() {
   126  		p2, p2Filename = filepath.Split(p2)
   127  	}
   128  
   129  	if isPrefix1, err := HasFilepathPrefix(p1, p2); err != nil {
   130  		return false, errors.Wrap(err, "failed to check for path equivalence")
   131  	} else if isPrefix2, err := HasFilepathPrefix(p2, p1); err != nil {
   132  		return false, errors.Wrap(err, "failed to check for path equivalence")
   133  	} else if !isPrefix1 || !isPrefix2 {
   134  		return false, nil
   135  	}
   136  
   137  	if p1Filename != "" || p2Filename != "" {
   138  		caseSensitive, err := IsCaseSensitiveFilesystem(filepath.Join(p1, p1Filename))
   139  		if err != nil {
   140  			return false, errors.Wrap(err, "could not check for filesystem case-sensitivity")
   141  		}
   142  		if caseSensitive {
   143  			if p1Filename != p2Filename {
   144  				return false, nil
   145  			}
   146  		} else {
   147  			if !strings.EqualFold(p1Filename, p2Filename) {
   148  				return false, nil
   149  			}
   150  		}
   151  	}
   152  
   153  	return true, nil
   154  }
   155  
   156  // RenameWithFallback attempts to rename a file or directory, but falls back to
   157  // copying in the event of a cross-device link error. If the fallback copy
   158  // succeeds, src is still removed, emulating normal rename behavior.
   159  func RenameWithFallback(src, dst string) error {
   160  	_, err := os.Stat(src)
   161  	if err != nil {
   162  		return errors.Wrapf(err, "cannot stat %s", src)
   163  	}
   164  
   165  	err = os.Rename(src, dst)
   166  	if err == nil {
   167  		return nil
   168  	}
   169  
   170  	return renameFallback(err, src, dst)
   171  }
   172  
   173  // renameByCopy attempts to rename a file or directory by copying it to the
   174  // destination and then removing the src thus emulating the rename behavior.
   175  func renameByCopy(src, dst string) error {
   176  	var cerr error
   177  	if dir, _ := IsDir(src); dir {
   178  		cerr = CopyDir(src, dst)
   179  		if cerr != nil {
   180  			cerr = errors.Wrap(cerr, "copying directory failed")
   181  		}
   182  	} else {
   183  		cerr = copyFile(src, dst)
   184  		if cerr != nil {
   185  			cerr = errors.Wrap(cerr, "copying file failed")
   186  		}
   187  	}
   188  
   189  	if cerr != nil {
   190  		return errors.Wrapf(cerr, "rename fallback failed: cannot rename %s to %s", src, dst)
   191  	}
   192  
   193  	return errors.Wrapf(os.RemoveAll(src), "cannot delete %s", src)
   194  }
   195  
   196  // IsCaseSensitiveFilesystem determines if the filesystem where dir
   197  // exists is case sensitive or not.
   198  //
   199  // CAVEAT: this function works by taking the last component of the given
   200  // path and flipping the case of the first letter for which case
   201  // flipping is a reversible operation (/foo/Bar → /foo/bar), then
   202  // testing for the existence of the new filename. There are two
   203  // possibilities:
   204  //
   205  // 1. The alternate filename does not exist. We can conclude that the
   206  // filesystem is case sensitive.
   207  //
   208  // 2. The filename happens to exist. We have to test if the two files
   209  // are the same file (case insensitive file system) or different ones
   210  // (case sensitive filesystem).
   211  //
   212  // If the input directory is such that the last component is composed
   213  // exclusively of case-less codepoints (e.g.  numbers), this function will
   214  // return false.
   215  func IsCaseSensitiveFilesystem(dir string) (bool, error) {
   216  	alt := filepath.Join(filepath.Dir(dir), genTestFilename(filepath.Base(dir)))
   217  
   218  	dInfo, err := os.Stat(dir)
   219  	if err != nil {
   220  		return false, errors.Wrap(err, "could not determine the case-sensitivity of the filesystem")
   221  	}
   222  
   223  	aInfo, err := os.Stat(alt)
   224  	if err != nil {
   225  		// If the file doesn't exists, assume we are on a case-sensitive filesystem.
   226  		if os.IsNotExist(err) {
   227  			return true, nil
   228  		}
   229  
   230  		return false, errors.Wrap(err, "could not determine the case-sensitivity of the filesystem")
   231  	}
   232  
   233  	return !os.SameFile(dInfo, aInfo), nil
   234  }
   235  
   236  // genTestFilename returns a string with at most one rune case-flipped.
   237  //
   238  // The transformation is applied only to the first rune that can be
   239  // reversibly case-flipped, meaning:
   240  //
   241  // * A lowercase rune for which it's true that lower(upper(r)) == r
   242  // * An uppercase rune for which it's true that upper(lower(r)) == r
   243  //
   244  // All the other runes are left intact.
   245  func genTestFilename(str string) string {
   246  	flip := true
   247  	return strings.Map(func(r rune) rune {
   248  		if flip {
   249  			if unicode.IsLower(r) {
   250  				u := unicode.ToUpper(r)
   251  				if unicode.ToLower(u) == r {
   252  					r = u
   253  					flip = false
   254  				}
   255  			} else if unicode.IsUpper(r) {
   256  				l := unicode.ToLower(r)
   257  				if unicode.ToUpper(l) == r {
   258  					r = l
   259  					flip = false
   260  				}
   261  			}
   262  		}
   263  		return r
   264  	}, str)
   265  }
   266  
   267  var errPathNotDir = errors.New("given path is not a directory")
   268  
   269  // ReadActualFilenames is used to determine the actual file names in given directory.
   270  //
   271  // On case sensitive file systems like ext4, it will check if those files exist using
   272  // `os.Stat` and return a map with key and value as filenames which exist in the folder.
   273  //
   274  // Otherwise, it reads the contents of the directory and returns a map which has the
   275  // given file name as the key and actual filename as the value(if it was found).
   276  func ReadActualFilenames(dirPath string, names []string) (map[string]string, error) {
   277  	actualFilenames := make(map[string]string, len(names))
   278  	if len(names) == 0 {
   279  		// This isn't expected to happen for current usage. Adding edge case handling,
   280  		// as it may be useful in future.
   281  		return actualFilenames, nil
   282  	}
   283  	// First, check that the given path is valid and it is a directory
   284  	dirStat, err := os.Stat(dirPath)
   285  	if err != nil {
   286  		return nil, errors.Wrap(err, "failed to read actual filenames")
   287  	}
   288  
   289  	if !dirStat.IsDir() {
   290  		return nil, errPathNotDir
   291  	}
   292  
   293  	// Ideally, we would use `os.Stat` for getting the actual file names but that returns
   294  	// the name we passed in as an argument and not the actual filename. So we are forced
   295  	// to list the directory contents and check against that. Since this check is costly,
   296  	// we do it only if absolutely necessary.
   297  	caseSensitive, err := IsCaseSensitiveFilesystem(dirPath)
   298  	if err != nil {
   299  		return nil, errors.Wrap(err, "failed to read actual filenames")
   300  	}
   301  	if caseSensitive {
   302  		// There will be no difference between actual filename and given filename. So
   303  		// just check if those files exist.
   304  		for _, name := range names {
   305  			_, err := os.Stat(filepath.Join(dirPath, name))
   306  			if err == nil {
   307  				actualFilenames[name] = name
   308  			} else if !os.IsNotExist(err) {
   309  				// Some unexpected err, wrap and return it.
   310  				return nil, errors.Wrap(err, "failed to read actual filenames")
   311  			}
   312  		}
   313  		return actualFilenames, nil
   314  	}
   315  
   316  	dir, err := os.Open(dirPath)
   317  	if err != nil {
   318  		return nil, errors.Wrap(err, "failed to read actual filenames")
   319  	}
   320  	defer dir.Close()
   321  
   322  	// Pass -1 to read all filenames in directory
   323  	filenames, err := dir.Readdirnames(-1)
   324  	if err != nil {
   325  		return nil, errors.Wrap(err, "failed to read actual filenames")
   326  	}
   327  
   328  	// namesMap holds the mapping from lowercase name to search name. Using this, we can
   329  	// avoid repeatedly looping through names.
   330  	namesMap := make(map[string]string, len(names))
   331  	for _, name := range names {
   332  		namesMap[strings.ToLower(name)] = name
   333  	}
   334  
   335  	for _, filename := range filenames {
   336  		searchName, ok := namesMap[strings.ToLower(filename)]
   337  		if ok {
   338  			// We are interested in this file, case insensitive match successful.
   339  			actualFilenames[searchName] = filename
   340  			if len(actualFilenames) == len(names) {
   341  				// We found all that we were looking for.
   342  				return actualFilenames, nil
   343  			}
   344  		}
   345  	}
   346  	return actualFilenames, nil
   347  }
   348  
   349  var (
   350  	errSrcNotDir = errors.New("source is not a directory")
   351  	errDstExist  = errors.New("destination already exists")
   352  )
   353  
   354  // CopyDir recursively copies a directory tree, attempting to preserve permissions.
   355  // Source directory must exist, destination directory must *not* exist.
   356  func CopyDir(src, dst string) error {
   357  	src = filepath.Clean(src)
   358  	dst = filepath.Clean(dst)
   359  
   360  	// We use os.Lstat() here to ensure we don't fall in a loop where a symlink
   361  	// actually links to a one of its parent directories.
   362  	fi, err := os.Lstat(src)
   363  	if err != nil {
   364  		return err
   365  	}
   366  	if !fi.IsDir() {
   367  		return errSrcNotDir
   368  	}
   369  
   370  	_, err = os.Stat(dst)
   371  	if err != nil && !os.IsNotExist(err) {
   372  		return err
   373  	}
   374  	if err == nil {
   375  		return errDstExist
   376  	}
   377  
   378  	if err = os.MkdirAll(dst, fi.Mode()); err != nil {
   379  		return errors.Wrapf(err, "cannot mkdir %s", dst)
   380  	}
   381  
   382  	entries, err := ioutil.ReadDir(src)
   383  	if err != nil {
   384  		return errors.Wrapf(err, "cannot read directory %s", dst)
   385  	}
   386  
   387  	for _, entry := range entries {
   388  		srcPath := filepath.Join(src, entry.Name())
   389  		dstPath := filepath.Join(dst, entry.Name())
   390  
   391  		if entry.IsDir() {
   392  			if err = CopyDir(srcPath, dstPath); err != nil {
   393  				return errors.Wrap(err, "copying directory failed")
   394  			}
   395  		} else {
   396  			// This will include symlinks, which is what we want when
   397  			// copying things.
   398  			if err = copyFile(srcPath, dstPath); err != nil {
   399  				return errors.Wrap(err, "copying file failed")
   400  			}
   401  		}
   402  	}
   403  
   404  	return nil
   405  }
   406  
   407  // copyFile copies the contents of the file named src to the file named
   408  // by dst. The file will be created if it does not already exist. If the
   409  // destination file exists, all its contents will be replaced by the contents
   410  // of the source file. The file mode will be copied from the source.
   411  func copyFile(src, dst string) (err error) {
   412  	if sym, err := IsSymlink(src); err != nil {
   413  		return errors.Wrap(err, "symlink check failed")
   414  	} else if sym {
   415  		if err := cloneSymlink(src, dst); err != nil {
   416  			if runtime.GOOS == "windows" {
   417  				// If cloning the symlink fails on Windows because the user
   418  				// does not have the required privileges, ignore the error and
   419  				// fall back to copying the file contents.
   420  				//
   421  				// ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522):
   422  				// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx
   423  				if lerr, ok := err.(*os.LinkError); ok && lerr.Err != syscall.Errno(1314) {
   424  					return err
   425  				}
   426  			} else {
   427  				return err
   428  			}
   429  		} else {
   430  			return nil
   431  		}
   432  	}
   433  
   434  	in, err := os.Open(src)
   435  	if err != nil {
   436  		return
   437  	}
   438  	defer in.Close()
   439  
   440  	out, err := os.Create(dst)
   441  	if err != nil {
   442  		return
   443  	}
   444  
   445  	if _, err = io.Copy(out, in); err != nil {
   446  		out.Close()
   447  		return
   448  	}
   449  
   450  	// Check for write errors on Close
   451  	if err = out.Close(); err != nil {
   452  		return
   453  	}
   454  
   455  	si, err := os.Stat(src)
   456  	if err != nil {
   457  		return
   458  	}
   459  
   460  	// Temporary fix for Go < 1.9
   461  	//
   462  	// See: https://github.com/golang/dep/issues/774
   463  	// and https://github.com/golang/go/issues/20829
   464  	if runtime.GOOS == "windows" {
   465  		dst = fixLongPath(dst)
   466  	}
   467  	err = os.Chmod(dst, si.Mode())
   468  
   469  	return
   470  }
   471  
   472  // cloneSymlink will create a new symlink that points to the resolved path of sl.
   473  // If sl is a relative symlink, dst will also be a relative symlink.
   474  func cloneSymlink(sl, dst string) error {
   475  	resolved, err := os.Readlink(sl)
   476  	if err != nil {
   477  		return err
   478  	}
   479  
   480  	return os.Symlink(resolved, dst)
   481  }
   482  
   483  // EnsureDir tries to ensure that a directory is present at the given path. It first
   484  // checks if the directory already exists at the given path. If there isn't one, it tries
   485  // to create it with the given permissions. However, it does not try to create the
   486  // directory recursively.
   487  func EnsureDir(path string, perm os.FileMode) error {
   488  	_, err := IsDir(path)
   489  
   490  	if os.IsNotExist(err) {
   491  		err = os.Mkdir(path, perm)
   492  		if err != nil {
   493  			return errors.Wrapf(err, "failed to ensure directory at %q", path)
   494  		}
   495  	}
   496  
   497  	return err
   498  }
   499  
   500  // IsDir determines is the path given is a directory or not.
   501  func IsDir(name string) (bool, error) {
   502  	fi, err := os.Stat(name)
   503  	if err != nil {
   504  		return false, err
   505  	}
   506  	if !fi.IsDir() {
   507  		return false, errors.Errorf("%q is not a directory", name)
   508  	}
   509  	return true, nil
   510  }
   511  
   512  // IsNonEmptyDir determines if the path given is a non-empty directory or not.
   513  func IsNonEmptyDir(name string) (bool, error) {
   514  	isDir, err := IsDir(name)
   515  	if err != nil && !os.IsNotExist(err) {
   516  		return false, err
   517  	} else if !isDir {
   518  		return false, nil
   519  	}
   520  
   521  	// Get file descriptor
   522  	f, err := os.Open(name)
   523  	if err != nil {
   524  		return false, err
   525  	}
   526  	defer f.Close()
   527  
   528  	// Query only 1 child. EOF if no children.
   529  	_, err = f.Readdirnames(1)
   530  	switch err {
   531  	case io.EOF:
   532  		return false, nil
   533  	case nil:
   534  		return true, nil
   535  	default:
   536  		return false, err
   537  	}
   538  }
   539  
   540  // IsRegular determines if the path given is a regular file or not.
   541  func IsRegular(name string) (bool, error) {
   542  	fi, err := os.Stat(name)
   543  	if os.IsNotExist(err) {
   544  		return false, nil
   545  	}
   546  	if err != nil {
   547  		return false, err
   548  	}
   549  	mode := fi.Mode()
   550  	if mode&os.ModeType != 0 {
   551  		return false, errors.Errorf("%q is a %v, expected a file", name, mode)
   552  	}
   553  	return true, nil
   554  }
   555  
   556  // IsSymlink determines if the given path is a symbolic link.
   557  func IsSymlink(path string) (bool, error) {
   558  	l, err := os.Lstat(path)
   559  	if err != nil {
   560  		return false, err
   561  	}
   562  
   563  	return l.Mode()&os.ModeSymlink == os.ModeSymlink, nil
   564  }
   565  
   566  // fixLongPath returns the extended-length (\\?\-prefixed) form of
   567  // path when needed, in order to avoid the default 260 character file
   568  // path limit imposed by Windows. If path is not easily converted to
   569  // the extended-length form (for example, if path is a relative path
   570  // or contains .. elements), or is short enough, fixLongPath returns
   571  // path unmodified.
   572  //
   573  // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
   574  func fixLongPath(path string) string {
   575  	// Do nothing (and don't allocate) if the path is "short".
   576  	// Empirically (at least on the Windows Server 2013 builder),
   577  	// the kernel is arbitrarily okay with < 248 bytes. That
   578  	// matches what the docs above say:
   579  	// "When using an API to create a directory, the specified
   580  	// path cannot be so long that you cannot append an 8.3 file
   581  	// name (that is, the directory name cannot exceed MAX_PATH
   582  	// minus 12)." Since MAX_PATH is 260, 260 - 12 = 248.
   583  	//
   584  	// The MSDN docs appear to say that a normal path that is 248 bytes long
   585  	// will work; empirically the path must be less then 248 bytes long.
   586  	if len(path) < 248 {
   587  		// Don't fix. (This is how Go 1.7 and earlier worked,
   588  		// not automatically generating the \\?\ form)
   589  		return path
   590  	}
   591  
   592  	// The extended form begins with \\?\, as in
   593  	// \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt.
   594  	// The extended form disables evaluation of . and .. path
   595  	// elements and disables the interpretation of / as equivalent
   596  	// to \. The conversion here rewrites / to \ and elides
   597  	// . elements as well as trailing or duplicate separators. For
   598  	// simplicity it avoids the conversion entirely for relative
   599  	// paths or paths containing .. elements. For now,
   600  	// \\server\share paths are not converted to
   601  	// \\?\UNC\server\share paths because the rules for doing so
   602  	// are less well-specified.
   603  	if len(path) >= 2 && path[:2] == `\\` {
   604  		// Don't canonicalize UNC paths.
   605  		return path
   606  	}
   607  	if !isAbs(path) {
   608  		// Relative path
   609  		return path
   610  	}
   611  
   612  	const prefix = `\\?`
   613  
   614  	pathbuf := make([]byte, len(prefix)+len(path)+len(`\`))
   615  	copy(pathbuf, prefix)
   616  	n := len(path)
   617  	r, w := 0, len(prefix)
   618  	for r < n {
   619  		switch {
   620  		case os.IsPathSeparator(path[r]):
   621  			// empty block
   622  			r++
   623  		case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
   624  			// /./
   625  			r++
   626  		case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
   627  			// /../ is currently unhandled
   628  			return path
   629  		default:
   630  			pathbuf[w] = '\\'
   631  			w++
   632  			for ; r < n && !os.IsPathSeparator(path[r]); r++ {
   633  				pathbuf[w] = path[r]
   634  				w++
   635  			}
   636  		}
   637  	}
   638  	// A drive's root directory needs a trailing \
   639  	if w == len(`\\?\c:`) {
   640  		pathbuf[w] = '\\'
   641  		w++
   642  	}
   643  	return string(pathbuf[:w])
   644  }
   645  
   646  func isAbs(path string) (b bool) {
   647  	v := volumeName(path)
   648  	if v == "" {
   649  		return false
   650  	}
   651  	path = path[len(v):]
   652  	if path == "" {
   653  		return false
   654  	}
   655  	return os.IsPathSeparator(path[0])
   656  }
   657  
   658  func volumeName(path string) (v string) {
   659  	if len(path) < 2 {
   660  		return ""
   661  	}
   662  	// with drive letter
   663  	c := path[0]
   664  	if path[1] == ':' &&
   665  		('0' <= c && c <= '9' || 'a' <= c && c <= 'z' ||
   666  			'A' <= c && c <= 'Z') {
   667  		return path[:2]
   668  	}
   669  	// is it UNC
   670  	if l := len(path); l >= 5 && os.IsPathSeparator(path[0]) && os.IsPathSeparator(path[1]) &&
   671  		!os.IsPathSeparator(path[2]) && path[2] != '.' {
   672  		// first, leading `\\` and next shouldn't be `\`. its server name.
   673  		for n := 3; n < l-1; n++ {
   674  			// second, next '\' shouldn't be repeated.
   675  			if os.IsPathSeparator(path[n]) {
   676  				n++
   677  				// third, following something characters. its share name.
   678  				if !os.IsPathSeparator(path[n]) {
   679  					if path[n] == '.' {
   680  						break
   681  					}
   682  					for ; n < l; n++ {
   683  						if os.IsPathSeparator(path[n]) {
   684  							break
   685  						}
   686  					}
   687  					return path[:n]
   688  				}
   689  				break
   690  			}
   691  		}
   692  	}
   693  	return ""
   694  }