| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package copy | |
| import ( | |
| "io" | |
| "os" | |
| "path/filepath" | |
| "strings" | |
| ) | |
| // CopyDir recursively copies all of the files within the directory given in | |
| // src to the directory given in dst. | |
| // | |
| // Both directories should already exist. If the destination directory is | |
| // non-empty then the new files will merge in with the old, overwriting any | |
| // files that have a relative path in common between source and destination. | |
| // | |
| // Recursive copying of directories is inevitably a rather opinionated sort of | |
| // operation, so this function won't be appropriate for all use-cases. Some | |
| // of the "opinions" it has are described in the following paragraphs: | |
| // | |
| // Symlinks in the source directory are recreated with the same target in the | |
| // destination directory. If the symlink is to a directory itself, that | |
| // directory is not recursively visited for further copying. | |
| // | |
| // File and directory modes are not preserved exactly, but the executable | |
| // flag is preserved for files on operating systems where it is significant. | |
| // | |
| // Any "dot files" it encounters along the way are skipped, even on platforms | |
| // that do not normally ascribe special meaning to files with names starting | |
| // with dots. | |
| // | |
| // Callers may rely on the above details and other undocumented details of | |
| // this function, so if you intend to change it be sure to review the callers | |
| // first and make sure they are compatible with the change you intend to make. | |
| func CopyDir(dst, src string) error { | |
| src, err := filepath.EvalSymlinks(src) | |
| if err != nil { | |
| return err | |
| } | |
| walkFn := func(path string, info os.FileInfo, err error) error { | |
| if err != nil { | |
| return err | |
| } | |
| if path == src { | |
| return nil | |
| } | |
| if strings.HasPrefix(filepath.Base(path), ".") { | |
| // Skip any dot files | |
| if info.IsDir() { | |
| return filepath.SkipDir | |
| } else { | |
| return nil | |
| } | |
| } | |
| // The "path" has the src prefixed to it. We need to join our | |
| // destination with the path without the src on it. | |
| dstPath := filepath.Join(dst, path[len(src):]) | |
| // we don't want to try and copy the same file over itself. | |
| if eq, err := SameFile(path, dstPath); eq { | |
| return nil | |
| } else if err != nil { | |
| return err | |
| } | |
| // If we have a directory, make that subdirectory, then continue | |
| // the walk. | |
| if info.IsDir() { | |
| if path == filepath.Join(src, dst) { | |
| // dst is in src; don't walk it. | |
| return nil | |
| } | |
| if err := os.MkdirAll(dstPath, 0755); err != nil { | |
| return err | |
| } | |
| return nil | |
| } | |
| // If the current path is a symlink, recreate the symlink relative to | |
| // the dst directory | |
| if info.Mode()&os.ModeSymlink == os.ModeSymlink { | |
| target, err := os.Readlink(path) | |
| if err != nil { | |
| return err | |
| } | |
| return os.Symlink(target, dstPath) | |
| } | |
| // If we have a file, copy the contents. | |
| srcF, err := os.Open(path) | |
| if err != nil { | |
| return err | |
| } | |
| defer srcF.Close() | |
| dstF, err := os.Create(dstPath) | |
| if err != nil { | |
| return err | |
| } | |
| defer dstF.Close() | |
| if _, err := io.Copy(dstF, srcF); err != nil { | |
| return err | |
| } | |
| // Chmod it | |
| return os.Chmod(dstPath, info.Mode()) | |
| } | |
| return filepath.Walk(src, walkFn) | |
| } | |
| // SameFile returns true if the two given paths refer to the same physical | |
| // file on disk, using the unique file identifiers from the underlying | |
| // operating system. For example, on Unix systems this checks whether the | |
| // two files are on the same device and have the same inode. | |
| func SameFile(a, b string) (bool, error) { | |
| if a == b { | |
| return true, nil | |
| } | |
| aInfo, err := os.Lstat(a) | |
| if err != nil { | |
| if os.IsNotExist(err) { | |
| return false, nil | |
| } | |
| return false, err | |
| } | |
| bInfo, err := os.Lstat(b) | |
| if err != nil { | |
| if os.IsNotExist(err) { | |
| return false, nil | |
| } | |
| return false, err | |
| } | |
| return os.SameFile(aInfo, bInfo), nil | |
| } |