2015-07-11 15:03:40 +00:00
|
|
|
// Copyright (C) 2015 The Syncthing Authors.
|
|
|
|
//
|
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
2017-02-09 06:52:18 +00:00
|
|
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
2015-07-11 15:03:40 +00:00
|
|
|
|
|
|
|
package osutil
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"path/filepath"
|
2017-08-19 14:36:56 +00:00
|
|
|
|
2022-07-28 17:36:39 +00:00
|
|
|
"github.com/syncthing/syncthing/lib/build"
|
2017-08-19 14:36:56 +00:00
|
|
|
"github.com/syncthing/syncthing/lib/fs"
|
2015-07-11 15:03:40 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrClosed = errors.New("write to closed writer")
|
|
|
|
TempPrefix = ".syncthing.tmp."
|
|
|
|
)
|
|
|
|
|
|
|
|
// An AtomicWriter is an *os.File that writes to a temporary file in the same
|
2016-05-08 10:54:22 +00:00
|
|
|
// directory as the final path. On successful Close the file is renamed to
|
2017-11-04 07:20:11 +00:00
|
|
|
// its final path. Any error on Write or during Close is accumulated and
|
2015-07-11 15:03:40 +00:00
|
|
|
// returned on Close, so a lazy user can ignore errors until Close.
|
|
|
|
type AtomicWriter struct {
|
|
|
|
path string
|
2017-08-19 14:36:56 +00:00
|
|
|
next fs.File
|
|
|
|
fs fs.Filesystem
|
2015-07-11 15:03:40 +00:00
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
2016-11-23 14:06:08 +00:00
|
|
|
// CreateAtomic is like os.Create, except a temporary file name is used
|
|
|
|
// instead of the given name. The file is created with secure (0600)
|
|
|
|
// permissions.
|
|
|
|
func CreateAtomic(path string) (*AtomicWriter, error) {
|
2017-08-19 14:36:56 +00:00
|
|
|
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, filepath.Dir(path))
|
|
|
|
return CreateAtomicFilesystem(fs, filepath.Base(path))
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateAtomicFilesystem is like os.Create, except a temporary file name is used
|
|
|
|
// instead of the given name. The file is created with secure (0600)
|
|
|
|
// permissions.
|
|
|
|
func CreateAtomicFilesystem(filesystem fs.Filesystem, path string) (*AtomicWriter, error) {
|
2016-11-23 14:06:08 +00:00
|
|
|
// The security of this depends on the tempfile having secure
|
2021-11-22 07:59:47 +00:00
|
|
|
// permissions, 0600, from the beginning. This is what os.CreateTemp
|
2016-11-23 14:06:08 +00:00
|
|
|
// does. We have a test that verifies that that is the case, should this
|
|
|
|
// ever change in the standard library in the future.
|
2017-08-19 14:36:56 +00:00
|
|
|
fd, err := TempFile(filesystem, filepath.Dir(path), TempPrefix)
|
2015-07-11 15:03:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
w := &AtomicWriter{
|
|
|
|
path: path,
|
|
|
|
next: fd,
|
2017-08-19 14:36:56 +00:00
|
|
|
fs: filesystem,
|
2015-07-11 15:03:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return w, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write is like io.Writer, but is a no-op on an already failed AtomicWriter.
|
|
|
|
func (w *AtomicWriter) Write(bs []byte) (int, error) {
|
|
|
|
if w.err != nil {
|
|
|
|
return 0, w.err
|
|
|
|
}
|
|
|
|
n, err := w.next.Write(bs)
|
|
|
|
if err != nil {
|
|
|
|
w.err = err
|
|
|
|
w.next.Close()
|
|
|
|
}
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes the temporary file and renames it to the final path. It is
|
|
|
|
// invalid to call Write() or Close() after Close().
|
|
|
|
func (w *AtomicWriter) Close() error {
|
|
|
|
if w.err != nil {
|
|
|
|
return w.err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to not leave temp file around, but ignore error.
|
2019-02-02 11:16:27 +00:00
|
|
|
defer w.fs.Remove(w.next.Name())
|
2015-07-11 15:03:40 +00:00
|
|
|
|
2020-11-14 08:23:27 +00:00
|
|
|
// sync() isn't supported everywhere, our best effort will suffice.
|
|
|
|
_ = w.next.Sync()
|
2016-11-21 17:09:29 +00:00
|
|
|
|
2015-07-11 15:03:40 +00:00
|
|
|
if err := w.next.Close(); err != nil {
|
|
|
|
w.err = err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-12 09:33:56 +00:00
|
|
|
info, infoErr := w.fs.Lstat(w.path)
|
|
|
|
if infoErr != nil && !fs.IsNotExist(infoErr) {
|
|
|
|
w.err = infoErr
|
|
|
|
return infoErr
|
2020-08-10 16:48:37 +00:00
|
|
|
}
|
2020-08-12 09:33:56 +00:00
|
|
|
err := w.fs.Rename(w.next.Name(), w.path)
|
2022-07-28 17:36:39 +00:00
|
|
|
if build.IsWindows && fs.IsPermission(err) {
|
2020-04-07 13:38:55 +00:00
|
|
|
// On Windows, we might not be allowed to rename over the file
|
|
|
|
// because it's read-only. Get us some write permissions and try
|
|
|
|
// again.
|
|
|
|
_ = w.fs.Chmod(w.path, 0644)
|
|
|
|
err = w.fs.Rename(w.next.Name(), w.path)
|
|
|
|
}
|
|
|
|
if err != nil {
|
2015-07-11 15:03:40 +00:00
|
|
|
w.err = err
|
|
|
|
return err
|
|
|
|
}
|
2020-08-12 09:33:56 +00:00
|
|
|
if infoErr == nil {
|
2023-02-20 14:41:10 +00:00
|
|
|
// Restore chmod setting for final file to what it was
|
2020-08-10 16:48:37 +00:00
|
|
|
if err := w.fs.Chmod(w.path, info.Mode()); err != nil {
|
2023-02-20 14:41:10 +00:00
|
|
|
// Only fail if permissions differ, since some filesystems are expected to not allow chmod (e.g. error
|
|
|
|
// `operation not permitted`).
|
|
|
|
infoAfterRename, infoAfterRenameErr := w.fs.Lstat(w.path)
|
|
|
|
if infoAfterRenameErr != nil || infoAfterRename.Mode() != info.Mode() {
|
|
|
|
w.err = err
|
|
|
|
return err
|
|
|
|
}
|
2020-08-10 16:48:37 +00:00
|
|
|
}
|
|
|
|
}
|
2015-07-11 15:03:40 +00:00
|
|
|
|
2017-08-19 14:36:56 +00:00
|
|
|
// fsync the directory too
|
|
|
|
if fd, err := w.fs.Open(filepath.Dir(w.next.Name())); err == nil {
|
2019-02-02 11:16:27 +00:00
|
|
|
fd.Sync()
|
2017-08-19 14:36:56 +00:00
|
|
|
fd.Close()
|
|
|
|
}
|
2016-11-21 17:09:29 +00:00
|
|
|
|
2015-07-11 15:03:40 +00:00
|
|
|
// Set w.err to return appropriately for any future operations.
|
|
|
|
w.err = ErrClosed
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|