11package keystore
22
33import (
4+ "bytes"
45 "context"
6+ "fmt"
7+ "io"
58 "os"
9+ "path/filepath"
610)
711
8- const readWritePerms = os .FileMode (0600 )
9-
1012var _ Storage = & FileStorage {}
1113
1214// FileStorage implements Storage using a file
@@ -25,5 +27,90 @@ func (f *FileStorage) GetEncryptedKeystore(ctx context.Context) ([]byte, error)
2527}
2628
2729func (f * FileStorage ) PutEncryptedKeystore (ctx context.Context , encryptedKeystore []byte ) error {
28- return os .WriteFile (f .name , encryptedKeystore , readWritePerms )
30+ return writeFile (f .name , bytes .NewReader (encryptedKeystore ))
31+ }
32+
33+ // The atomic write function below is from https://github.com/natefinch/atomic/blob/master/atomic.go under license:
34+ // The MIT License (MIT)
35+ //
36+ // # Copyright (c) 2015 Nate Finch
37+ //
38+ // Permission is hereby granted, free of charge, to any person obtaining a copy
39+ // of this software and associated documentation files (the "Software"), to deal
40+ // in the Software without restriction, including without limitation the rights
41+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
42+ // copies of the Software, and to permit persons to whom the Software is
43+ // furnished to do so, subject to the following conditions:
44+ //
45+ // The above copyright notice and this permission notice shall be included in all
46+ // copies or substantial portions of the Software.
47+ //
48+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
49+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
50+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
51+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
52+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
53+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
54+ // SOFTWARE.
55+ //
56+ // writeFile atomically writes the contents of r to the specified filepath. If
57+ // an error occurs, the target file is guaranteed to be either fully written, or
58+ // not written at all. writeFile overwrites any file that exists at the
59+ // location (but only if the write fully succeeds, otherwise the existing file
60+ // is unmodified).
61+ func writeFile (filename string , r io.Reader ) (err error ) {
62+ // write to a temp file first, then we'll atomically replace the target file
63+ // with the temp file.
64+ dir , file := filepath .Split (filename )
65+ if dir == "" {
66+ dir = "."
67+ }
68+
69+ f , err := os .CreateTemp (dir , file )
70+ if err != nil {
71+ return fmt .Errorf ("cannot create temp file: %v" , err )
72+ }
73+ defer func () {
74+ if err != nil {
75+ // Don't leave the temp file lying around on error.
76+ _ = os .Remove (f .Name ()) // yes, ignore the error, not much we can do about it.
77+ }
78+ }()
79+ // ensure we always close f. Note that this does not conflict with the
80+ // close below, as close is idempotent.
81+ defer f .Close ()
82+ name := f .Name ()
83+ if _ , err := io .Copy (f , r ); err != nil {
84+ return fmt .Errorf ("cannot write data to tempfile %q: %v" , name , err )
85+ }
86+ // fsync is important, otherwise os.Rename could rename a zero-length file
87+ if err := f .Sync (); err != nil {
88+ return fmt .Errorf ("can't flush tempfile %q: %v" , name , err )
89+ }
90+ if err := f .Close (); err != nil {
91+ return fmt .Errorf ("can't close tempfile %q: %v" , name , err )
92+ }
93+
94+ // get the file mode from the original file and use that for the replacement
95+ // file, too.
96+ destInfo , err := os .Stat (filename )
97+ if os .IsNotExist (err ) {
98+ // no original file
99+ } else if err != nil {
100+ return err
101+ } else {
102+ sourceInfo , err := os .Stat (name )
103+ if err != nil {
104+ return err
105+ }
106+ if sourceInfo .Mode () != destInfo .Mode () {
107+ if err := os .Chmod (name , destInfo .Mode ()); err != nil {
108+ return fmt .Errorf ("can't set filemode on tempfile %q: %v" , name , err )
109+ }
110+ }
111+ }
112+ if err := os .Rename (name , filename ); err != nil {
113+ return fmt .Errorf ("cannot replace %q with tempfile %q: %v" , filename , name , err )
114+ }
115+ return nil
29116}
0 commit comments