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 }