github.com/kevinklinger/open_terraform@v1.3.6/noninternal/replacefile/writefile.go (about) 1 package replacefile 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 ) 9 10 // AtomicWriteFile uses a temporary file along with this package's AtomicRename 11 // function in order to provide a replacement for ioutil.WriteFile that 12 // writes the given file into place as atomically as the underlying operating 13 // system can support. 14 // 15 // The sense of "atomic" meant by this function is that the file at the 16 // given filename will either contain the entirety of the previous contents 17 // or the entirety of the given data array if opened and read at any point 18 // during the execution of the function. 19 // 20 // On some platforms attempting to overwrite a file that has at least one 21 // open filehandle will produce an error. On other platforms, the overwriting 22 // will succeed but existing open handles will still refer to the old file, 23 // even though its directory entry is no longer present. 24 // 25 // Although AtomicWriteFile tries its best to avoid leaving behind its 26 // temporary file on error, some particularly messy error cases may result 27 // in a leftover temporary file. 28 func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error { 29 dir, file := filepath.Split(filename) 30 if dir == "" { 31 // If the file is in the current working directory then dir will 32 // end up being "", but that's not right here because TempFile 33 // treats an empty dir as meaning "use the TMPDIR environment variable". 34 dir = "." 35 } 36 f, err := ioutil.TempFile(dir, file) // alongside target file and with a similar name 37 if err != nil { 38 return fmt.Errorf("cannot create temporary file to update %s: %s", filename, err) 39 } 40 tmpName := f.Name() 41 moved := false 42 defer func(f *os.File, name string) { 43 // Remove the temporary file if it hasn't been moved yet. We're 44 // ignoring errors here because there's nothing we can do about 45 // them anyway. 46 if !moved { 47 os.Remove(name) 48 } 49 }(f, tmpName) 50 51 // We'll try to apply the requested permissions. This may 52 // not be effective on all platforms, but should at least work on 53 // Unix-like targets and should be harmless elsewhere. 54 if err := os.Chmod(tmpName, perm); err != nil { 55 return fmt.Errorf("cannot set mode for temporary file %s: %s", tmpName, err) 56 } 57 58 // Write the credentials to the temporary file, then immediately close 59 // it, whether or not the write succeeds. Note that closing the file here 60 // is required because on Windows we can't move a file while it's open. 61 _, err = f.Write(data) 62 f.Close() 63 if err != nil { 64 return fmt.Errorf("cannot write to temporary file %s: %s", tmpName, err) 65 } 66 67 // Temporary file now replaces the original file, as atomically as 68 // possible. (At the very least, we should not end up with a file 69 // containing only a partial JSON object.) 70 err = AtomicRename(tmpName, filename) 71 if err != nil { 72 return fmt.Errorf("failed to replace %s with temporary file %s: %s", filename, tmpName, err) 73 } 74 75 moved = true 76 return nil 77 }