Skip to content

Commit

Permalink
Implement store_perms & store_path_perms to specify file permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
HorlogeSkynet committed Feb 19, 2024
1 parent 7855cd3 commit 9b1e77a
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 @@ -66,7 +66,7 @@ use utils::*;

use directories_next::ProjectDirs;
use serde::{de::DeserializeOwned, Serialize};
use std::fs::{self, File, OpenOptions};
use std::fs::{self, File, OpenOptions, Permissions};
use std::io::Write;
use std::path::{Path, PathBuf};
use thiserror::Error;
Expand Down Expand Up @@ -128,6 +128,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 @@ -233,6 +236,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 @@ -241,7 +261,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!("{:?} is a root or prefix", path)))?;
Expand Down Expand Up @@ -282,6 +324,11 @@ pub fn store_path<T: Serialize>(path: impl AsRef<Path>, cfg: T) -> Result<(), Co
.open(&path_tmp)
.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)?;
f.flush().map_err(ConfyError::WriteConfigurationFileError)?;
Expand Down Expand Up @@ -326,6 +373,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 @@ -368,6 +418,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 9b1e77a

Please sign in to comment.