Ctrl K
ComunidadGuías…

util/file_windows.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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}
}