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