forked from TrueCloudLab/frostfs-node
127 lines
2.8 KiB
Go
127 lines
2.8 KiB
Go
package fstree
|
|
|
|
import (
|
|
"errors"
|
|
"io/fs"
|
|
"os"
|
|
"strconv"
|
|
"sync/atomic"
|
|
"syscall"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/util/logicerr"
|
|
utilSync "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/sync"
|
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
|
)
|
|
|
|
type writer interface {
|
|
writeData(string, []byte) error
|
|
removeFile(string) error
|
|
}
|
|
|
|
type genericWriter struct {
|
|
perm fs.FileMode
|
|
flags int
|
|
|
|
fileGuard keyLock
|
|
fileCounter FileCounter
|
|
fileCounterEnabled bool
|
|
suffix atomic.Uint64
|
|
}
|
|
|
|
func newGenericWriteData(c FileCounter, perm fs.FileMode, noSync bool) writer {
|
|
flags := os.O_WRONLY | os.O_CREATE | os.O_TRUNC | os.O_EXCL
|
|
if !noSync {
|
|
flags |= os.O_SYNC
|
|
}
|
|
|
|
var fileGuard keyLock = &noopKeyLock{}
|
|
fileCounterEnabled := counterEnabled(c)
|
|
if fileCounterEnabled {
|
|
fileGuard = utilSync.NewKeyLocker[string]()
|
|
}
|
|
|
|
w := &genericWriter{
|
|
perm: perm,
|
|
flags: flags,
|
|
|
|
fileCounterEnabled: fileCounterEnabled,
|
|
fileGuard: fileGuard,
|
|
fileCounter: c,
|
|
}
|
|
return w
|
|
}
|
|
|
|
func (w *genericWriter) writeData(p string, data []byte) error {
|
|
tmpPath := p + "#" + strconv.FormatUint(w.suffix.Add(1), 10)
|
|
return w.writeAndRename(tmpPath, p, data)
|
|
}
|
|
|
|
// writeAndRename opens tmpPath exclusively, writes data to it and renames it to p.
|
|
func (w *genericWriter) writeAndRename(tmpPath, p string, data []byte) error {
|
|
if w.fileCounterEnabled {
|
|
w.fileGuard.Lock(p)
|
|
defer w.fileGuard.Unlock(p)
|
|
}
|
|
|
|
err := w.writeFile(tmpPath, data)
|
|
if err != nil {
|
|
var pe *fs.PathError
|
|
if errors.As(err, &pe) {
|
|
switch pe.Err {
|
|
case syscall.ENOSPC:
|
|
err = common.ErrNoSpace
|
|
_ = os.RemoveAll(tmpPath)
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
if w.fileCounterEnabled {
|
|
w.fileCounter.Inc()
|
|
var targetFileExists bool
|
|
if _, e := os.Stat(p); e == nil {
|
|
targetFileExists = true
|
|
}
|
|
err = os.Rename(tmpPath, p)
|
|
if err == nil && targetFileExists {
|
|
w.fileCounter.Dec()
|
|
}
|
|
} else {
|
|
err = os.Rename(tmpPath, p)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// writeFile writes data to a file with path p.
|
|
// The code is copied from `os.WriteFile` with minor corrections for flags.
|
|
func (w *genericWriter) writeFile(p string, data []byte) error {
|
|
f, err := os.OpenFile(p, w.flags, w.perm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write(data)
|
|
if err1 := f.Close(); err1 != nil && err == nil {
|
|
err = err1
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (w *genericWriter) removeFile(p string) error {
|
|
var err error
|
|
if w.fileCounterEnabled {
|
|
w.fileGuard.Lock(p)
|
|
err = os.Remove(p)
|
|
w.fileGuard.Unlock(p)
|
|
if err == nil {
|
|
w.fileCounter.Dec()
|
|
}
|
|
} else {
|
|
err = os.Remove(p)
|
|
}
|
|
|
|
if err != nil && os.IsNotExist(err) {
|
|
err = logicerr.Wrap(new(apistatus.ObjectNotFound))
|
|
}
|
|
return err
|
|
}
|