mirror of
https://github.com/kopia/kopia.git
synced 2026-05-10 15:54:20 -04:00
- 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
87 lines
2.1 KiB
Go
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
|
|
}
|