Bump reva

This commit is contained in:
André Duffeck
2026-03-12 08:05:50 +01:00
parent cd0831aa10
commit 44549379ca
86 changed files with 2741 additions and 12188 deletions
+11 -1
View File
@@ -13,7 +13,6 @@
// limitations under the License.
//go:build !windows
// +build !windows
package renameio
@@ -86,3 +85,14 @@ func WithReplaceOnClose() Option {
c.renameOnClose = true
})
}
// WithRoot specifies a root directory to use when working with files.
// See [os.Root] and https://go.dev/blog/osroot for more details.
//
// When WithRoot is used, WithTempDir (and the $TMPDIR environment variable) are
// ignored, as temporary files must be created in the specified root directory.
func WithRoot(root *os.Root) Option {
return optionFunc(func(c *config) {
c.root = root
})
}
+114 -16
View File
@@ -13,13 +13,11 @@
// limitations under the License.
//go:build !windows
// +build !windows
package renameio
import (
"io/ioutil"
"math/rand"
"math/rand/v2"
"os"
"path/filepath"
"strconv"
@@ -29,10 +27,10 @@ import (
const defaultPerm os.FileMode = 0o600
// nextrandom is a function generating a random number.
var nextrandom = rand.Int63
var nextrandom = rand.Int64
// openTempFile creates a randomly named file and returns an open handle. It is
// similar to ioutil.TempFile except that the directory must be given, the file
// similar to os.CreateTemp except that the directory must be given, the file
// permissions can be controlled and patterns in the name are not supported.
// The name is always suffixed with a random number.
func openTempFile(dir, name string, perm os.FileMode) (*os.File, error) {
@@ -58,6 +56,33 @@ func openTempFile(dir, name string, perm os.FileMode) (*os.File, error) {
}
}
// openTempFileRoot creates a randomly named file in root and returns an open
// handle. It is similar to os.CreateTemp except that the directory must be
// given, the file permissions can be controlled and patterns in the name are
// not supported. The name is always suffixed with a random number.
func openTempFileRoot(root *os.Root, name string, perm os.FileMode) (string, *os.File, error) {
prefix := name
for attempt := 0; ; {
// Generate a reasonably random name which is unlikely to already
// exist. O_EXCL ensures that existing files generate an error.
name := prefix + strconv.FormatInt(nextrandom(), 10)
f, err := root.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm)
if !os.IsExist(err) {
return name, f, err
}
if attempt++; attempt > 10000 {
return "", nil, &os.PathError{
Op: "tempfile",
Path: name,
Err: os.ErrExist,
}
}
}
}
// TempDir checks whether os.TempDir() can be used as a temporary directory for
// later atomically replacing files within dest. If no (os.TempDir() resides on
// a different mount point), dest is returned.
@@ -83,7 +108,7 @@ func tempDir(dir, dest string) string {
// the TMPDIR environment variable.
tmpdir := os.TempDir()
testsrc, err := ioutil.TempFile(tmpdir, "."+filepath.Base(dest))
testsrc, err := os.CreateTemp(tmpdir, "."+filepath.Base(dest))
if err != nil {
return fallback
}
@@ -95,7 +120,7 @@ func tempDir(dir, dest string) string {
}()
testsrc.Close()
testdest, err := ioutil.TempFile(filepath.Dir(dest), "."+filepath.Base(dest))
testdest, err := os.CreateTemp(filepath.Dir(dest), "."+filepath.Base(dest))
if err != nil {
return fallback
}
@@ -118,6 +143,8 @@ type PendingFile struct {
done bool
closed bool
replaceOnClose bool
root *os.Root
tmpname string
}
// Cleanup is a no-op if CloseAtomicallyReplace succeeded, and otherwise closes
@@ -134,8 +161,14 @@ func (t *PendingFile) Cleanup() error {
if !t.closed {
closeErr = t.File.Close()
}
if err := os.Remove(t.Name()); err != nil {
return err
if t.root != nil {
if err := t.root.Remove(t.tmpname); err != nil {
return err
}
} else {
if err := os.Remove(t.Name()); err != nil {
return err
}
}
t.done = true
return closeErr
@@ -163,8 +196,14 @@ func (t *PendingFile) CloseAtomicallyReplace() error {
if err := t.File.Close(); err != nil {
return err
}
if err := os.Rename(t.Name(), t.path); err != nil {
return err
if t.root != nil {
if err := t.root.Rename(t.tmpname, t.path); err != nil {
return err
}
} else {
if err := os.Rename(t.Name(), t.path); err != nil {
return err
}
}
t.done = true
return nil
@@ -200,6 +239,7 @@ type config struct {
ignoreUmask bool
chmod *os.FileMode
renameOnClose bool
root *os.Root
}
// NewPendingFile creates a temporary file destined to atomically creating or
@@ -227,8 +267,15 @@ func NewPendingFile(path string, opts ...Option) (*PendingFile, error) {
}
if cfg.attemptPermCopy {
var existing os.FileInfo
var err error
if cfg.root != nil {
existing, err = cfg.root.Lstat(cfg.path)
} else {
existing, err = os.Lstat(cfg.path)
}
// Try to determine permissions from an existing file.
if existing, err := os.Lstat(cfg.path); err == nil && existing.Mode().IsRegular() {
if err == nil && existing.Mode().IsRegular() {
perm := existing.Mode() & os.ModePerm
cfg.chmod = &perm
@@ -240,7 +287,14 @@ func NewPendingFile(path string, opts ...Option) (*PendingFile, error) {
}
}
f, err := openTempFile(tempDir(cfg.dir, cfg.path), "."+filepath.Base(cfg.path), cfg.createPerm)
var f *os.File
var err error
var tmpname string
if cfg.root != nil {
tmpname, f, err = openTempFileRoot(cfg.root, "."+filepath.Base(cfg.path), cfg.createPerm)
} else {
f, err = openTempFile(tempDir(cfg.dir, cfg.path), "."+filepath.Base(cfg.path), cfg.createPerm)
}
if err != nil {
return nil, err
}
@@ -255,7 +309,13 @@ func NewPendingFile(path string, opts ...Option) (*PendingFile, error) {
}
}
return &PendingFile{File: f, path: cfg.path, replaceOnClose: cfg.renameOnClose}, nil
return &PendingFile{
File: f,
path: cfg.path,
replaceOnClose: cfg.renameOnClose,
root: cfg.root,
tmpname: tmpname,
}, nil
}
// Symlink wraps os.Symlink, replacing an existing symlink with the same name
@@ -267,9 +327,9 @@ func Symlink(oldname, newname string) error {
return err
}
// We need to use ioutil.TempDir, as we cannot overwrite a ioutil.TempFile,
// We need to use os.MkdirTemp, as we cannot overwrite a os.CreateTemp file,
// and removing+symlinking creates a TOCTOU race.
d, err := ioutil.TempDir(filepath.Dir(newname), "."+filepath.Base(newname))
d, err := os.MkdirTemp(filepath.Dir(newname), "."+filepath.Base(newname))
if err != nil {
return err
}
@@ -292,3 +352,41 @@ func Symlink(oldname, newname string) error {
cleanup = false
return os.RemoveAll(d)
}
// SymlinkRoot wraps os.Symlink, replacing an existing symlink with the same
// name atomically (os.Symlink fails when newname already exists, at least on
// Linux).
func SymlinkRoot(root *os.Root, oldname, newname string) error {
// Fast path: if newname does not exist yet, we can skip the whole dance
// below.
if err := root.Symlink(oldname, newname); err == nil || !os.IsExist(err) {
return err
}
// We need to use os.MkdirTemp, as we cannot overwrite a os.CreateTemp file,
// and removing+symlinking creates a TOCTOU race.
//
// There is no os.Root-compatible os.MkdirTemp, so we use the path directly.
d, err := os.MkdirTemp(root.Name(), "."+filepath.Base(newname))
if err != nil {
return err
}
cleanup := true
defer func() {
if cleanup {
os.RemoveAll(d)
}
}()
symlink := filepath.Join(filepath.Base(d), "tmp.symlink")
if err := root.Symlink(oldname, symlink); err != nil {
return err
}
if err := root.Rename(symlink, newname); err != nil {
return err
}
cleanup = false
return os.RemoveAll(d)
}
+1 -2
View File
@@ -13,13 +13,12 @@
// limitations under the License.
//go:build !windows
// +build !windows
package renameio
import "os"
// WriteFile mirrors ioutil.WriteFile, replacing an existing file with the same
// WriteFile mirrors os.WriteFile, replacing an existing file with the same
// name atomically.
func WriteFile(filename string, data []byte, perm os.FileMode, opts ...Option) error {
opts = append([]Option{