github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/cmd/bisync/bilib/files.go (about) 1 // Package bilib provides common stuff for bisync and bisync_test 2 // Here it's got local file/directory helpers (nice to have in lib/file) 3 package bilib 4 5 import ( 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "regexp" 11 "runtime" 12 ) 13 14 // PermSecure is a Unix permission for a file accessible only by its owner 15 const PermSecure = 0600 16 17 var ( 18 regexLocalPath = regexp.MustCompile(`^[./\\]`) 19 regexWindowsPath = regexp.MustCompile(`^[a-zA-Z]:`) 20 regexRemotePath = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*:`) 21 ) 22 23 // IsLocalPath returns true if its argument is a non-remote path. 24 // Empty string or a relative path will be considered local. 25 // Note: `c:dir` will be considered local on Windows but remote on Linux. 26 func IsLocalPath(path string) bool { 27 if path == "" || regexLocalPath.MatchString(path) { 28 return true 29 } 30 if runtime.GOOS == "windows" && regexWindowsPath.MatchString(path) { 31 return true 32 } 33 return !regexRemotePath.MatchString(path) 34 } 35 36 // FileExists returns true if the local file exists 37 func FileExists(file string) bool { 38 _, err := os.Stat(file) 39 return !os.IsNotExist(err) 40 } 41 42 // CopyFileIfExists is like CopyFile but does not fail if source does not exist 43 func CopyFileIfExists(srcFile, dstFile string) error { 44 if !FileExists(srcFile) { 45 return nil 46 } 47 return CopyFile(srcFile, dstFile) 48 } 49 50 // CopyFile copies a local file 51 func CopyFile(src, dst string) (err error) { 52 var ( 53 rd io.ReadCloser 54 wr io.WriteCloser 55 info os.FileInfo 56 ) 57 if info, err = os.Stat(src); err != nil { 58 return 59 } 60 if rd, err = os.Open(src); err != nil { 61 return 62 } 63 defer func() { 64 _ = rd.Close() 65 }() 66 if wr, err = os.Create(dst); err != nil { 67 return 68 } 69 _, err = io.Copy(wr, rd) 70 if e := wr.Close(); err == nil { 71 err = e 72 } 73 if e := os.Chmod(dst, info.Mode()); err == nil { 74 err = e 75 } 76 if e := os.Chtimes(dst, info.ModTime(), info.ModTime()); err == nil { 77 err = e 78 } 79 return 80 } 81 82 // CopyDir copies a local directory 83 func CopyDir(src string, dst string) (err error) { 84 src = filepath.Clean(src) 85 dst = filepath.Clean(dst) 86 87 si, err := os.Stat(src) 88 if err != nil { 89 return err 90 } 91 if !si.IsDir() { 92 return fmt.Errorf("source is not a directory") 93 } 94 95 _, err = os.Stat(dst) 96 if err != nil && !os.IsNotExist(err) { 97 return 98 } 99 if err == nil { 100 return fmt.Errorf("destination already exists") 101 } 102 103 err = os.MkdirAll(dst, si.Mode()) 104 if err != nil { 105 return 106 } 107 108 entries, err := os.ReadDir(src) 109 if err != nil { 110 return 111 } 112 113 for _, entry := range entries { 114 srcPath := filepath.Join(src, entry.Name()) 115 dstPath := filepath.Join(dst, entry.Name()) 116 117 if entry.IsDir() { 118 err = CopyDir(srcPath, dstPath) 119 if err != nil { 120 return 121 } 122 } else { 123 // Skip symlinks. 124 if entry.Type()&os.ModeSymlink != 0 { 125 continue 126 } 127 128 err = CopyFile(srcPath, dstPath) 129 if err != nil { 130 return 131 } 132 } 133 } 134 135 return 136 }