mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-10 15:20:56 +00:00
a3c724f2c3
all: Add package runtimeos for runtime.GOOS comparisons I grew tired of hand written string comparisons. This adds generated constants for the GOOS values, and predefined Is$OS constants that can be iffed on. In a couple of places I rewrote trivial switch:es to if:s, and added Illumos where we checked for Solaris (because they are effectively the same, and if we're going to target one of them that would be Illumos...).
129 lines
3.4 KiB
Go
129 lines
3.4 KiB
Go
// 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 {
|
|
if err := w.fs.Chmod(w.path, info.Mode()); err != nil {
|
|
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
|
|
}
|