| package afero |
| |
| import ( |
| "io" |
| "os" |
| "path/filepath" |
| "syscall" |
| ) |
| |
| // The UnionFile implements the afero.File interface and will be returned |
| // when reading a directory present at least in the overlay or opening a file |
| // for writing. |
| // |
| // The calls to |
| // Readdir() and Readdirnames() merge the file os.FileInfo / names from the |
| // base and the overlay - for files present in both layers, only those |
| // from the overlay will be used. |
| // |
| // When opening files for writing (Create() / OpenFile() with the right flags) |
| // the operations will be done in both layers, starting with the overlay. A |
| // successful read in the overlay will move the cursor position in the base layer |
| // by the number of bytes read. |
| type UnionFile struct { |
| Base File |
| Layer File |
| Merger DirsMerger |
| off int |
| files []os.FileInfo |
| } |
| |
| func (f *UnionFile) Close() error { |
| // first close base, so we have a newer timestamp in the overlay. If we'd close |
| // the overlay first, we'd get a cacheStale the next time we access this file |
| // -> cache would be useless ;-) |
| if f.Base != nil { |
| f.Base.Close() |
| } |
| if f.Layer != nil { |
| return f.Layer.Close() |
| } |
| return BADFD |
| } |
| |
| func (f *UnionFile) Read(s []byte) (int, error) { |
| if f.Layer != nil { |
| n, err := f.Layer.Read(s) |
| if (err == nil || err == io.EOF) && f.Base != nil { |
| // advance the file position also in the base file, the next |
| // call may be a write at this position (or a seek with SEEK_CUR) |
| if _, seekErr := f.Base.Seek(int64(n), os.SEEK_CUR); seekErr != nil { |
| // only overwrite err in case the seek fails: we need to |
| // report an eventual io.EOF to the caller |
| err = seekErr |
| } |
| } |
| return n, err |
| } |
| if f.Base != nil { |
| return f.Base.Read(s) |
| } |
| return 0, BADFD |
| } |
| |
| func (f *UnionFile) ReadAt(s []byte, o int64) (int, error) { |
| if f.Layer != nil { |
| n, err := f.Layer.ReadAt(s, o) |
| if (err == nil || err == io.EOF) && f.Base != nil { |
| _, err = f.Base.Seek(o+int64(n), os.SEEK_SET) |
| } |
| return n, err |
| } |
| if f.Base != nil { |
| return f.Base.ReadAt(s, o) |
| } |
| return 0, BADFD |
| } |
| |
| func (f *UnionFile) Seek(o int64, w int) (pos int64, err error) { |
| if f.Layer != nil { |
| pos, err = f.Layer.Seek(o, w) |
| if (err == nil || err == io.EOF) && f.Base != nil { |
| _, err = f.Base.Seek(o, w) |
| } |
| return pos, err |
| } |
| if f.Base != nil { |
| return f.Base.Seek(o, w) |
| } |
| return 0, BADFD |
| } |
| |
| func (f *UnionFile) Write(s []byte) (n int, err error) { |
| if f.Layer != nil { |
| n, err = f.Layer.Write(s) |
| if err == nil && f.Base != nil { // hmm, do we have fixed size files where a write may hit the EOF mark? |
| _, err = f.Base.Write(s) |
| } |
| return n, err |
| } |
| if f.Base != nil { |
| return f.Base.Write(s) |
| } |
| return 0, BADFD |
| } |
| |
| func (f *UnionFile) WriteAt(s []byte, o int64) (n int, err error) { |
| if f.Layer != nil { |
| n, err = f.Layer.WriteAt(s, o) |
| if err == nil && f.Base != nil { |
| _, err = f.Base.WriteAt(s, o) |
| } |
| return n, err |
| } |
| if f.Base != nil { |
| return f.Base.WriteAt(s, o) |
| } |
| return 0, BADFD |
| } |
| |
| func (f *UnionFile) Name() string { |
| if f.Layer != nil { |
| return f.Layer.Name() |
| } |
| return f.Base.Name() |
| } |
| |
| // DirsMerger is how UnionFile weaves two directories together. |
| // It takes the FileInfo slices from the layer and the base and returns a |
| // single view. |
| type DirsMerger func(lofi, bofi []os.FileInfo) ([]os.FileInfo, error) |
| |
| var defaultUnionMergeDirsFn = func(lofi, bofi []os.FileInfo) ([]os.FileInfo, error) { |
| var files = make(map[string]os.FileInfo) |
| |
| for _, fi := range lofi { |
| files[fi.Name()] = fi |
| } |
| |
| for _, fi := range bofi { |
| if _, exists := files[fi.Name()]; !exists { |
| files[fi.Name()] = fi |
| } |
| } |
| |
| rfi := make([]os.FileInfo, len(files)) |
| |
| i := 0 |
| for _, fi := range files { |
| rfi[i] = fi |
| i++ |
| } |
| |
| return rfi, nil |
| |
| } |
| |
| // Readdir will weave the two directories together and |
| // return a single view of the overlayed directories |
| // At the end of the directory view, the error is io.EOF. |
| func (f *UnionFile) Readdir(c int) (ofi []os.FileInfo, err error) { |
| var merge DirsMerger = f.Merger |
| if merge == nil { |
| merge = defaultUnionMergeDirsFn |
| } |
| |
| if f.off == 0 { |
| var lfi []os.FileInfo |
| if f.Layer != nil { |
| lfi, err = f.Layer.Readdir(-1) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| var bfi []os.FileInfo |
| if f.Base != nil { |
| bfi, err = f.Base.Readdir(-1) |
| if err != nil { |
| return nil, err |
| } |
| |
| } |
| merged, err := merge(lfi, bfi) |
| if err != nil { |
| return nil, err |
| } |
| f.files = append(f.files, merged...) |
| } |
| |
| if f.off >= len(f.files) { |
| return nil, io.EOF |
| } |
| |
| if c == -1 { |
| return f.files[f.off:], nil |
| } |
| |
| if c > len(f.files) { |
| c = len(f.files) |
| } |
| |
| defer func() { f.off += c }() |
| return f.files[f.off:c], nil |
| } |
| |
| func (f *UnionFile) Readdirnames(c int) ([]string, error) { |
| rfi, err := f.Readdir(c) |
| if err != nil { |
| return nil, err |
| } |
| var names []string |
| for _, fi := range rfi { |
| names = append(names, fi.Name()) |
| } |
| return names, nil |
| } |
| |
| func (f *UnionFile) Stat() (os.FileInfo, error) { |
| if f.Layer != nil { |
| return f.Layer.Stat() |
| } |
| if f.Base != nil { |
| return f.Base.Stat() |
| } |
| return nil, BADFD |
| } |
| |
| func (f *UnionFile) Sync() (err error) { |
| if f.Layer != nil { |
| err = f.Layer.Sync() |
| if err == nil && f.Base != nil { |
| err = f.Base.Sync() |
| } |
| return err |
| } |
| if f.Base != nil { |
| return f.Base.Sync() |
| } |
| return BADFD |
| } |
| |
| func (f *UnionFile) Truncate(s int64) (err error) { |
| if f.Layer != nil { |
| err = f.Layer.Truncate(s) |
| if err == nil && f.Base != nil { |
| err = f.Base.Truncate(s) |
| } |
| return err |
| } |
| if f.Base != nil { |
| return f.Base.Truncate(s) |
| } |
| return BADFD |
| } |
| |
| func (f *UnionFile) WriteString(s string) (n int, err error) { |
| if f.Layer != nil { |
| n, err = f.Layer.WriteString(s) |
| if err == nil && f.Base != nil { |
| _, err = f.Base.WriteString(s) |
| } |
| return n, err |
| } |
| if f.Base != nil { |
| return f.Base.WriteString(s) |
| } |
| return 0, BADFD |
| } |
| |
| func copyToLayer(base Fs, layer Fs, name string) error { |
| bfh, err := base.Open(name) |
| if err != nil { |
| return err |
| } |
| defer bfh.Close() |
| |
| // First make sure the directory exists |
| exists, err := Exists(layer, filepath.Dir(name)) |
| if err != nil { |
| return err |
| } |
| if !exists { |
| err = layer.MkdirAll(filepath.Dir(name), 0777) // FIXME? |
| if err != nil { |
| return err |
| } |
| } |
| |
| // Create the file on the overlay |
| lfh, err := layer.Create(name) |
| if err != nil { |
| return err |
| } |
| n, err := io.Copy(lfh, bfh) |
| if err != nil { |
| // If anything fails, clean up the file |
| layer.Remove(name) |
| lfh.Close() |
| return err |
| } |
| |
| bfi, err := bfh.Stat() |
| if err != nil || bfi.Size() != n { |
| layer.Remove(name) |
| lfh.Close() |
| return syscall.EIO |
| } |
| |
| err = lfh.Close() |
| if err != nil { |
| layer.Remove(name) |
| lfh.Close() |
| return err |
| } |
| return layer.Chtimes(name, bfi.ModTime(), bfi.ModTime()) |
| } |