github.com/sandwich-go/boost@v1.3.29/xos/copy.go (about) 1 package xos 2 3 import ( 4 "io" 5 "io/fs" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 ) 10 11 const ( 12 // tmpPermissionForDirectory makes the destination directory writable, 13 // so that stuff can be copied recursively even if any original directory is NOT writable. 14 // See https://github.com/otiai10/copy/pull/9 for more information. 15 tmpPermissionForDirectory = os.FileMode(0755) 16 ) 17 18 // Copy copies src to dest, doesn't matter if src is a directory or a file. 19 func Copy(src, dest string, opt ...CopyOption) error { 20 info, err := os.Lstat(src) 21 if err != nil { 22 return err 23 } 24 return switchboard(src, dest, info, NewCopyOptions(opt...)) 25 } 26 27 // switchboard switches proper copy functions regarding file type, etc... 28 // If there would be anything else here, add a case to this switchboard. 29 func switchboard(src, dest string, info os.FileInfo, opt *CopyOptions) error { 30 switch { 31 case info.Mode()&os.ModeSymlink != 0: 32 return onsymlink(src, dest, info, opt) 33 case info.IsDir(): 34 return dcopy(src, dest, info, opt) 35 default: 36 return fcopy(src, dest, info, opt) 37 } 38 } 39 40 // copy decide if this src should be copied or not. 41 // Because this "copy" could be called recursively, 42 // "info" MUST be given here, NOT nil. 43 func _copy(src, dest string, info os.FileInfo, opt *CopyOptions) error { 44 skip, err := opt.Skip(src) 45 if err != nil { 46 return err 47 } 48 if skip { 49 return nil 50 } 51 return switchboard(src, dest, info, opt) 52 } 53 54 // fcopy is for just a file, 55 // with considering existence of parent directory 56 // and file permission. 57 func fcopy(src, dest string, info os.FileInfo, opt *CopyOptions) error { 58 if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil { 59 return err 60 } 61 f, err := os.Create(dest) 62 if err != nil { 63 return err 64 } 65 defer fclose(f, &err) 66 if err = os.Chmod(f.Name(), info.Mode()|opt.AddPermission); err != nil { 67 return err 68 } 69 var s *os.File 70 s, err = os.Open(src) 71 if err != nil { 72 return err 73 } 74 defer fclose(s, &err) 75 if _, err = io.Copy(f, s); err != nil { 76 return err 77 } 78 if opt.Sync { 79 err = f.Sync() 80 } 81 return err 82 } 83 84 // dcopy is for a directory, 85 // with scanning contents inside the directory 86 // and pass everything to "copy" recursively. 87 func dcopy(srcdir, destdir string, info os.FileInfo, opt *CopyOptions) (err error) { 88 originalMode := info.Mode() 89 // Make dest dir with 0755 so that everything writable. 90 if err = os.MkdirAll(destdir, tmpPermissionForDirectory); err != nil { 91 return 92 } 93 // Recover dir mode with original one. 94 defer chmod(destdir, originalMode|opt.AddPermission, &err) 95 var contents []fs.FileInfo 96 contents, err = ioutil.ReadDir(srcdir) 97 if err != nil { 98 return 99 } 100 for _, content := range contents { 101 cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name()) 102 if err = _copy(cs, cd, content, opt); err != nil { 103 // If any error, exit immediately 104 return 105 } 106 } 107 return 108 } 109 110 func onsymlink(src, dest string, info os.FileInfo, opt *CopyOptions) error { 111 switch opt.OnSymlink(src) { 112 case Shallow: 113 return lcopy(src, dest) 114 case Deep: 115 orig, err := filepath.EvalSymlinks(src) 116 if err != nil { 117 return err 118 } 119 info, err = os.Lstat(orig) 120 if err != nil { 121 return err 122 } 123 return _copy(orig, dest, info, opt) 124 case Skip: 125 fallthrough 126 default: 127 return nil // do nothing 128 } 129 } 130 131 // lcopy is for a symlink, 132 // with just creating a new symlink by replicating src symlink. 133 func lcopy(src, dest string) error { 134 linkSrc, err := os.Readlink(src) 135 if err != nil { 136 return err 137 } 138 return os.Symlink(linkSrc, dest) 139 } 140 141 // fclose ANYHOW closes file, 142 // with asiging error raised during Close, 143 // BUT respecting the error already reported. 144 func fclose(f *os.File, reported *error) { 145 if err := f.Close(); *reported == nil { 146 *reported = err 147 } 148 } 149 150 // chmod ANYHOW changes file mode, 151 // with asiging error raised during Chmod, 152 // BUT respecting the error already reported. 153 func chmod(dir string, mode os.FileMode, reported *error) { 154 if err := os.Chmod(dir, mode); *reported == nil { 155 *reported = err 156 } 157 }