github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/configs/configload/copy_dir.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package configload
     5  
     6  import (
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  )
    11  
    12  // copyDir copies the src directory contents into dst. Both directories
    13  // should already exist.
    14  func copyDir(dst, src string) error {
    15  	src, err := filepath.EvalSymlinks(src)
    16  	if err != nil {
    17  		return err
    18  	}
    19  
    20  	walkFn := func(path string, info os.FileInfo, err error) error {
    21  		if err != nil {
    22  			return err
    23  		}
    24  
    25  		if path == src {
    26  			return nil
    27  		}
    28  
    29  		// The "path" has the src prefixed to it. We need to join our
    30  		// destination with the path without the src on it.
    31  		dstPath := filepath.Join(dst, path[len(src):])
    32  
    33  		// we don't want to try and copy the same file over itself.
    34  		if eq, err := sameFile(path, dstPath); eq {
    35  			return nil
    36  		} else if err != nil {
    37  			return err
    38  		}
    39  
    40  		// If we have a directory, make that subdirectory, then continue
    41  		// the walk.
    42  		if info.IsDir() {
    43  			if path == filepath.Join(src, dst) {
    44  				// dst is in src; don't walk it.
    45  				return nil
    46  			}
    47  
    48  			if err := os.MkdirAll(dstPath, 0755); err != nil {
    49  				return err
    50  			}
    51  
    52  			return nil
    53  		}
    54  
    55  		// If the current path is a symlink, recreate the symlink relative to
    56  		// the dst directory
    57  		if info.Mode()&os.ModeSymlink == os.ModeSymlink {
    58  			target, err := os.Readlink(path)
    59  			if err != nil {
    60  				return err
    61  			}
    62  
    63  			return os.Symlink(target, dstPath)
    64  		}
    65  
    66  		// If we have a file, copy the contents.
    67  		srcF, err := os.Open(path)
    68  		if err != nil {
    69  			return err
    70  		}
    71  		defer srcF.Close()
    72  
    73  		dstF, err := os.Create(dstPath)
    74  		if err != nil {
    75  			return err
    76  		}
    77  		defer dstF.Close()
    78  
    79  		if _, err := io.Copy(dstF, srcF); err != nil {
    80  			return err
    81  		}
    82  
    83  		// Chmod it
    84  		return os.Chmod(dstPath, info.Mode())
    85  	}
    86  
    87  	return filepath.Walk(src, walkFn)
    88  }
    89  
    90  // sameFile tried to determine if to paths are the same file.
    91  // If the paths don't match, we lookup the inode on supported systems.
    92  func sameFile(a, b string) (bool, error) {
    93  	if a == b {
    94  		return true, nil
    95  	}
    96  
    97  	aIno, err := inode(a)
    98  	if err != nil {
    99  		if os.IsNotExist(err) {
   100  			return false, nil
   101  		}
   102  		return false, err
   103  	}
   104  
   105  	bIno, err := inode(b)
   106  	if err != nil {
   107  		if os.IsNotExist(err) {
   108  			return false, nil
   109  		}
   110  		return false, err
   111  	}
   112  
   113  	if aIno > 0 && aIno == bIno {
   114  		return true, nil
   115  	}
   116  
   117  	return false, nil
   118  }