github.com/fiagdao/tendermint@v0.32.11-0.20220824195748-2087fcc480c1/libs/tempfile/tempfile.go (about) 1 package tempfile 2 3 import ( 4 fmt "fmt" 5 "io" 6 "os" 7 "path/filepath" 8 "strconv" 9 "strings" 10 "sync" 11 "time" 12 ) 13 14 const ( 15 atomicWriteFilePrefix = "write-file-atomic-" 16 // Maximum number of atomic write file conflicts before we start reseeding 17 // (reduced from golang's default 10 due to using an increased randomness space) 18 atomicWriteFileMaxNumConflicts = 5 19 // Maximum number of attempts to make at writing the write file before giving up 20 // (reduced from golang's default 10000 due to using an increased randomness space) 21 atomicWriteFileMaxNumWriteAttempts = 1000 22 // LCG constants from Donald Knuth MMIX 23 // This LCG's has a period equal to 2**64 24 lcgA = 6364136223846793005 25 lcgC = 1442695040888963407 26 // Create in case it doesn't exist and force kernel 27 // flush, which still leaves the potential of lingering disk cache. 28 // Never overwrites files 29 atomicWriteFileFlag = os.O_WRONLY | os.O_CREATE | os.O_SYNC | os.O_TRUNC | os.O_EXCL 30 ) 31 32 var ( 33 atomicWriteFileRand uint64 34 atomicWriteFileRandMu sync.Mutex 35 ) 36 37 func writeFileRandReseed() uint64 { 38 // Scale the PID, to minimize the chance that two processes seeded at similar times 39 // don't get the same seed. Note that PID typically ranges in [0, 2**15), but can be 40 // up to 2**22 under certain configurations. We left bit-shift the PID by 20, so that 41 // a PID difference of one corresponds to a time difference of 2048 seconds. 42 // The important thing here is that now for a seed conflict, they would both have to be on 43 // the correct nanosecond offset, and second-based offset, which is much less likely than 44 // just a conflict with the correct nanosecond offset. 45 return uint64(time.Now().UnixNano() + int64(os.Getpid()<<20)) 46 } 47 48 // Use a fast thread safe LCG for atomic write file names. 49 // Returns a string corresponding to a 64 bit int. 50 // If it was a negative int, the leading number is a 0. 51 func randWriteFileSuffix() string { 52 atomicWriteFileRandMu.Lock() 53 r := atomicWriteFileRand 54 if r == 0 { 55 r = writeFileRandReseed() 56 } 57 58 // Update randomness according to lcg 59 r = r*lcgA + lcgC 60 61 atomicWriteFileRand = r 62 atomicWriteFileRandMu.Unlock() 63 // Can have a negative name, replace this in the following 64 suffix := strconv.Itoa(int(r)) 65 if string(suffix[0]) == "-" { 66 // Replace first "-" with "0". This is purely for UI clarity, 67 // as otherwhise there would be two `-` in a row. 68 suffix = strings.Replace(suffix, "-", "0", 1) 69 } 70 return suffix 71 } 72 73 // WriteFileAtomic creates a temporary file with data and provided perm and 74 // swaps it atomically with filename if successful. 75 func WriteFileAtomic(filename string, data []byte, perm os.FileMode) (err error) { 76 // This implementation is inspired by the golang stdlibs method of creating 77 // tempfiles. Notable differences are that we use different flags, a 64 bit LCG 78 // and handle negatives differently. 79 // The core reason we can't use golang's TempFile is that we must write 80 // to the file synchronously, as we need this to persist to disk. 81 // We also open it in write-only mode, to avoid concerns that arise with read. 82 var ( 83 dir = filepath.Dir(filename) 84 f *os.File 85 ) 86 87 nconflict := 0 88 // Limit the number of attempts to create a file. Something is seriously 89 // wrong if it didn't get created after 1000 attempts, and we don't want 90 // an infinite loop 91 i := 0 92 for ; i < atomicWriteFileMaxNumWriteAttempts; i++ { 93 name := filepath.Join(dir, atomicWriteFilePrefix+randWriteFileSuffix()) 94 f, err = os.OpenFile(name, atomicWriteFileFlag, perm) 95 // If the file already exists, try a new file 96 if os.IsExist(err) { 97 // If the files exists too many times, start reseeding as we've 98 // likely hit another instances seed. 99 if nconflict++; nconflict > atomicWriteFileMaxNumConflicts { 100 atomicWriteFileRandMu.Lock() 101 atomicWriteFileRand = writeFileRandReseed() 102 atomicWriteFileRandMu.Unlock() 103 } 104 continue 105 } else if err != nil { 106 return err 107 } 108 break 109 } 110 if i == atomicWriteFileMaxNumWriteAttempts { 111 return fmt.Errorf("could not create atomic write file after %d attempts", i) 112 } 113 114 // Clean up in any case. Defer stacking order is last-in-first-out. 115 defer os.Remove(f.Name()) 116 defer f.Close() 117 118 if n, err := f.Write(data); err != nil { 119 return err 120 } else if n < len(data) { 121 return io.ErrShortWrite 122 } 123 // Close the file before renaming it, otherwise it will cause "The process 124 // cannot access the file because it is being used by another process." on windows. 125 f.Close() 126 127 return os.Rename(f.Name(), filename) 128 }