github.com/assistd/afero@v1.1.1/unionFile.go (about) 1 package afero 2 3 import ( 4 "io" 5 "os" 6 "path/filepath" 7 "syscall" 8 ) 9 10 // The UnionFile implements the afero.File interface and will be returned 11 // when reading a directory present at least in the overlay or opening a file 12 // for writing. 13 // 14 // The calls to 15 // Readdir() and Readdirnames() merge the file os.FileInfo / names from the 16 // base and the overlay - for files present in both layers, only those 17 // from the overlay will be used. 18 // 19 // When opening files for writing (Create() / OpenFile() with the right flags) 20 // the operations will be done in both layers, starting with the overlay. A 21 // successful read in the overlay will move the cursor position in the base layer 22 // by the number of bytes read. 23 type UnionFile struct { 24 Base File 25 Layer File 26 Merger DirsMerger 27 off int 28 files []os.FileInfo 29 } 30 31 func (f *UnionFile) Close() error { 32 // first close base, so we have a newer timestamp in the overlay. If we'd close 33 // the overlay first, we'd get a cacheStale the next time we access this file 34 // -> cache would be useless ;-) 35 if f.Base != nil { 36 f.Base.Close() 37 } 38 if f.Layer != nil { 39 return f.Layer.Close() 40 } 41 return BADFD 42 } 43 44 func (f *UnionFile) Read(s []byte) (int, error) { 45 if f.Layer != nil { 46 n, err := f.Layer.Read(s) 47 if (err == nil || err == io.EOF) && f.Base != nil { 48 // advance the file position also in the base file, the next 49 // call may be a write at this position (or a seek with SEEK_CUR) 50 if _, seekErr := f.Base.Seek(int64(n), os.SEEK_CUR); seekErr != nil { 51 // only overwrite err in case the seek fails: we need to 52 // report an eventual io.EOF to the caller 53 err = seekErr 54 } 55 } 56 return n, err 57 } 58 if f.Base != nil { 59 return f.Base.Read(s) 60 } 61 return 0, BADFD 62 } 63 64 func (f *UnionFile) ReadAt(s []byte, o int64) (int, error) { 65 if f.Layer != nil { 66 n, err := f.Layer.ReadAt(s, o) 67 if (err == nil || err == io.EOF) && f.Base != nil { 68 _, err = f.Base.Seek(o+int64(n), os.SEEK_SET) 69 } 70 return n, err 71 } 72 if f.Base != nil { 73 return f.Base.ReadAt(s, o) 74 } 75 return 0, BADFD 76 } 77 78 func (f *UnionFile) Seek(o int64, w int) (pos int64, err error) { 79 if f.Layer != nil { 80 pos, err = f.Layer.Seek(o, w) 81 if (err == nil || err == io.EOF) && f.Base != nil { 82 _, err = f.Base.Seek(o, w) 83 } 84 return pos, err 85 } 86 if f.Base != nil { 87 return f.Base.Seek(o, w) 88 } 89 return 0, BADFD 90 } 91 92 func (f *UnionFile) Write(s []byte) (n int, err error) { 93 if f.Layer != nil { 94 n, err = f.Layer.Write(s) 95 if err == nil && f.Base != nil { // hmm, do we have fixed size files where a write may hit the EOF mark? 96 _, err = f.Base.Write(s) 97 } 98 return n, err 99 } 100 if f.Base != nil { 101 return f.Base.Write(s) 102 } 103 return 0, BADFD 104 } 105 106 func (f *UnionFile) WriteAt(s []byte, o int64) (n int, err error) { 107 if f.Layer != nil { 108 n, err = f.Layer.WriteAt(s, o) 109 if err == nil && f.Base != nil { 110 _, err = f.Base.WriteAt(s, o) 111 } 112 return n, err 113 } 114 if f.Base != nil { 115 return f.Base.WriteAt(s, o) 116 } 117 return 0, BADFD 118 } 119 120 func (f *UnionFile) Name() string { 121 if f.Layer != nil { 122 return f.Layer.Name() 123 } 124 return f.Base.Name() 125 } 126 127 // DirsMerger is how UnionFile weaves two directories together. 128 // It takes the FileInfo slices from the layer and the base and returns a 129 // single view. 130 type DirsMerger func(lofi, bofi []os.FileInfo) ([]os.FileInfo, error) 131 132 var defaultUnionMergeDirsFn = func(lofi, bofi []os.FileInfo) ([]os.FileInfo, error) { 133 var files = make(map[string]os.FileInfo) 134 135 for _, fi := range lofi { 136 files[fi.Name()] = fi 137 } 138 139 for _, fi := range bofi { 140 if _, exists := files[fi.Name()]; !exists { 141 files[fi.Name()] = fi 142 } 143 } 144 145 rfi := make([]os.FileInfo, len(files)) 146 147 i := 0 148 for _, fi := range files { 149 rfi[i] = fi 150 i++ 151 } 152 153 return rfi, nil 154 155 } 156 157 // Readdir will weave the two directories together and 158 // return a single view of the overlayed directories 159 func (f *UnionFile) Readdir(c int) (ofi []os.FileInfo, err error) { 160 var merge DirsMerger = f.Merger 161 if merge == nil { 162 merge = defaultUnionMergeDirsFn 163 } 164 165 if f.off == 0 { 166 var lfi []os.FileInfo 167 if f.Layer != nil { 168 lfi, err = f.Layer.Readdir(-1) 169 if err != nil { 170 return nil, err 171 } 172 } 173 174 var bfi []os.FileInfo 175 if f.Base != nil { 176 bfi, err = f.Base.Readdir(-1) 177 if err != nil { 178 return nil, err 179 } 180 181 } 182 merged, err := merge(lfi, bfi) 183 if err != nil { 184 return nil, err 185 } 186 f.files = append(f.files, merged...) 187 } 188 if c == -1 { 189 return f.files[f.off:], nil 190 } 191 defer func() { f.off += c }() 192 return f.files[f.off:c], nil 193 } 194 195 func (f *UnionFile) Readdirnames(c int) ([]string, error) { 196 rfi, err := f.Readdir(c) 197 if err != nil { 198 return nil, err 199 } 200 var names []string 201 for _, fi := range rfi { 202 names = append(names, fi.Name()) 203 } 204 return names, nil 205 } 206 207 func (f *UnionFile) Stat() (os.FileInfo, error) { 208 if f.Layer != nil { 209 return f.Layer.Stat() 210 } 211 if f.Base != nil { 212 return f.Base.Stat() 213 } 214 return nil, BADFD 215 } 216 217 func (f *UnionFile) Sync() (err error) { 218 if f.Layer != nil { 219 err = f.Layer.Sync() 220 if err == nil && f.Base != nil { 221 err = f.Base.Sync() 222 } 223 return err 224 } 225 if f.Base != nil { 226 return f.Base.Sync() 227 } 228 return BADFD 229 } 230 231 func (f *UnionFile) Truncate(s int64) (err error) { 232 if f.Layer != nil { 233 err = f.Layer.Truncate(s) 234 if err == nil && f.Base != nil { 235 err = f.Base.Truncate(s) 236 } 237 return err 238 } 239 if f.Base != nil { 240 return f.Base.Truncate(s) 241 } 242 return BADFD 243 } 244 245 func (f *UnionFile) WriteString(s string) (n int, err error) { 246 if f.Layer != nil { 247 n, err = f.Layer.WriteString(s) 248 if err == nil && f.Base != nil { 249 _, err = f.Base.WriteString(s) 250 } 251 return n, err 252 } 253 if f.Base != nil { 254 return f.Base.WriteString(s) 255 } 256 return 0, BADFD 257 } 258 259 func copyToLayer(base Fs, layer Fs, name string) error { 260 bfh, err := base.Open(name) 261 if err != nil { 262 return err 263 } 264 defer bfh.Close() 265 266 // First make sure the directory exists 267 exists, err := Exists(layer, filepath.Dir(name)) 268 if err != nil { 269 return err 270 } 271 if !exists { 272 err = layer.MkdirAll(filepath.Dir(name), 0777) // FIXME? 273 if err != nil { 274 return err 275 } 276 } 277 278 // Create the file on the overlay 279 lfh, err := layer.Create(name) 280 if err != nil { 281 return err 282 } 283 n, err := io.Copy(lfh, bfh) 284 if err != nil { 285 // If anything fails, clean up the file 286 layer.Remove(name) 287 lfh.Close() 288 return err 289 } 290 291 bfi, err := bfh.Stat() 292 if err != nil || bfi.Size() != n { 293 layer.Remove(name) 294 lfh.Close() 295 return syscall.EIO 296 } 297 298 err = lfh.Close() 299 if err != nil { 300 layer.Remove(name) 301 lfh.Close() 302 return err 303 } 304 return layer.Chtimes(name, bfi.ModTime(), bfi.ModTime()) 305 }