1
1
//! file storage for passwords.
2
2
use anyhow:: Result ;
3
3
use fslock:: LockFile ;
4
- use once_cell:: sync:: Lazy ;
5
- use std:: collections:: { BTreeMap , HashSet } ;
4
+ use std:: collections:: BTreeMap ;
5
+ use std:: path:: Path ;
6
+ use std:: sync:: Arc ;
6
7
use std:: { path:: PathBuf , sync:: Mutex } ;
7
8
8
9
use crate :: authentication_storage:: StorageBackend ;
9
10
use crate :: Authentication ;
10
11
12
+ #[ derive( Clone , Debug ) ]
13
+ struct FileStorageCache {
14
+ cache : BTreeMap < String , Authentication > ,
15
+ file_exists : bool ,
16
+ }
17
+
11
18
/// A struct that implements storage and access of authentication
12
19
/// information backed by a on-disk JSON file
13
20
#[ derive( Clone , Debug ) ]
14
21
pub struct FileStorage {
15
22
/// The path to the JSON file
16
23
pub path : PathBuf ,
24
+
25
+ /// The cache of the file storage
26
+ /// This is used to avoid reading the file from disk every time
27
+ /// a credential is accessed
28
+ cache : Arc < Mutex < FileStorageCache > > ,
17
29
}
18
30
19
31
/// An error that can occur when accessing the file storage
@@ -32,83 +44,103 @@ pub enum FileStorageError {
32
44
JSONError ( #[ from] serde_json:: Error ) ,
33
45
}
34
46
35
- impl FileStorage {
36
- /// Create a new file storage with the given path
37
- pub fn new ( path : PathBuf ) -> Self {
38
- Self { path }
47
+ /// Lock the file storage file for reading and writing. This will block until the lock is
48
+ /// acquired.
49
+ fn lock_file_storage ( path : & Path , write : bool ) -> Result < Option < LockFile > , FileStorageError > {
50
+ if !write && !path. exists ( ) {
51
+ return Ok ( None ) ;
39
52
}
40
53
41
- /// Lock the file storage file for reading and writing. This will block until the lock is
42
- /// acquired.
43
- fn lock ( & self ) -> Result < LockFile , FileStorageError > {
44
- std:: fs:: create_dir_all ( self . path . parent ( ) . unwrap ( ) ) ?;
45
- let path = self . path . with_extension ( "lock" ) ;
46
- let mut lock = fslock:: LockFile :: open ( & path)
54
+ std:: fs:: create_dir_all ( path. parent ( ) . unwrap ( ) ) ?;
55
+ let path = path. with_extension ( "lock" ) ;
56
+ let mut lock = fslock:: LockFile :: open ( & path)
57
+ . map_err ( |e| FileStorageError :: FailedToLock ( path. to_string_lossy ( ) . into_owned ( ) , e) ) ?;
58
+
59
+ // First try to lock the file without block. If we can't immediately get the lock we block and issue a debug message.
60
+ if !lock
61
+ . try_lock_with_pid ( )
62
+ . map_err ( |e| FileStorageError :: FailedToLock ( path. to_string_lossy ( ) . into_owned ( ) , e) ) ?
63
+ {
64
+ tracing:: debug!( "waiting for lock on {}" , path. to_string_lossy( ) ) ;
65
+ lock. lock_with_pid ( )
47
66
. map_err ( |e| FileStorageError :: FailedToLock ( path. to_string_lossy ( ) . into_owned ( ) , e) ) ?;
67
+ }
48
68
49
- // First try to lock the file without block. If we can't immediately get the lock we block and issue a debug message.
50
- if !lock
51
- . try_lock_with_pid ( )
52
- . map_err ( |e| FileStorageError :: FailedToLock ( path. to_string_lossy ( ) . into_owned ( ) , e) ) ?
53
- {
54
- tracing:: debug!( "waiting for lock on {}" , path. to_string_lossy( ) ) ;
55
- lock. lock_with_pid ( ) . map_err ( |e| {
56
- FileStorageError :: FailedToLock ( path. to_string_lossy ( ) . into_owned ( ) , e)
57
- } ) ?;
58
- }
69
+ Ok ( Some ( lock) )
70
+ }
59
71
60
- Ok ( lock)
72
+ impl FileStorageCache {
73
+ pub fn from_path ( path : & Path ) -> Result < Self , FileStorageError > {
74
+ let file_exists = path. exists ( ) ;
75
+ let cache = if file_exists {
76
+ lock_file_storage ( path, false ) ?;
77
+ let file = std:: fs:: File :: open ( path) ?;
78
+ let reader = std:: io:: BufReader :: new ( file) ;
79
+ serde_json:: from_reader ( reader) ?
80
+ } else {
81
+ BTreeMap :: new ( )
82
+ } ;
83
+
84
+ Ok ( Self { cache, file_exists } )
85
+ }
86
+ }
87
+
88
+ impl FileStorage {
89
+ /// Create a new file storage with the given path
90
+ pub fn new ( path : PathBuf ) -> Result < Self , FileStorageError > {
91
+ // read the JSON file if it exists, and store it in the cache
92
+ let cache = Arc :: new ( Mutex :: new ( FileStorageCache :: from_path ( & path) ?) ) ;
93
+
94
+ Ok ( Self { path, cache } )
61
95
}
62
96
63
97
/// Read the JSON file and deserialize it into a `BTreeMap`, or return an empty `BTreeMap` if the
64
98
/// file does not exist
65
99
fn read_json ( & self ) -> Result < BTreeMap < String , Authentication > , FileStorageError > {
66
- if !self . path . exists ( ) {
67
- static WARN_GUARD : Lazy < Mutex < HashSet < PathBuf > > > =
68
- Lazy :: new ( || Mutex :: new ( HashSet :: new ( ) ) ) ;
69
- let mut guard = WARN_GUARD . lock ( ) . unwrap ( ) ;
70
- if !guard. insert ( self . path . clone ( ) ) {
71
- tracing:: warn!(
72
- "Can't find path for file storage on {}" ,
73
- self . path. to_string_lossy( )
74
- ) ;
75
- }
76
- return Ok ( BTreeMap :: new ( ) ) ;
77
- }
78
- let file = std:: fs:: File :: open ( & self . path ) ?;
79
- let reader = std:: io:: BufReader :: new ( file) ;
80
- let dict = serde_json:: from_reader ( reader) ?;
81
- Ok ( dict)
100
+ let new_cache = FileStorageCache :: from_path ( & self . path ) ?;
101
+ let mut cache = self . cache . lock ( ) . unwrap ( ) ;
102
+ cache. cache = new_cache. cache ;
103
+ cache. file_exists = new_cache. file_exists ;
104
+
105
+ Ok ( cache. cache . clone ( ) )
82
106
}
83
107
84
108
/// Serialize the given `BTreeMap` and write it to the JSON file
85
109
fn write_json ( & self , dict : & BTreeMap < String , Authentication > ) -> Result < ( ) , FileStorageError > {
110
+ let _lock = lock_file_storage ( & self . path , true ) ?;
111
+
86
112
let file = std:: fs:: File :: create ( & self . path ) ?;
87
113
let writer = std:: io:: BufWriter :: new ( file) ;
88
114
serde_json:: to_writer ( writer, dict) ?;
115
+
116
+ // Store the new data in the cache
117
+ let mut cache = self . cache . lock ( ) . unwrap ( ) ;
118
+ cache. cache = dict. clone ( ) ;
119
+ cache. file_exists = true ;
120
+
89
121
Ok ( ( ) )
90
122
}
91
123
}
92
124
93
125
impl StorageBackend for FileStorage {
94
126
fn store ( & self , host : & str , authentication : & crate :: Authentication ) -> Result < ( ) > {
95
- let _lock = self . lock ( ) ?;
96
127
let mut dict = self . read_json ( ) ?;
97
128
dict. insert ( host. to_string ( ) , authentication. clone ( ) ) ;
98
129
Ok ( self . write_json ( & dict) ?)
99
130
}
100
131
101
132
fn get ( & self , host : & str ) -> Result < Option < crate :: Authentication > > {
102
- let _lock = self . lock ( ) ?;
103
- let dict = self . read_json ( ) ?;
104
- Ok ( dict. get ( host) . cloned ( ) )
133
+ let cache = self . cache . lock ( ) . unwrap ( ) ;
134
+ Ok ( cache. cache . get ( host) . cloned ( ) )
105
135
}
106
136
107
137
fn delete ( & self , host : & str ) -> Result < ( ) > {
108
- let _lock = self . lock ( ) ?;
109
138
let mut dict = self . read_json ( ) ?;
110
- dict. remove ( host) ;
111
- Ok ( self . write_json ( & dict) ?)
139
+ if dict. remove ( host) . is_some ( ) {
140
+ Ok ( self . write_json ( & dict) ?)
141
+ } else {
142
+ Ok ( ( ) )
143
+ }
112
144
}
113
145
}
114
146
@@ -117,7 +149,13 @@ impl Default for FileStorage {
117
149
let mut path = dirs:: home_dir ( ) . unwrap ( ) ;
118
150
path. push ( ".rattler" ) ;
119
151
path. push ( "credentials.json" ) ;
120
- Self { path }
152
+ Self :: new ( path. clone ( ) ) . unwrap_or ( Self {
153
+ path,
154
+ cache : Arc :: new ( Mutex :: new ( FileStorageCache {
155
+ cache : BTreeMap :: new ( ) ,
156
+ file_exists : false ,
157
+ } ) ) ,
158
+ } )
121
159
}
122
160
}
123
161
@@ -133,7 +171,7 @@ mod tests {
133
171
let file = tempdir ( ) . unwrap ( ) ;
134
172
let path = file. path ( ) . join ( "test.json" ) ;
135
173
136
- let storage = FileStorage :: new ( path. clone ( ) ) ;
174
+ let storage = FileStorage :: new ( path. clone ( ) ) . unwrap ( ) ;
137
175
138
176
assert_eq ! ( storage. get( "test" ) . unwrap( ) , None ) ;
139
177
@@ -168,6 +206,7 @@ mod tests {
168
206
169
207
let mut file = std:: fs:: File :: create ( & path) . unwrap ( ) ;
170
208
file. write_all ( b"invalid json" ) . unwrap ( ) ;
171
- assert ! ( storage. get( "test" ) . is_err( ) ) ;
209
+
210
+ assert ! ( FileStorage :: new( path. clone( ) ) . is_err( ) ) ;
172
211
}
173
212
}
0 commit comments