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