github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/internal/third_party/dep/fs/fs.go (about)

     1  /*
     2  Copyright (c) for portions of fs.go are held by The Go Authors, 2016 and are provided under
     3  the BSD license.
     4  
     5  Redistribution and use in source and binary forms, with or without
     6  modification, are permitted provided that the following conditions are
     7  met:
     8  
     9     * Redistributions of source code must retain the above copyright
    10  notice, this list of conditions and the following disclaimer.
    11     * Redistributions in binary form must reproduce the above
    12  copyright notice, this list of conditions and the following disclaimer
    13  in the documentation and/or other materials provided with the
    14  distribution.
    15     * Neither the name of Google Inc. nor the names of its
    16  contributors may be used to endorse or promote products derived from
    17  this software without specific prior written permission.
    18  
    19  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    20  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    21  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    22  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    23  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    24  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    25  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    26  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    27  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    28  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    29  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    30  */
    31  
    32  package fs
    33  
    34  import (
    35  	"io"
    36  	"os"
    37  	"path/filepath"
    38  	"runtime"
    39  	"syscall"
    40  
    41  	"github.com/pkg/errors"
    42  )
    43  
    44  // fs contains a copy of a few functions from dep tool code to avoid a dependency on golang/dep.
    45  // This code is copied from https://github.com/golang/dep/blob/37d6c560cdf407be7b6cd035b23dba89df9275cf/internal/fs/fs.go
    46  // No changes to the code were made other than removing some unused functions
    47  
    48  // RenameWithFallback attempts to rename a file or directory, but falls back to
    49  // copying in the event of a cross-device link error. If the fallback copy
    50  // succeeds, src is still removed, emulating normal rename behavior.
    51  func RenameWithFallback(src, dst string) error {
    52  	_, err := os.Stat(src)
    53  	if err != nil {
    54  		return errors.Wrapf(err, "cannot stat %s", src)
    55  	}
    56  
    57  	err = os.Rename(src, dst)
    58  	if err == nil {
    59  		return nil
    60  	}
    61  
    62  	return renameFallback(err, src, dst)
    63  }
    64  
    65  // renameByCopy attempts to rename a file or directory by copying it to the
    66  // destination and then removing the src thus emulating the rename behavior.
    67  func renameByCopy(src, dst string) error {
    68  	var cerr error
    69  	if dir, _ := IsDir(src); dir {
    70  		cerr = CopyDir(src, dst)
    71  		if cerr != nil {
    72  			cerr = errors.Wrap(cerr, "copying directory failed")
    73  		}
    74  	} else {
    75  		cerr = copyFile(src, dst)
    76  		if cerr != nil {
    77  			cerr = errors.Wrap(cerr, "copying file failed")
    78  		}
    79  	}
    80  
    81  	if cerr != nil {
    82  		return errors.Wrapf(cerr, "rename fallback failed: cannot rename %s to %s", src, dst)
    83  	}
    84  
    85  	return errors.Wrapf(os.RemoveAll(src), "cannot delete %s", src)
    86  }
    87  
    88  var (
    89  	errSrcNotDir = errors.New("source is not a directory")
    90  	errDstExist  = errors.New("destination already exists")
    91  )
    92  
    93  // CopyDir recursively copies a directory tree, attempting to preserve permissions.
    94  // Source directory must exist, destination directory must *not* exist.
    95  func CopyDir(src, dst string) error {
    96  	src = filepath.Clean(src)
    97  	dst = filepath.Clean(dst)
    98  
    99  	// We use os.Lstat() here to ensure we don't fall in a loop where a symlink
   100  	// actually links to a one of its parent directories.
   101  	fi, err := os.Lstat(src)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	if !fi.IsDir() {
   106  		return errSrcNotDir
   107  	}
   108  
   109  	_, err = os.Stat(dst)
   110  	if err != nil && !os.IsNotExist(err) {
   111  		return err
   112  	}
   113  	if err == nil {
   114  		return errDstExist
   115  	}
   116  
   117  	if err = os.MkdirAll(dst, fi.Mode()); err != nil {
   118  		return errors.Wrapf(err, "cannot mkdir %s", dst)
   119  	}
   120  
   121  	entries, err := os.ReadDir(src)
   122  	if err != nil {
   123  		return errors.Wrapf(err, "cannot read directory %s", dst)
   124  	}
   125  
   126  	for _, entry := range entries {
   127  		srcPath := filepath.Join(src, entry.Name())
   128  		dstPath := filepath.Join(dst, entry.Name())
   129  
   130  		if entry.IsDir() {
   131  			if err = CopyDir(srcPath, dstPath); err != nil {
   132  				return errors.Wrap(err, "copying directory failed")
   133  			}
   134  		} else {
   135  			// This will include symlinks, which is what we want when
   136  			// copying things.
   137  			if err = copyFile(srcPath, dstPath); err != nil {
   138  				return errors.Wrap(err, "copying file failed")
   139  			}
   140  		}
   141  	}
   142  
   143  	return nil
   144  }
   145  
   146  // copyFile copies the contents of the file named src to the file named
   147  // by dst. The file will be created if it does not already exist. If the
   148  // destination file exists, all its contents will be replaced by the contents
   149  // of the source file. The file mode will be copied from the source.
   150  func copyFile(src, dst string) (err error) {
   151  	if sym, err := IsSymlink(src); err != nil {
   152  		return errors.Wrap(err, "symlink check failed")
   153  	} else if sym {
   154  		if err := cloneSymlink(src, dst); err != nil {
   155  			if runtime.GOOS == "windows" {
   156  				// If cloning the symlink fails on Windows because the user
   157  				// does not have the required privileges, ignore the error and
   158  				// fall back to copying the file contents.
   159  				//
   160  				// ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522):
   161  				// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx
   162  				if lerr, ok := err.(*os.LinkError); ok && lerr.Err != syscall.Errno(1314) {
   163  					return err
   164  				}
   165  			} else {
   166  				return err
   167  			}
   168  		} else {
   169  			return nil
   170  		}
   171  	}
   172  
   173  	in, err := os.Open(src)
   174  	if err != nil {
   175  		return
   176  	}
   177  	defer in.Close()
   178  
   179  	out, err := os.Create(dst)
   180  	if err != nil {
   181  		return
   182  	}
   183  
   184  	if _, err = io.Copy(out, in); err != nil {
   185  		out.Close()
   186  		return
   187  	}
   188  
   189  	// Check for write errors on Close
   190  	if err = out.Close(); err != nil {
   191  		return
   192  	}
   193  
   194  	si, err := os.Stat(src)
   195  	if err != nil {
   196  		return
   197  	}
   198  
   199  	// Temporary fix for Go < 1.9
   200  	//
   201  	// See: https://github.com/golang/dep/issues/774
   202  	// and https://github.com/golang/go/issues/20829
   203  	if runtime.GOOS == "windows" {
   204  		dst = fixLongPath(dst)
   205  	}
   206  	err = os.Chmod(dst, si.Mode())
   207  
   208  	return
   209  }
   210  
   211  // cloneSymlink will create a new symlink that points to the resolved path of sl.
   212  // If sl is a relative symlink, dst will also be a relative symlink.
   213  func cloneSymlink(sl, dst string) error {
   214  	resolved, err := os.Readlink(sl)
   215  	if err != nil {
   216  		return err
   217  	}
   218  
   219  	return os.Symlink(resolved, dst)
   220  }
   221  
   222  // IsDir determines is the path given is a directory or not.
   223  func IsDir(name string) (bool, error) {
   224  	fi, err := os.Stat(name)
   225  	if err != nil {
   226  		return false, err
   227  	}
   228  	if !fi.IsDir() {
   229  		return false, errors.Errorf("%q is not a directory", name)
   230  	}
   231  	return true, nil
   232  }
   233  
   234  // IsSymlink determines if the given path is a symbolic link.
   235  func IsSymlink(path string) (bool, error) {
   236  	l, err := os.Lstat(path)
   237  	if err != nil {
   238  		return false, err
   239  	}
   240  
   241  	return l.Mode()&os.ModeSymlink == os.ModeSymlink, nil
   242  }
   243  
   244  // fixLongPath returns the extended-length (\\?\-prefixed) form of
   245  // path when needed, in order to avoid the default 260 character file
   246  // path limit imposed by Windows. If path is not easily converted to
   247  // the extended-length form (for example, if path is a relative path
   248  // or contains .. elements), or is short enough, fixLongPath returns
   249  // path unmodified.
   250  //
   251  // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
   252  func fixLongPath(path string) string {
   253  	// Do nothing (and don't allocate) if the path is "short".
   254  	// Empirically (at least on the Windows Server 2013 builder),
   255  	// the kernel is arbitrarily okay with < 248 bytes. That
   256  	// matches what the docs above say:
   257  	// "When using an API to create a directory, the specified
   258  	// path cannot be so long that you cannot append an 8.3 file
   259  	// name (that is, the directory name cannot exceed MAX_PATH
   260  	// minus 12)." Since MAX_PATH is 260, 260 - 12 = 248.
   261  	//
   262  	// The MSDN docs appear to say that a normal path that is 248 bytes long
   263  	// will work; empirically the path must be less then 248 bytes long.
   264  	if len(path) < 248 {
   265  		// Don't fix. (This is how Go 1.7 and earlier worked,
   266  		// not automatically generating the \\?\ form)
   267  		return path
   268  	}
   269  
   270  	// The extended form begins with \\?\, as in
   271  	// \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt.
   272  	// The extended form disables evaluation of . and .. path
   273  	// elements and disables the interpretation of / as equivalent
   274  	// to \. The conversion here rewrites / to \ and elides
   275  	// . elements as well as trailing or duplicate separators. For
   276  	// simplicity it avoids the conversion entirely for relative
   277  	// paths or paths containing .. elements. For now,
   278  	// \\server\share paths are not converted to
   279  	// \\?\UNC\server\share paths because the rules for doing so
   280  	// are less well-specified.
   281  	if len(path) >= 2 && path[:2] == `\\` {
   282  		// Don't canonicalize UNC paths.
   283  		return path
   284  	}
   285  	if !isAbs(path) {
   286  		// Relative path
   287  		return path
   288  	}
   289  
   290  	const prefix = `\\?`
   291  
   292  	pathbuf := make([]byte, len(prefix)+len(path)+len(`\`))
   293  	copy(pathbuf, prefix)
   294  	n := len(path)
   295  	r, w := 0, len(prefix)
   296  	for r < n {
   297  		switch {
   298  		case os.IsPathSeparator(path[r]):
   299  			// empty block
   300  			r++
   301  		case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
   302  			// /./
   303  			r++
   304  		case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
   305  			// /../ is currently unhandled
   306  			return path
   307  		default:
   308  			pathbuf[w] = '\\'
   309  			w++
   310  			for ; r < n && !os.IsPathSeparator(path[r]); r++ {
   311  				pathbuf[w] = path[r]
   312  				w++
   313  			}
   314  		}
   315  	}
   316  	// A drive's root directory needs a trailing \
   317  	if w == len(`\\?\c:`) {
   318  		pathbuf[w] = '\\'
   319  		w++
   320  	}
   321  	return string(pathbuf[:w])
   322  }
   323  
   324  func isAbs(path string) (b bool) {
   325  	v := volumeName(path)
   326  	if v == "" {
   327  		return false
   328  	}
   329  	path = path[len(v):]
   330  	if path == "" {
   331  		return false
   332  	}
   333  	return os.IsPathSeparator(path[0])
   334  }
   335  
   336  func volumeName(path string) (v string) {
   337  	if len(path) < 2 {
   338  		return ""
   339  	}
   340  	// with drive letter
   341  	c := path[0]
   342  	if path[1] == ':' &&
   343  		('0' <= c && c <= '9' || 'a' <= c && c <= 'z' ||
   344  			'A' <= c && c <= 'Z') {
   345  		return path[:2]
   346  	}
   347  	// is it UNC
   348  	if l := len(path); l >= 5 && os.IsPathSeparator(path[0]) && os.IsPathSeparator(path[1]) &&
   349  		!os.IsPathSeparator(path[2]) && path[2] != '.' {
   350  		// first, leading `\\` and next shouldn't be `\`. its server name.
   351  		for n := 3; n < l-1; n++ {
   352  			// second, next '\' shouldn't be repeated.
   353  			if os.IsPathSeparator(path[n]) {
   354  				n++
   355  				// third, following something characters. its share name.
   356  				if !os.IsPathSeparator(path[n]) {
   357  					if path[n] == '.' {
   358  						break
   359  					}
   360  					for ; n < l; n++ {
   361  						if os.IsPathSeparator(path[n]) {
   362  							break
   363  						}
   364  					}
   365  					return path[:n]
   366  				}
   367  				break
   368  			}
   369  		}
   370  	}
   371  	return ""
   372  }