package utils import ( "fmt" "io" "os" "path/filepath" "syscall" ) // Move moves src to dst, including across filesystems. // It first tries os.Rename; if that fails because src and dst are on different // devices (EXDEV), it falls back to a recursive copy followed by removal of src. func Move(src, dst string) error { if err := os.Rename(src, dst); err == nil { return nil } else if !isCrossDeviceErr(err) { return err } info, err := os.Stat(src) if err != nil { return err } if info.IsDir() { return moveDir(src, dst) } return moveFile(src, dst, info) } func isCrossDeviceErr(err error) bool { if linkErr, ok := err.(*os.LinkError); ok { return linkErr.Err == syscall.EXDEV } return false } func moveFile(src, dst string, info os.FileInfo) error { if err := copyFile(src, dst, info.Mode()); err != nil { return err } return os.Remove(src) } func copyFile(src, dst string, mode os.FileMode) error { in, err := os.Open(src) if err != nil { return err } defer in.Close() out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode) if err != nil { return err } _, err = io.Copy(out, in) closeErr := out.Close() if err != nil { return err } return closeErr } func moveDir(src, dst string) error { if err := copyDir(src, dst); err != nil { return err } return os.RemoveAll(src) } func copyDir(src, dst string) error { srcInfo, err := os.Stat(src) if err != nil { return err } if !srcInfo.IsDir() { return fmt.Errorf("source is not a directory: %s", src) } if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil { return err } entries, err := os.ReadDir(src) if err != nil { return err } for _, entry := range entries { srcPath := filepath.Join(src, entry.Name()) dstPath := filepath.Join(dst, entry.Name()) info, err := entry.Info() if err != nil { return err } if entry.IsDir() { if err := copyDir(srcPath, dstPath); err != nil { return err } } else { if err := copyFile(srcPath, dstPath, info.Mode()); err != nil { return err } } } return nil }