github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/lib/fsutil/copy.go (about) 1 package fsutil 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "path" 9 "syscall" 10 ) 11 12 const ( 13 dirPerms = syscall.S_IRWXU | syscall.S_IRGRP | syscall.S_IXGRP | 14 syscall.S_IROTH | syscall.S_IXOTH 15 ) 16 17 func copyToFile(destFilename string, perm os.FileMode, reader io.Reader, 18 length uint64) error { 19 tmpFilename := destFilename + "~" 20 destFile, err := os.OpenFile(tmpFilename, 21 os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm) 22 if err != nil { 23 return err 24 } 25 defer os.Remove(tmpFilename) 26 defer destFile.Close() 27 var nCopied int64 28 iLength := int64(length) 29 if length < 1 { 30 if _, err := io.Copy(destFile, reader); err != nil { 31 return fmt.Errorf("error copying: %s", err) 32 } 33 } else { 34 if nCopied, err = io.CopyN(destFile, reader, iLength); err != nil { 35 return fmt.Errorf("error copying: %s", err) 36 } 37 if nCopied != iLength { 38 return fmt.Errorf("expected length: %d, got: %d for: %s\n", 39 length, nCopied, tmpFilename) 40 } 41 } 42 return os.Rename(tmpFilename, destFilename) 43 } 44 45 func copyTree(destDir, sourceDir string, 46 copyFunc func(destFilename, sourceFilename string, 47 mode os.FileMode) error) error { 48 file, err := os.Open(sourceDir) 49 if err != nil { 50 if os.IsNotExist(err) { 51 return nil 52 } 53 return err 54 } 55 names, err := file.Readdirnames(-1) 56 file.Close() 57 if err != nil { 58 return err 59 } 60 for _, name := range names { 61 sourceFilename := path.Join(sourceDir, name) 62 destFilename := path.Join(destDir, name) 63 var stat syscall.Stat_t 64 if err := syscall.Lstat(sourceFilename, &stat); err != nil { 65 return errors.New(sourceFilename + ": " + err.Error()) 66 } 67 switch stat.Mode & syscall.S_IFMT { 68 case syscall.S_IFDIR: 69 if err := os.Mkdir(destFilename, dirPerms); err != nil { 70 if !os.IsExist(err) { 71 return err 72 } 73 } 74 err := copyTree(destFilename, sourceFilename, copyFunc) 75 if err != nil { 76 return err 77 } 78 case syscall.S_IFREG: 79 err := copyFunc(destFilename, sourceFilename, 80 os.FileMode(stat.Mode)&os.ModePerm) 81 if err != nil { 82 return err 83 } 84 case syscall.S_IFLNK: 85 sourceTarget, err := os.Readlink(sourceFilename) 86 if err != nil { 87 return errors.New(sourceFilename + ": " + err.Error()) 88 } 89 if destTarget, err := os.Readlink(destFilename); err == nil { 90 if sourceTarget == destTarget { 91 continue 92 } 93 } 94 os.Remove(destFilename) 95 if err := os.Symlink(sourceTarget, destFilename); err != nil { 96 return err 97 } 98 default: 99 return errors.New("unsupported file type") 100 } 101 } 102 return nil 103 } 104 105 func copyFile(destFilename, sourceFilename string, mode os.FileMode) error { 106 if mode == 0 { 107 var stat syscall.Stat_t 108 if err := syscall.Stat(sourceFilename, &stat); err != nil { 109 return errors.New(sourceFilename + ": " + err.Error()) 110 } 111 mode = os.FileMode(stat.Mode & syscall.S_IFMT) 112 } 113 sourceFile, err := os.Open(sourceFilename) 114 if err != nil { 115 return errors.New(sourceFilename + ": " + err.Error()) 116 } 117 defer sourceFile.Close() 118 return copyToFile(destFilename, mode, sourceFile, 0) 119 }