Skip to content

Commit

Permalink
Implement store_perms & store_path_perms to specify file permissi…
Browse files Browse the repository at this point in the history
…ons (#94)
  • Loading branch information
HorlogeSkynet authored Mar 1, 2024
1 parent 1ecf860 commit 05e4fdd
Showing 1 changed file with 91 additions and 2 deletions.
93 changes: 91 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ use utils::*;

use directories::ProjectDirs;
use serde::{de::DeserializeOwned, Serialize};
use std::fs::{self, File, OpenOptions};
use std::fs::{self, File, OpenOptions, Permissions};
use std::io::{ErrorKind::NotFound, Write};
use std::path::{Path, PathBuf};
use thiserror::Error;
Expand Down Expand Up @@ -150,6 +150,9 @@ pub enum ConfyError {

#[error("Failed to open configuration file")]
OpenConfigurationFileError(#[source] std::io::Error),

#[error("Failed to set configuration file permissions")]
SetPermissionsFileError(#[source] std::io::Error),
}

/// Load an application configuration from disk
Expand Down Expand Up @@ -268,6 +271,23 @@ pub fn store<'a, T: Serialize>(
store_path(path, cfg)
}

/// Save changes made to a configuration object at a specified path
///
/// This is an alternate version of [`store`] that allows the specification of
/// file permissions that must be set. For more information on errors and
/// behavior, see [`store`]'s documentation.
///
/// [`store`]: fn.store.html
pub fn store_perms<'a, T: Serialize>(
app_name: &str,
config_name: impl Into<Option<&'a str>>,
cfg: T,
perms: Permissions,
) -> Result<(), ConfyError> {
let path = get_configuration_file_path(app_name, config_name)?;
store_path_perms(path, cfg, perms)
}

/// Save changes made to a configuration object at a specified path
///
/// This is an alternate version of [`store`] that allows the specification of
Expand All @@ -276,7 +296,29 @@ pub fn store<'a, T: Serialize>(
///
/// [`store`]: fn.store.html
pub fn store_path<T: Serialize>(path: impl AsRef<Path>, cfg: T) -> Result<(), ConfyError> {
let path = path.as_ref();
do_store(path.as_ref(), cfg, None)
}

/// Save changes made to a configuration object at a specified path
///
/// This is an alternate version of [`store_path`] that allows the
/// specification of file permissions that must be set. For more information on
/// errors and behavior, see [`store`]'s documentation.
///
/// [`store_path`]: fn.store_path.html
pub fn store_path_perms<T: Serialize>(
path: impl AsRef<Path>,
cfg: T,
perms: Permissions,
) -> Result<(), ConfyError> {
do_store(path.as_ref(), cfg, Some(perms))
}

fn do_store<T: Serialize>(
path: &Path,
cfg: T,
perms: Option<Permissions>,
) -> Result<(), ConfyError> {
let config_dir = path
.parent()
.ok_or_else(|| ConfyError::BadConfigDirectory(format!("{path:?} is a root or prefix")))?;
Expand Down Expand Up @@ -304,6 +346,11 @@ pub fn store_path<T: Serialize>(path: impl AsRef<Path>, cfg: T) -> Result<(), Co
.open(path)
.map_err(ConfyError::OpenConfigurationFileError)?;

if let Some(p) = perms {
f.set_permissions(p)
.map_err(ConfyError::SetPermissionsFileError)?;
}

f.write_all(s.as_bytes())
.map_err(ConfyError::WriteConfigurationFileError)?;
Ok(())
Expand Down Expand Up @@ -345,6 +392,9 @@ mod tests {
use serde::Serializer;
use serde_derive::{Deserialize, Serialize};

#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;

#[derive(PartialEq, Default, Debug, Serialize, Deserialize)]
struct ExampleConfig {
name: String,
Expand Down Expand Up @@ -387,6 +437,45 @@ mod tests {
})
}

/// [`store_path_perms`] stores [`ExampleConfig`], with only read permission for owner (UNIX).
#[test]
#[cfg(unix)]
fn test_store_path_perms() {
with_config_path(|path| {
let config: ExampleConfig = ExampleConfig {
name: "Secret".to_string(),
count: 16549,
};
store_path_perms(path, &config, Permissions::from_mode(0o600))
.expect("store_path_perms failed");
let loaded = load_path(path).expect("load_path failed");
assert_eq!(config, loaded);
})
}

/// [`store_path_perms`] stores [`ExampleConfig`], as read-only.
#[test]
fn test_store_path_perms_readonly() {
with_config_path(|path| {
let config: ExampleConfig = ExampleConfig {
name: "Soon read-only".to_string(),
count: 27115,
};
store_path(path, &config).expect("store_path failed");

let metadata = fs::metadata(path).expect("reading metadata failed");
let mut permissions = metadata.permissions();
permissions.set_readonly(true);

store_path_perms(path, &config, permissions).expect("store_path_perms failed");

assert!(fs::metadata(path)
.expect("reading metadata failed")
.permissions()
.readonly());
})
}

/// [`store_path`] fails when given a root path.
#[test]
fn test_store_path_root_error() {
Expand Down

0 comments on commit 05e4fdd

Please sign in to comment.