kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/copy/copy_dir.go (about)

     1  package copy
     2  
     3  import (
     4  	"io"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  )
     9  
    10  // CopyDir recursively copies all of the files within the directory given in
    11  // src to the directory given in dst.
    12  //
    13  // Both directories should already exist. If the destination directory is
    14  // non-empty then the new files will merge in with the old, overwriting any
    15  // files that have a relative path in common between source and destination.
    16  //
    17  // Recursive copying of directories is inevitably a rather opinionated sort of
    18  // operation, so this function won't be appropriate for all use-cases. Some
    19  // of the "opinions" it has are described in the following paragraphs:
    20  //
    21  // Symlinks in the source directory are recreated with the same target in the
    22  // destination directory. If the symlink is to a directory itself, that
    23  // directory is not recursively visited for further copying.
    24  //
    25  // File and directory modes are not preserved exactly, but the executable
    26  // flag is preserved for files on operating systems where it is significant.
    27  //
    28  // Any "dot files" it encounters along the way are skipped, even on platforms
    29  // that do not normally ascribe special meaning to files with names starting
    30  // with dots.
    31  //
    32  // Callers may rely on the above details and other undocumented details of
    33  // this function, so if you intend to change it be sure to review the callers
    34  // first and make sure they are compatible with the change you intend to make.
    35  func CopyDir(dst, src string) error {
    36  	src, err := filepath.EvalSymlinks(src)
    37  	if err != nil {
    38  		return err
    39  	}
    40  
    41  	walkFn := func(path string, info os.FileInfo, err error) error {
    42  		if err != nil {
    43  			return err
    44  		}
    45  
    46  		if path == src {
    47  			return nil
    48  		}
    49  
    50  		if strings.HasPrefix(filepath.Base(path), ".") {
    51  			// Skip any dot files
    52  			if info.IsDir() {
    53  				return filepath.SkipDir
    54  			} else {
    55  				return nil
    56  			}
    57  		}
    58  
    59  		// The "path" has the src prefixed to it. We need to join our
    60  		// destination with the path without the src on it.
    61  		dstPath := filepath.Join(dst, path[len(src):])
    62  
    63  		// we don't want to try and copy the same file over itself.
    64  		if eq, err := SameFile(path, dstPath); eq {
    65  			return nil
    66  		} else if err != nil {
    67  			return err
    68  		}
    69  
    70  		// If we have a directory, make that subdirectory, then continue
    71  		// the walk.
    72  		if info.IsDir() {
    73  			if path == filepath.Join(src, dst) {
    74  				// dst is in src; don't walk it.
    75  				return nil
    76  			}
    77  
    78  			if err := os.MkdirAll(dstPath, 0755); err != nil {
    79  				return err
    80  			}
    81  
    82  			return nil
    83  		}
    84  
    85  		// If the current path is a symlink, recreate the symlink relative to
    86  		// the dst directory
    87  		if info.Mode()&os.ModeSymlink == os.ModeSymlink {
    88  			target, err := os.Readlink(path)
    89  			if err != nil {
    90  				return err
    91  			}
    92  
    93  			return os.Symlink(target, dstPath)
    94  		}
    95  
    96  		// If we have a file, copy the contents.
    97  		srcF, err := os.Open(path)
    98  		if err != nil {
    99  			return err
   100  		}
   101  		defer srcF.Close()
   102  
   103  		dstF, err := os.Create(dstPath)
   104  		if err != nil {
   105  			return err
   106  		}
   107  		defer dstF.Close()
   108  
   109  		if _, err := io.Copy(dstF, srcF); err != nil {
   110  			return err
   111  		}
   112  
   113  		// Chmod it
   114  		return os.Chmod(dstPath, info.Mode())
   115  	}
   116  
   117  	return filepath.Walk(src, walkFn)
   118  }
   119  
   120  // SameFile returns true if the two given paths refer to the same physical
   121  // file on disk, using the unique file identifiers from the underlying
   122  // operating system. For example, on Unix systems this checks whether the
   123  // two files are on the same device and have the same inode.
   124  func SameFile(a, b string) (bool, error) {
   125  	if a == b {
   126  		return true, nil
   127  	}
   128  
   129  	aInfo, err := os.Lstat(a)
   130  	if err != nil {
   131  		if os.IsNotExist(err) {
   132  			return false, nil
   133  		}
   134  		return false, err
   135  	}
   136  
   137  	bInfo, err := os.Lstat(b)
   138  	if err != nil {
   139  		if os.IsNotExist(err) {
   140  			return false, nil
   141  		}
   142  		return false, err
   143  	}
   144  
   145  	return os.SameFile(aInfo, bInfo), nil
   146  }