Files
kopia/repo/content/write_temp_file.go
Julio López e1dce8a049 fix(repository): sync index blob file before closing (#5292)
- Sync index blob file before closing
- Ensure file is closed even when there are errors
- Remove temp file on error
- Make writeTempFileAtomic testable
- Test writeTempFile atomic
2026-04-09 10:26:02 -07:00

87 lines
2.1 KiB
Go

package content
import (
stderrors "errors"
"io"
"io/fs"
"os"
"github.com/pkg/errors"
"github.com/kopia/kopia/internal/cache"
)
type file interface {
io.WriteCloser
Name() string
Sync() error
}
type fsInterface interface {
CreateTemp(dir, pattern string) (file, error)
Remove(name string) error
MkdirAll(path string, perm fs.FileMode) error
}
type localFS struct{}
func (l localFS) CreateTemp(dir, pattern string) (file, error) {
return os.CreateTemp(dir, pattern) //nolint:wrapcheck
}
func (l localFS) Remove(name string) error {
return os.Remove(name) //nolint:wrapcheck
}
func (l localFS) MkdirAll(dirPath string, perm fs.FileMode) error {
return os.MkdirAll(dirPath, perm) //nolint:wrapcheck
}
func writeTempFileAtomic(dirname string, data []byte) (filename string, err error) {
return writeTempFileAtomicImp(localFS{}, dirname, data)
}
func writeTempFileAtomicImp(fsi fsInterface, dirname string, data []byte) (filename string, err error) {
// write to a temp file to avoid race where two processes are writing at the same time.
tf, err2 := fsi.CreateTemp(dirname, "tmp")
if err2 != nil {
if os.IsNotExist(err2) {
if mdErr := fsi.MkdirAll(dirname, cache.DirMode); mdErr != nil {
return "", stderrors.Join(errors.Wrap(mdErr, "cannot create parent directory for temp file"),
errors.Wrap(err2, "cannot create temp file"))
}
tf, err2 = fsi.CreateTemp(dirname, "tmp")
}
}
if err2 != nil {
return "", errors.Wrap(err2, "can't create tmp file")
}
defer func() {
if cerr := tf.Close(); cerr != nil {
err = stderrors.Join(err, errors.Wrap(cerr, "can't close tmp file"))
}
if err != nil {
// remove tmp file on error to avoid leaving them behind
if rerr := fsi.Remove(tf.Name()); rerr != nil {
err = stderrors.Join(err, errors.Wrap(rerr, "can't remove tmp file"))
}
filename = ""
}
}()
if _, err2 := tf.Write(data); err2 != nil {
return "", errors.Wrap(err2, "can't write to temp file")
}
if err2 := tf.Sync(); err2 != nil {
return "", errors.Wrapf(err2, "cannot sync temporary file in dir %s", dirname)
}
return tf.Name(), nil
}