123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- // 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,
- // You can obtain one at https://mozilla.org/MPL/2.0/.
- package osutil
- import (
- "errors"
- "path/filepath"
- "github.com/syncthing/syncthing/lib/build"
- "github.com/syncthing/syncthing/lib/fs"
- )
- 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
- // directory as the final path. On successful Close the file is renamed to
- // its final path. Any error on Write or during Close is accumulated and
- // returned on Close, so a lazy user can ignore errors until Close.
- type AtomicWriter struct {
- path string
- next fs.File
- fs fs.Filesystem
- err error
- }
- // 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) {
- 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) {
- // The security of this depends on the tempfile having secure
- // permissions, 0600, from the beginning. This is what os.CreateTemp
- // does. We have a test that verifies that that is the case, should this
- // ever change in the standard library in the future.
- fd, err := TempFile(filesystem, filepath.Dir(path), TempPrefix)
- if err != nil {
- return nil, err
- }
- w := &AtomicWriter{
- path: path,
- next: fd,
- fs: filesystem,
- }
- 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.
- defer w.fs.Remove(w.next.Name())
- // sync() isn't supported everywhere, our best effort will suffice.
- _ = w.next.Sync()
- if err := w.next.Close(); err != nil {
- w.err = err
- return err
- }
- info, infoErr := w.fs.Lstat(w.path)
- if infoErr != nil && !fs.IsNotExist(infoErr) {
- w.err = infoErr
- return infoErr
- }
- err := w.fs.Rename(w.next.Name(), w.path)
- if build.IsWindows && fs.IsPermission(err) {
- // 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 {
- w.err = err
- return err
- }
- if infoErr == nil {
- // Restore chmod setting for final file to what it was
- if err := w.fs.Chmod(w.path, info.Mode()); err != nil {
- // 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
- }
- }
- }
- // fsync the directory too
- if fd, err := w.fs.Open(filepath.Dir(w.next.Name())); err == nil {
- fd.Sync()
- fd.Close()
- }
- // Set w.err to return appropriately for any future operations.
- w.err = ErrClosed
- return nil
- }
|