mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-24 15:49:06 +02:00
* Use POSIX-like file read/write of json for windows + tests: allow renaming an open file.
80 lines
2.5 KiB
Go
80 lines
2.5 KiB
Go
package util
|
|
|
|
import (
|
|
"errors"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// openRead opens path for reading without holding it against a rename.
|
|
//
|
|
// os.Open does not set FILE_SHARE_DELETE on Windows, so you cannot rename an
|
|
// open file like on UNIX. This caused concurrency issues with active state
|
|
// config file.
|
|
//
|
|
// os.Root opens through NtCreateFile with delete sharing, which is the
|
|
// behaviour Unix has.
|
|
// https://cs.opensource.google/go/go/+/refs/tags/go1.27.1:src/os/root_windows.go;drc=a4f5d9bbdbdf42da7e2d7e976ac85753c4db5d75;l=176
|
|
func openRead(path string) (*os.File, error) {
|
|
root, err := os.OpenRoot(filepath.Dir(path))
|
|
if err != nil {
|
|
// Names the file the caller asked for, not the directory the root
|
|
// failed on, so a missing directory reads like a missing file.
|
|
return nil, pathError("open", path, err)
|
|
}
|
|
defer func() { _ = root.Close() }()
|
|
|
|
// The file outlives the root: closing a Root closes the directory handle it
|
|
// holds, not the files opened through it.
|
|
f, err := root.Open(filepath.Base(path))
|
|
if err != nil {
|
|
return nil, pathError("open", path, err)
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
// renameFile replaces newpath with oldpath, including while something holds
|
|
// newpath open for reading.
|
|
//
|
|
// os.Root.Rename asks for POSIX semantics, which unlink the destination
|
|
// immediately and leave open handles reading the version they opened.
|
|
// https://cs.opensource.google/go/go/+/master:src/internal/syscall/windows/at_windows.go;drc=a4f5d9bbdbdf42da7e2d7e976ac85753c4db5d75;l=384
|
|
func renameFile(oldpath, newpath string) error {
|
|
dir := filepath.Dir(newpath)
|
|
if filepath.Dir(oldpath) != dir {
|
|
return os.Rename(oldpath, newpath)
|
|
}
|
|
|
|
root, err := os.OpenRoot(dir)
|
|
if err != nil {
|
|
return os.Rename(oldpath, newpath)
|
|
}
|
|
defer func() { _ = root.Close() }()
|
|
|
|
if err := root.Rename(filepath.Base(oldpath), filepath.Base(newpath)); err != nil {
|
|
return linkError("rename", oldpath, newpath, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// pathError restores the full path on an error from a root, which names the
|
|
// file by the base name it was opened with.
|
|
func pathError(op, path string, err error) error {
|
|
var perr *fs.PathError
|
|
if errors.As(err, &perr) {
|
|
err = perr.Err
|
|
}
|
|
return &fs.PathError{Op: op, Path: path, Err: err}
|
|
}
|
|
|
|
// linkError does the same as pathError for a rename, which reports both files
|
|
// by their base names.
|
|
func linkError(op, oldpath, newpath string, err error) error {
|
|
var lerr *os.LinkError
|
|
if errors.As(err, &lerr) {
|
|
err = lerr.Err
|
|
}
|
|
return &os.LinkError{Op: op, Old: oldpath, New: newpath, Err: err}
|
|
}
|