Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
320 lines
6.6 KiB
320 lines
6.6 KiB
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 if c > 0. |
|
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 c <= 0 && len(f.files) == 0 { |
|
return f.files, nil |
|
} |
|
|
|
if f.off >= len(f.files) { |
|
return nil, io.EOF |
|
} |
|
|
|
if c <= 0 { |
|
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()) |
|
}
|
|
|