Source file src/cmd/go/internal/renameio/renameio.go
Documentation: cmd/go/internal/renameio
1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package renameio writes files atomically by renaming temporary files. 6 package renameio 7 8 import ( 9 "bytes" 10 "io" 11 "io/fs" 12 "math/rand" 13 "os" 14 "path/filepath" 15 "strconv" 16 17 "cmd/go/internal/robustio" 18 ) 19 20 const patternSuffix = ".tmp" 21 22 // Pattern returns a glob pattern that matches the unrenamed temporary files 23 // created when writing to filename. 24 func Pattern(filename string) string { 25 return filepath.Join(filepath.Dir(filename), filepath.Base(filename)+patternSuffix) 26 } 27 28 // WriteFile is like os.WriteFile, but first writes data to an arbitrary 29 // file in the same directory as filename, then renames it atomically to the 30 // final name. 31 // 32 // That ensures that the final location, if it exists, is always a complete file. 33 func WriteFile(filename string, data []byte, perm fs.FileMode) (err error) { 34 return WriteToFile(filename, bytes.NewReader(data), perm) 35 } 36 37 // WriteToFile is a variant of WriteFile that accepts the data as an io.Reader 38 // instead of a slice. 39 func WriteToFile(filename string, data io.Reader, perm fs.FileMode) (err error) { 40 f, err := tempFile(filepath.Dir(filename), filepath.Base(filename), perm) 41 if err != nil { 42 return err 43 } 44 defer func() { 45 // Only call os.Remove on f.Name() if we failed to rename it: otherwise, 46 // some other process may have created a new file with the same name after 47 // that. 48 if err != nil { 49 f.Close() 50 os.Remove(f.Name()) 51 } 52 }() 53 54 if _, err := io.Copy(f, data); err != nil { 55 return err 56 } 57 // Sync the file before renaming it: otherwise, after a crash the reader may 58 // observe a 0-length file instead of the actual contents. 59 // See https://golang.org/issue/22397#issuecomment-380831736. 60 if err := f.Sync(); err != nil { 61 return err 62 } 63 if err := f.Close(); err != nil { 64 return err 65 } 66 67 return robustio.Rename(f.Name(), filename) 68 } 69 70 // ReadFile is like os.ReadFile, but on Windows retries spurious errors that 71 // may occur if the file is concurrently replaced. 72 // 73 // Errors are classified heuristically and retries are bounded, so even this 74 // function may occasionally return a spurious error on Windows. 75 // If so, the error will likely wrap one of: 76 // - syscall.ERROR_ACCESS_DENIED 77 // - syscall.ERROR_FILE_NOT_FOUND 78 // - internal/syscall/windows.ERROR_SHARING_VIOLATION 79 func ReadFile(filename string) ([]byte, error) { 80 return robustio.ReadFile(filename) 81 } 82 83 // tempFile creates a new temporary file with given permission bits. 84 func tempFile(dir, prefix string, perm fs.FileMode) (f *os.File, err error) { 85 for i := 0; i < 10000; i++ { 86 name := filepath.Join(dir, prefix+strconv.Itoa(rand.Intn(1000000000))+patternSuffix) 87 f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm) 88 if os.IsExist(err) { 89 continue 90 } 91 break 92 } 93 return 94 } 95