package crypto import ( "cachefs/pkg/provider" "io" "io/fs" "os" "sync" ) var _ provider.File = (*file)(nil) type file struct { provider.File rmu sync.Mutex wmu sync.Mutex fs *FS r *reader w *writer nonce [NonceSize]byte offset int64 name string stat *stat dir bool } func newFile(key []byte, name string, fs *FS, f provider.File, dir bool) (ef provider.File, err error) { if dir { return &file{ File: f, fs: fs, name: name, dir: dir, }, nil } nonce, err := initNonce(f) if err != nil { return } skey := streamKey(key, nonce) w, err := newWriter(skey, f) if err != nil { return } r, err := newReader(skey, f) if err != nil { return } ef = &file{ File: f, fs: fs, w: w, r: r, name: name, } return ef, nil } func (f *file) Stat() (fs.FileInfo, error) { if f.stat != nil { return f.stat, nil } fi, err := f.File.Stat() if err != nil { return nil, err } if f.dir { return dir{FileInfo: fi, name: f.name}, nil } f.stat = &stat{ FileInfo: fi, size: RealSize(fi.Size()), name: f.name, } return f.stat, nil } func (f *file) Name() string { return f.name } func (f *file) Fd() uintptr { osf, ok := f.File.(*os.File) if ok { return osf.Fd() } return 0 } type stat struct { fs.FileInfo size int64 name string } func (s stat) Size() int64 { return s.size } func (s stat) Name() string { return s.name } func (f *file) Truncate(n int64) error { return f.File.Truncate(Size(n)) } func (f *file) Close() error { if f.dir { return f.File.Close() } e1 := f.w.Close() e2 := f.File.Close() if e1 != nil { return e1 } return e2 } func (f *file) Seek(offset int64, whence int) (n int64, err error) { var cn, off, roff int64 switch whence { case io.SeekStart: cn, off, roff = align(offset) f.offset = KDFNonceSize + off n, err = f.File.Seek(f.offset, whence) case io.SeekCurrent: if f.offset == 0 { f.offset = KDFNonceSize } cn, off, roff = align(f.offset + f.r.off + offset) f.offset = off _, err = f.File.Seek(f.offset, io.SeekStart) n = offset } f.r.err = nil f.r.unread = f.r.unread[:0] if err != nil { return } f.nonce = [NonceSize]byte{} put(&f.nonce, cn) f.r.nonce = f.nonce f.w.nonce = f.nonce f.r.off = roff f.r.cn = cn return } func (f *file) Read(p []byte) (n int, err error) { f.r.cn = -1 n, err = f.r.Read(p) return } func (f *file) Write(p []byte) (n int, err error) { n, err = f.w.Write(p) return } func (f *file) ReadAt(p []byte, pos int64) (n int, err error) { f.rmu.Lock() defer f.rmu.Unlock() cn, _, roff := align(pos) var last bool if f.r.cn != cn || roff >= int64(len(f.r.unread)) { _, err = f.Seek(pos, io.SeekStart) if err != nil { return } last, err = f.r.readChunk() if err != nil { return } } n = copy(p, f.r.unread[roff:]) if last && len(f.r.unread) == n+int(roff) { err = io.EOF } return } func (f *file) WriteAt(data []byte, pos int64) (n int, err error) { f.wmu.Lock() defer f.wmu.Unlock() for n != len(data) { cn, off, woff := align(pos + int64(n)) c, ok := f.w.cm[cn] if !ok { c = &chunkWriter{offset: off + KDFNonceSize, w: f.w, cn: cn} put(&c.nonce, cn) f.w.cm[cn] = c if f.w.last == nil { f.w.last = c } else if cn > f.w.last.cn { f.w.last = c } } w, e := c.writeAt(data[n:], woff) n += w if c.full() { if f.w.last != nil && f.w.last.cn == cn { f.w.last = nil } // async flush go func() { c.flush(notLastChunk) }() delete(f.w.cm, cn) } if e != nil { err = e return } } return } func (f *file) Readdir(n int) ([]fs.FileInfo, error) { fis, err := f.File.Readdir(n) if err != nil { return nil, err } fio := make([]fs.FileInfo, len(fis)) for i, fi := range fis { name, err := f.fs.decPath(fi.Name()) if err != nil { return nil, err } fio[i] = stat{FileInfo: fi, size: RealSize(fi.Size()), name: name} } return fio, nil } func (f *file) ReadDir(n int) ([]fs.DirEntry, error) { fis, err := f.Readdir(n) if err != nil { return nil, err } fio := make([]fs.DirEntry, len(fis)) for i, fi := range fis { fio[i] = fs.FileInfoToDirEntry(fi) } return fio, nil } type dir struct { fs.FileInfo name string } func (d dir) Name() string { return d.name }