Search code examples
gocopydirectory

Copy a folder in go


Is there an easy way to copy a directory in go? I have the following function:

err = CopyDir("sourceFolder","destinationFolder")

Nothing so far has worked, including libraries such as github.com/cf-guardian/guardian/kernel/fileutils

One important thing to note is that I need to preserve directory structure, including the sourceFolder itself, not simply copy all contents of the folder.


Solution

  • I believe that docker implementation can be considered as complete solution for handling edge cases: https://github.com/moby/moby/blob/master/daemon/graphdriver/copy/copy.go

    There are following good things:

    • unsupported file type rise error
    • preserving permissions and ownership
    • preserving extended attributes
    • preserving timestamp

    but because of a lot of imports your tiny application becomes huge.

    I've tried to combine several solutions but use stdlib and for Linux only:

    func CopyDirectory(scrDir, dest string) error {
        entries, err := os.ReadDir(scrDir)
        if err != nil {
            return err
        }
        for _, entry := range entries {
            sourcePath := filepath.Join(scrDir, entry.Name())
            destPath := filepath.Join(dest, entry.Name())
    
            fileInfo, err := os.Stat(sourcePath)
            if err != nil {
                return err
            }
    
            stat, ok := fileInfo.Sys().(*syscall.Stat_t)
            if !ok {
                return fmt.Errorf("failed to get raw syscall.Stat_t data for '%s'", sourcePath)
            }
    
            switch fileInfo.Mode() & os.ModeType{
            case os.ModeDir:
                if err := CreateIfNotExists(destPath, 0755); err != nil {
                    return err
                }
                if err := CopyDirectory(sourcePath, destPath); err != nil {
                    return err
                }
            case os.ModeSymlink:
                if err := CopySymLink(sourcePath, destPath); err != nil {
                    return err
                }
            default:
                if err := Copy(sourcePath, destPath); err != nil {
                    return err
                }
            }
    
            if err := os.Lchown(destPath, int(stat.Uid), int(stat.Gid)); err != nil {
                return err
            }
    
            fInfo, err := entry.Info()
            if err != nil {
                return err
            }
    
            isSymlink := fInfo.Mode()&os.ModeSymlink != 0
            if !isSymlink {
                if err := os.Chmod(destPath, fInfo.Mode()); err != nil {
                    return err
                }
            }
        }
        return nil
    }
    
    func Copy(srcFile, dstFile string) error {
        out, err := os.Create(dstFile)
        if err != nil {
            return err
        }
    
        defer out.Close()
    
        in, err := os.Open(srcFile)
        if err != nil {
            return err
        }
    
        defer in.Close()
    
        _, err = io.Copy(out, in)
        if err != nil {
            return err
        }
    
        return nil
    }
    
    func Exists(filePath string) bool {
        if _, err := os.Stat(filePath); os.IsNotExist(err) {
            return false
        }
    
        return true
    }
    
    func CreateIfNotExists(dir string, perm os.FileMode) error {
        if Exists(dir) {
            return nil
        }
    
        if err := os.MkdirAll(dir, perm); err != nil {
            return fmt.Errorf("failed to create directory: '%s', error: '%s'", dir, err.Error())
        }
    
        return nil
    }
    
    func CopySymLink(source, dest string) error {
        link, err := os.Readlink(source)
        if err != nil {
            return err
        }
        return os.Symlink(link, dest)
    }