@@ -73,7 +73,7 @@ use utils::*;
7373
7474use directories:: ProjectDirs ;
7575use serde:: { de:: DeserializeOwned , Serialize } ;
76- use std:: fs:: { self , File , OpenOptions } ;
76+ use std:: fs:: { self , File , OpenOptions , Permissions } ;
7777use std:: io:: { ErrorKind :: NotFound , Write } ;
7878use std:: path:: { Path , PathBuf } ;
7979use thiserror:: Error ;
@@ -150,6 +150,9 @@ pub enum ConfyError {
150150
151151 #[ error( "Failed to open configuration file" ) ]
152152 OpenConfigurationFileError ( #[ source] std:: io:: Error ) ,
153+
154+ #[ error( "Failed to set configuration file permissions" ) ]
155+ SetPermissionsFileError ( #[ source] std:: io:: Error ) ,
153156}
154157
155158/// Load an application configuration from disk
@@ -268,6 +271,23 @@ pub fn store<'a, T: Serialize>(
268271 store_path ( path, cfg)
269272}
270273
274+ /// Save changes made to a configuration object at a specified path
275+ ///
276+ /// This is an alternate version of [`store`] that allows the specification of
277+ /// file permissions that must be set. For more information on errors and
278+ /// behavior, see [`store`]'s documentation.
279+ ///
280+ /// [`store`]: fn.store.html
281+ pub fn store_perms < ' a , T : Serialize > (
282+ app_name : & str ,
283+ config_name : impl Into < Option < & ' a str > > ,
284+ cfg : T ,
285+ perms : Permissions ,
286+ ) -> Result < ( ) , ConfyError > {
287+ let path = get_configuration_file_path ( app_name, config_name) ?;
288+ store_path_perms ( path, cfg, perms)
289+ }
290+
271291/// Save changes made to a configuration object at a specified path
272292///
273293/// This is an alternate version of [`store`] that allows the specification of
@@ -276,7 +296,29 @@ pub fn store<'a, T: Serialize>(
276296///
277297/// [`store`]: fn.store.html
278298pub fn store_path < T : Serialize > ( path : impl AsRef < Path > , cfg : T ) -> Result < ( ) , ConfyError > {
279- let path = path. as_ref ( ) ;
299+ do_store ( path. as_ref ( ) , cfg, None )
300+ }
301+
302+ /// Save changes made to a configuration object at a specified path
303+ ///
304+ /// This is an alternate version of [`store_path`] that allows the
305+ /// specification of file permissions that must be set. For more information on
306+ /// errors and behavior, see [`store`]'s documentation.
307+ ///
308+ /// [`store_path`]: fn.store_path.html
309+ pub fn store_path_perms < T : Serialize > (
310+ path : impl AsRef < Path > ,
311+ cfg : T ,
312+ perms : Permissions ,
313+ ) -> Result < ( ) , ConfyError > {
314+ do_store ( path. as_ref ( ) , cfg, Some ( perms) )
315+ }
316+
317+ fn do_store < T : Serialize > (
318+ path : & Path ,
319+ cfg : T ,
320+ perms : Option < Permissions > ,
321+ ) -> Result < ( ) , ConfyError > {
280322 let config_dir = path
281323 . parent ( )
282324 . ok_or_else ( || ConfyError :: BadConfigDirectory ( format ! ( "{path:?} is a root or prefix" ) ) ) ?;
@@ -304,6 +346,11 @@ pub fn store_path<T: Serialize>(path: impl AsRef<Path>, cfg: T) -> Result<(), Co
304346 . open ( path)
305347 . map_err ( ConfyError :: OpenConfigurationFileError ) ?;
306348
349+ if let Some ( p) = perms {
350+ f. set_permissions ( p)
351+ . map_err ( ConfyError :: SetPermissionsFileError ) ?;
352+ }
353+
307354 f. write_all ( s. as_bytes ( ) )
308355 . map_err ( ConfyError :: WriteConfigurationFileError ) ?;
309356 Ok ( ( ) )
@@ -345,6 +392,9 @@ mod tests {
345392 use serde:: Serializer ;
346393 use serde_derive:: { Deserialize , Serialize } ;
347394
395+ #[ cfg( unix) ]
396+ use std:: os:: unix:: fs:: PermissionsExt ;
397+
348398 #[ derive( PartialEq , Default , Debug , Serialize , Deserialize ) ]
349399 struct ExampleConfig {
350400 name : String ,
@@ -387,6 +437,45 @@ mod tests {
387437 } )
388438 }
389439
440+ /// [`store_path_perms`] stores [`ExampleConfig`], with only read permission for owner (UNIX).
441+ #[ test]
442+ #[ cfg( unix) ]
443+ fn test_store_path_perms ( ) {
444+ with_config_path ( |path| {
445+ let config: ExampleConfig = ExampleConfig {
446+ name : "Secret" . to_string ( ) ,
447+ count : 16549 ,
448+ } ;
449+ store_path_perms ( path, & config, Permissions :: from_mode ( 0o600 ) )
450+ . expect ( "store_path_perms failed" ) ;
451+ let loaded = load_path ( path) . expect ( "load_path failed" ) ;
452+ assert_eq ! ( config, loaded) ;
453+ } )
454+ }
455+
456+ /// [`store_path_perms`] stores [`ExampleConfig`], as read-only.
457+ #[ test]
458+ fn test_store_path_perms_readonly ( ) {
459+ with_config_path ( |path| {
460+ let config: ExampleConfig = ExampleConfig {
461+ name : "Soon read-only" . to_string ( ) ,
462+ count : 27115 ,
463+ } ;
464+ store_path ( path, & config) . expect ( "store_path failed" ) ;
465+
466+ let metadata = fs:: metadata ( path) . expect ( "reading metadata failed" ) ;
467+ let mut permissions = metadata. permissions ( ) ;
468+ permissions. set_readonly ( true ) ;
469+
470+ store_path_perms ( path, & config, permissions) . expect ( "store_path_perms failed" ) ;
471+
472+ assert ! ( fs:: metadata( path)
473+ . expect( "reading metadata failed" )
474+ . permissions( )
475+ . readonly( ) ) ;
476+ } )
477+ }
478+
390479 /// [`store_path`] fails when given a root path.
391480 #[ test]
392481 fn test_store_path_root_error ( ) {
0 commit comments