github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/cp/cp.go (about) 1 // Copyright 2018 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package cp implements routines to copy files. 6 // 7 // CopyTree in particular copies entire trees of files. 8 // 9 // Only directories, symlinks, and regular files are currently supported. 10 package cp 11 12 import ( 13 "errors" 14 "fmt" 15 "io" 16 "os" 17 "path/filepath" 18 ) 19 20 // ErrSkip can be returned by PreCallback to skip a file. 21 var ErrSkip = errors.New("skip") 22 23 // Options are configuration options for how copying files should behave. 24 type Options struct { 25 // If NoFollowSymlinks is set, Copy copies the symlink itself rather 26 // than following the symlink and copying the file it points to. 27 NoFollowSymlinks bool 28 29 // PreCallback is called on each file to be copied before it is copied 30 // if specified. 31 // 32 // If PreCallback returns ErrSkip, the file is skipped and Copy returns 33 // nil. 34 // 35 // If PreCallback returns another non-nil error, the file is not copied 36 // and Copy returns the error. 37 PreCallback func(src, dst string, srcfi os.FileInfo) error 38 39 // PostCallback is called on each file after it is copied if specified. 40 PostCallback func(src, dst string) 41 } 42 43 // Default are the default options. Default follows symlinks. 44 var Default = Options{} 45 46 // NoFollowSymlinks is the default options with following symlinks turned off. 47 var NoFollowSymlinks = Options{ 48 NoFollowSymlinks: true, 49 } 50 51 func (o Options) stat(path string) (os.FileInfo, error) { 52 if o.NoFollowSymlinks { 53 return os.Lstat(path) 54 } 55 return os.Stat(path) 56 } 57 58 // Copy copies a file at src to dst. 59 func (o Options) Copy(src, dst string) error { 60 srcInfo, err := o.stat(src) 61 if err != nil { 62 return err 63 } 64 65 if o.PreCallback != nil { 66 if err := o.PreCallback(src, dst, srcInfo); err == ErrSkip { 67 return nil 68 } else if err != nil { 69 return err 70 } 71 } 72 if err := copyFile(src, dst, srcInfo); err != nil { 73 return err 74 } 75 if o.PostCallback != nil { 76 o.PostCallback(src, dst) 77 } 78 return nil 79 } 80 81 // CopyTree recursively copies all files in the src tree to dst. 82 func (o Options) CopyTree(src, dst string) error { 83 return filepath.Walk(src, func(path string, fi os.FileInfo, err error) error { 84 if err != nil { 85 return err 86 } 87 88 rel, err := filepath.Rel(src, path) 89 if err != nil { 90 return err 91 } 92 return o.Copy(path, filepath.Join(dst, rel)) 93 }) 94 } 95 96 // Copy src file to dst file using Default's config. 97 func Copy(src, dst string) error { 98 return Default.Copy(src, dst) 99 } 100 101 // CopyTree recursively copies all files in the src tree to dst using Default's 102 // config. 103 func CopyTree(src, dst string) error { 104 return Default.CopyTree(src, dst) 105 } 106 107 func copyFile(src, dst string, srcInfo os.FileInfo) error { 108 m := srcInfo.Mode() 109 switch { 110 case m.IsDir(): 111 return os.Mkdir(dst, srcInfo.Mode().Perm()) 112 113 case m.IsRegular(): 114 return copyRegularFile(src, dst, srcInfo) 115 116 case m&os.ModeSymlink == os.ModeSymlink: 117 // Yeah, this may not make any sense logically. But this is how 118 // cp does it. 119 target, err := os.Readlink(src) 120 if err != nil { 121 return err 122 } 123 return os.Symlink(target, dst) 124 125 default: 126 return &os.PathError{ 127 Op: "copy", 128 Path: src, 129 Err: fmt.Errorf("unsupported file mode %s", m), 130 } 131 } 132 } 133 134 func copyRegularFile(src, dst string, srcfi os.FileInfo) error { 135 srcf, err := os.Open(src) 136 if err != nil { 137 return err 138 } 139 defer srcf.Close() 140 141 dstf, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcfi.Mode().Perm()) 142 if err != nil { 143 return err 144 } 145 defer dstf.Close() 146 147 _, err = io.Copy(dstf, srcf) 148 return err 149 }