256 lines
4.3 KiB
Go
256 lines
4.3 KiB
Go
// Copyright (C) 2022 Marius Schellenberger
|
|
|
|
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
|
|
}
|