Skip to content

Commit 05e4fdd

Browse files
Implement store_perms & store_path_perms to specify file permissions (#94)
1 parent 1ecf860 commit 05e4fdd

1 file changed

Lines changed: 91 additions & 2 deletions

File tree

src/lib.rs

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ use utils::*;
7373

7474
use directories::ProjectDirs;
7575
use serde::{de::DeserializeOwned, Serialize};
76-
use std::fs::{self, File, OpenOptions};
76+
use std::fs::{self, File, OpenOptions, Permissions};
7777
use std::io::{ErrorKind::NotFound, Write};
7878
use std::path::{Path, PathBuf};
7979
use 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
278298
pub 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

Comments
 (0)