mirror of
https://github.com/hanzoai/traces.git
synced 2026-10-30 18:20:41 +00:00
104 lines
2.8 KiB
Go
104 lines
2.8 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only
|
|
// Copied from github.com/grafana/mimir/pkg/util/atomicfs/fsync.go
|
|
// at commit 9657d59b990e51693f0c48a5bc79f2331a78bb83
|
|
|
|
package atomicfs
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/grafana/dskit/multierror"
|
|
)
|
|
|
|
// Create creates a new file at a temporary path that will be renamed to the
|
|
// supplied path on close from a temporary file in the same directory, ensuring
|
|
// all data and the containing directory have been fsynced to disk.
|
|
func Create(path string) (*File, error) {
|
|
// We rename from a temporary file in the same directory to because rename
|
|
// can only operate on two files that are on the same filesystem. Creating
|
|
// a temporary file in the same directory is an easy way to guarantee that.
|
|
final := filepath.Clean(path)
|
|
tmp := tempPath(final)
|
|
|
|
file, err := os.Create(tmp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &File{
|
|
File: file,
|
|
finalPath: final,
|
|
}, nil
|
|
}
|
|
|
|
// tempPath returns a path for the temporary version of a file. This function exists
|
|
// to ensure the logic here stays in sync with unit tests that check for this file being
|
|
// cleaned up.
|
|
func tempPath(final string) string {
|
|
return final + ".tmp"
|
|
}
|
|
|
|
// File is a wrapper around an os.File instance that uses a temporary file for writes
|
|
// that is renamed to its final path when Close is called. The Close method will also
|
|
// ensure that all data from the file has been fsynced as well as the containing
|
|
// directory. If the temporary file cannot be renamed or fsynced on Close, it is
|
|
// removed.
|
|
type File struct {
|
|
*os.File
|
|
finalPath string
|
|
}
|
|
|
|
func (a *File) Close() error {
|
|
cleanup := true
|
|
defer func() {
|
|
if cleanup {
|
|
_ = os.Remove(a.Name())
|
|
}
|
|
}()
|
|
|
|
merr := multierror.New()
|
|
merr.Add(a.Sync())
|
|
merr.Add(a.File.Close())
|
|
if err := merr.Err(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := os.Rename(a.Name(), a.finalPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
cleanup = false
|
|
// After writing the file and calling fsync on it, fsync the containing directory
|
|
// to ensure the directory entry is persisted to disk.
|
|
//
|
|
// From https://man7.org/linux/man-pages/man2/fsync.2.html
|
|
// > Calling fsync() does not necessarily ensure that the entry in the
|
|
// > directory containing the file has also reached disk. For that an
|
|
// > explicit fsync() on a file descriptor for the directory is also
|
|
// > needed.
|
|
dir, err := os.Open(filepath.Dir(a.finalPath))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
merr.Add(dir.Sync())
|
|
merr.Add(dir.Close())
|
|
return merr.Err()
|
|
}
|
|
|
|
// CreateFile safely writes the contents of data to filePath, ensuring that all data
|
|
// has been fsynced as well as the containing directory of the file.
|
|
func CreateFile(filePath string, data io.Reader) error {
|
|
f, err := Create(filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = io.Copy(f, data)
|
|
merr := multierror.New(err)
|
|
merr.Add(f.Close())
|
|
return merr.Err()
|
|
}
|