mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-04 08:02:26 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			330 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			330 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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), io.SeekCurrent); 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), io.SeekStart)
 | 
						|
		}
 | 
						|
		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) {
 | 
						|
	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...)
 | 
						|
	}
 | 
						|
	files := f.files[f.off:]
 | 
						|
 | 
						|
	if c <= 0 {
 | 
						|
		return files, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if len(files) == 0 {
 | 
						|
		return nil, io.EOF
 | 
						|
	}
 | 
						|
 | 
						|
	if c > len(files) {
 | 
						|
		c = len(files)
 | 
						|
	}
 | 
						|
 | 
						|
	defer func() { f.off += c }()
 | 
						|
	return files[: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 copyFile(base Fs, layer Fs, name string, bfh File) error {
 | 
						|
	// 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), 0o777) // 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())
 | 
						|
}
 | 
						|
 | 
						|
func copyToLayer(base Fs, layer Fs, name string) error {
 | 
						|
	bfh, err := base.Open(name)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	defer bfh.Close()
 | 
						|
 | 
						|
	return copyFile(base, layer, name, bfh)
 | 
						|
}
 | 
						|
 | 
						|
func copyFileToLayer(base Fs, layer Fs, name string, flag int, perm os.FileMode) error {
 | 
						|
	bfh, err := base.OpenFile(name, flag, perm)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	defer bfh.Close()
 | 
						|
 | 
						|
	return copyFile(base, layer, name, bfh)
 | 
						|
}
 |