summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRina Fujino <18257209+rina23q@users.noreply.github.com>2022-05-18 01:22:31 +0200
committerRina Fujino <18257209+rina23q@users.noreply.github.com>2022-05-18 01:22:31 +0200
commit88ef4b2fd0076811687a0b23ec21edad1ca2e0af (patch)
treed188a55087072d0f8c9ef05e89e34b4ac0126e0c
parent7e6d95fa0d0289885df2cd533318c9afdd3e5e69 (diff)
Add user, group, mode support
Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
-rw-r--r--crates/common/tedge_utils/Cargo.toml2
-rw-r--r--crates/common/tedge_utils/src/file.rs208
-rw-r--r--plugins/c8y_configuration_plugin/src/config.rs145
-rw-r--r--plugins/c8y_configuration_plugin/src/download.rs170
-rw-r--r--plugins/c8y_configuration_plugin/src/error.rs16
-rw-r--r--plugins/c8y_configuration_plugin/src/main.rs11
-rw-r--r--plugins/c8y_configuration_plugin/src/upload.rs13
7 files changed, 381 insertions, 184 deletions
diff --git a/crates/common/tedge_utils/Cargo.toml b/crates/common/tedge_utils/Cargo.toml
index 7a42e257..8ccb0ed0 100644
--- a/crates/common/tedge_utils/Cargo.toml
+++ b/crates/common/tedge_utils/Cargo.toml
@@ -16,7 +16,7 @@ logging = ["tracing", "tracing-subscriber"]
nix = "0.23.1"
tempfile = "3.2"
thiserror = "1.0"
-tokio = { version = "1.12", default_features = false, features = [ "fs", "io-util", "macros", "signal"] }
+tokio = { version = "1.12", default_features = false, features = [ "fs", "io-util", "macros", "signal", "rt-multi-thread"] }
tracing = { version = "0.1", features = [], optional = true }
tracing-subscriber = { version = "0.3", optional = true, features = [ "time" ] }
users = "0.11.0"
diff --git a/crates/common/tedge_utils/src/file.rs b/crates/common/tedge_utils/src/file.rs
index cc819e05..03cb68b0 100644
--- a/crates/common/tedge_utils/src/file.rs
+++ b/crates/common/tedge_utils/src/file.rs
@@ -2,6 +2,7 @@ use nix::unistd::*;
use std::fs::File;
use std::os::linux::fs::MetadataExt;
use std::os::unix::fs::PermissionsExt;
+use std::path::{Path, PathBuf};
use std::{fs, io};
use users::{get_group_by_name, get_user_by_name};
@@ -24,6 +25,9 @@ pub enum FileError {
#[error(transparent)]
Errno(#[from] nix::errno::Errno),
+
+ #[error("The path is not accessible. {path:?}")]
+ PathNotAccessible { path: PathBuf },
}
pub fn create_directory_with_user_group(
@@ -97,34 +101,91 @@ fn change_owner_and_permission(
}
};
- let uid = fs::metadata(file)
- .map_err(|e| FileError::MetaDataError {
- name: file.to_string(),
- from: e,
- })?
- .st_uid();
- let gid = fs::metadata(file)
- .map_err(|e| FileError::MetaDataError {
- name: file.to_string(),
- from: e,
- })?
- .st_gid();
+ let uid = get_metadata(Path::new(file))?.st_uid();
+ let gid = get_metadata(Path::new(file))?.st_gid();
// if user and group is same as existing, then do not change
if (ud != uid) && (gd != gid) {
- chown(
- file,
- Some(Uid::from_raw(ud.into())),
- Some(Gid::from_raw(gd.into())),
- )?;
+ chown(file, Some(Uid::from_raw(ud)), Some(Gid::from_raw(gd)))?;
+ }
+
+ let mut perm = get_metadata(Path::new(file))?.permissions();
+ perm.set_mode(mode);
+
+ fs::set_permissions(file, perm).map_err(|e| FileError::MetaDataError {
+ name: file.to_string(),
+ from: e,
+ })?;
+
+ Ok(())
+}
+
+#[derive(Debug, PartialEq, Eq, Default, Clone)]
+pub struct FilePermissions {
+ pub user: Option<String>,
+ pub group: Option<String>,
+ pub mode: Option<u32>,
+}
+
+impl FilePermissions {
+ pub fn new(user: Option<String>, group: Option<String>, mode: Option<u32>) -> Self {
+ Self { user, group, mode }
+ }
+
+ pub fn change_permissions(&self, file: &str) -> Result<(), FileError> {
+ if let Some(user) = &self.user {
+ let () = change_user(file, user)?;
+ }
+ if let Some(group) = &self.group {
+ let () = change_group(file, group)?;
+ }
+ if let Some(mode) = &self.mode {
+ let () = change_mode(file, *mode)?;
+ }
+ Ok(())
+ }
+}
+
+fn change_user(file: &str, user: &str) -> Result<(), FileError> {
+ let ud = match get_user_by_name(user) {
+ Some(user) => user.uid(),
+ None => {
+ return Err(FileError::UserNotFound { user: user.into() });
+ }
+ };
+
+ let uid = get_metadata(Path::new(file))?.st_uid();
+
+ // if user is same as existing, then do not change
+ if ud != uid {
+ chown(file, Some(Uid::from_raw(ud)), None)?;
+ }
+
+ Ok(())
+}
+
+fn change_group(file: &str, group: &str) -> Result<(), FileError> {
+ let gd = match get_group_by_name(group) {
+ Some(group) => group.gid(),
+ None => {
+ return Err(FileError::GroupNotFound {
+ group: group.into(),
+ });
+ }
+ };
+
+ let gid = get_metadata(Path::new(file))?.st_gid();
+
+ // if group is same as existing, then do not change
+ if gd != gid {
+ chown(file, None, Some(Gid::from_raw(gd)))?;
}
- let mut perm = fs::metadata(file)
- .map_err(|e| FileError::MetaDataError {
- name: file.to_string(),
- from: e,
- })?
- .permissions();
+ Ok(())
+}
+
+fn change_mode(file: &str, mode: u32) -> Result<(), FileError> {
+ let mut perm = get_metadata(Path::new(file))?.permissions();
perm.set_mode(mode);
fs::set_permissions(file, perm).map_err(|e| FileError::MetaDataError {
@@ -134,77 +195,136 @@ fn change_owner_and_permission(
Ok(())
}
+
+/// Return metadata when the given path exists and accessible by user
+pub fn get_metadata(path: &Path) -> Result<fs::Metadata, FileError> {
+ fs::metadata(&path).map_err(|_| FileError::PathNotAccessible {
+ path: path.to_path_buf(),
+ })
+}
+
+/// Return filename if the given path contains a filename
+pub fn get_filename(path: PathBuf) -> Option<String> {
+ let filename = path.file_name()?.to_str()?.to_string();
+ Some(filename)
+}
+
#[cfg(test)]
mod tests {
use super::*;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
+ use tempfile::TempDir;
#[test]
fn create_file_correct_user_group() {
+ let temp_dir = TempDir::new().unwrap();
+ let file_path = temp_dir.path().join("file").display().to_string();
+
let user = whoami::username();
- let _ = create_file_with_user_group("/tmp/fcreate_test", &user, &user, 0o644).unwrap();
- assert!(Path::new("/tmp/fcreate_test").exists());
- let meta = std::fs::metadata("/tmp/fcreate_test").unwrap();
+ let _ = create_file_with_user_group(file_path.as_str(), &user, &user, 0o644).unwrap();
+ assert!(Path::new(file_path.as_str()).exists());
+ let meta = std::fs::metadata(file_path.as_str()).unwrap();
let perm = meta.permissions();
println!("{:o}", perm.mode());
assert!(format!("{:o}", perm.mode()).contains("644"));
- fs::remove_file("/tmp/fcreate_test").unwrap();
}
#[test]
fn create_file_wrong_user() {
+ let temp_dir = TempDir::new().unwrap();
+ let file_path = temp_dir.path().join("file").display().to_string();
+
let user = whoami::username();
- let err = create_file_with_user_group("/tmp/fcreate_wrong_user", "test", &user, 0o775)
- .unwrap_err();
+ let err =
+ create_file_with_user_group(file_path.as_str(), "test", &user, 0o775).unwrap_err();
assert!(err.to_string().contains("User not found"));
- fs::remove_file("/tmp/fcreate_wrong_user").unwrap();
}
#[test]
fn create_file_wrong_group() {
+ let temp_dir = TempDir::new().unwrap();
+ let file_path = temp_dir.path().join("file").display().to_string();
+
let user = whoami::username();
- let err = create_file_with_user_group("/tmp/fcreate_wrong_group", &user, "test", 0o775)
- .unwrap_err();
+ let err =
+ create_file_with_user_group(file_path.as_str(), &user, "test", 0o775).unwrap_err();
assert!(err.to_string().contains("Group not found"));
- fs::remove_file("/tmp/fcreate_wrong_group").unwrap();
+ fs::remove_file(file_path.as_str()).unwrap();
}
#[test]
fn create_directory_with_correct_user_group() {
+ let temp_dir = TempDir::new().unwrap();
+ let dir_path = temp_dir.path().join("dir").display().to_string();
+
let user = whoami::username();
- let _ =
- create_directory_with_user_group("/tmp/fcreate_test_dir", &user, &user, 0o775).unwrap();
+ let _ = create_directory_with_user_group(dir_path.as_str(), &user, &user, 0o775).unwrap();
- assert!(Path::new("/tmp/fcreate_test_dir").exists());
- let meta = std::fs::metadata("/tmp/fcreate_test_dir").unwrap();
+ assert!(Path::new(dir_path.as_str()).exists());
+ let meta = fs::metadata(dir_path.as_str()).unwrap();
let perm = meta.permissions();
println!("{:o}", perm.mode());
assert!(format!("{:o}", perm.mode()).contains("775"));
- fs::remove_dir("/tmp/fcreate_test_dir").unwrap();
}
#[test]
fn create_directory_with_wrong_user() {
+ let temp_dir = TempDir::new().unwrap();
+ let dir_path = temp_dir.path().join("dir").display().to_string();
+
let user = whoami::username();
- let err = create_directory_with_user_group("/tmp/wrong_user_dir", "test", &user, 0o775)
- .unwrap_err();
+ let err =
+ create_directory_with_user_group(dir_path.as_str(), "test", &user, 0o775).unwrap_err();
assert!(err.to_string().contains("User not found"));
- fs::remove_dir("/tmp/wrong_user_dir").unwrap();
}
#[test]
fn create_directory_with_wrong_group() {
+ let temp_dir = TempDir::new().unwrap();
+ let dir_path = temp_dir.path().join("dir").display().to_string();
+
let user = whoami::username();
- let err = create_directory_with_user_group("/tmp/wrong_group_dir", &user, "test", 0o775)
- .unwrap_err();
+ let err =
+ create_directory_with_user_group(dir_path.as_str(), &user, "test", 0o775).unwrap_err();
assert!(err.to_string().contains("Group not found"));
- fs::remove_dir("/tmp/wrong_group_dir").unwrap();
+ }
+
+ #[test]
+ fn change_file_permissions() {
+ let temp_dir = TempDir::new().unwrap();
+ let file_path = temp_dir.path().join("file").display().to_string();
+
+ let user = whoami::username();
+ let _ = create_file_with_user_group(file_path.as_str(), &user, &user, 0o644).unwrap();
+ assert!(Path::new(file_path.as_str()).exists());
+
+ let meta = fs::metadata(file_path.as_str()).unwrap();
+ let perm = meta.permissions();
+ assert!(format!("{:o}", perm.mode()).contains("644"));
+
+ let file_permissions = FilePermissions::new(None, None, Some(0o444));
+ let () = file_permissions
+ .change_permissions(file_path.as_str())
+ .unwrap();
+
+ let meta = fs::metadata(file_path.as_str()).unwrap();
+ let perm = meta.permissions();
+ assert!(format!("{:o}", perm.mode()).contains("444"));
+ }
+
+ #[test]
+ fn verify_get_file_name() {
+ assert_eq!(
+ get_filename(PathBuf::from("/it/is/file.txt")),
+ Some("file.txt".to_string())
+ );
+ assert_eq!(get_filename(PathBuf::from("/")), None);
}
}
diff --git a/plugins/c8y_configuration_plugin/src/config.rs b/plugins/c8y_configuration_plugin/src/config.rs
index 839eab6d..0c42a804 100644
--- a/plugins/c8y_configuration_plugin/src/config.rs
+++ b/plugins/c8y_configuration_plugin/src/config.rs
@@ -7,6 +7,7 @@ use std::collections::HashSet;
use std::fs;
use std::hash::{Hash, Hasher};
use std::path::Path;
+use tedge_utils::file::FilePermissions;
use tracing::{info, warn};
#[derive(Deserialize, Debug, Default)]
@@ -21,6 +22,9 @@ pub struct RawFileEntry {
pub path: String,
#[serde(rename = "type")]
config_type: Option<String>,
+ user: Option<String>,
+ group: Option<String>,
+ mode: Option<u32>,
}
#[derive(Debug, Eq, PartialEq, Default, Clone)]
@@ -32,6 +36,7 @@ pub struct PluginConfig {
pub struct FileEntry {
pub path: String,
config_type: String,
+ pub file_permissions: FilePermissions,
}
impl Hash for FileEntry {
@@ -53,8 +58,18 @@ impl Borrow<String> for FileEntry {
}
impl FileEntry {
- pub fn new(path: String, config_type: String) -> Self {
- Self { path, config_type }
+ pub fn new(
+ path: String,
+ config_type: String,
+ user: Option<String>,
+ group: Option<String>,
+ mode: Option<u32>,
+ ) -> Self {
+ Self {
+ path,
+ config_type,
+ file_permissions: FilePermissions { user, group, mode },
+ }
}
}
@@ -65,16 +80,13 @@ impl RawPluginConfig {
match fs::read_to_string(config_file_path) {
Ok(contents) => match toml::from_str(contents.as_str()) {
Ok(config) => config,
- _ => {
- warn!("The config file {} is malformed.", path_str);
+ Err(err) => {
+ warn!("The config file {path_str} is malformed. {err}");
Self::default()
}
},
- Err(_) => {
- warn!(
- "The config file {} does not exist or is not readable.",
- path_str
- );
+ Err(err) => {
+ warn!("The config file {path_str} does not exist or is not readable. {err}");
Self::default()
}
}
@@ -92,6 +104,9 @@ impl PluginConfig {
let c8y_configuration_plugin = FileEntry::new(
config_file_path.display().to_string(),
DEFAULT_PLUGIN_CONFIG_TYPE.into(),
+ None,
+ None,
+ None,
);
Self {
files: HashSet::from([c8y_configuration_plugin]),
@@ -104,7 +119,22 @@ impl PluginConfig {
let config_type = raw_entry
.config_type
.unwrap_or_else(|| raw_entry.path.clone());
- let entry = FileEntry::new(raw_entry.path, config_type.clone());
+
+ if config_type.contains(&['+', '#']) {
+ warn!(
+ "The config type '{}' contains the forbidden characters, '+' or '#'.",
+ config_type
+ );
+ return original_plugin_config;
+ }
+
+ let entry = FileEntry::new(
+ raw_entry.path,
+ config_type.clone(),
+ raw_entry.user,
+ raw_entry.group,
+ raw_entry.mode,
+ );
if !self.files.insert(entry) {
warn!("The config file has the duplicated type '{}'.", config_type);
return original_plugin_config;
@@ -125,7 +155,10 @@ impl PluginConfig {
.collect::<Vec<_>>()
}
- pub fn get_path_from_type(&self, config_type: &str) -> Result<String, ConfigManagementError> {
+ pub fn get_file_entry_from_type(
+ &self,
+ config_type: &str,
+ ) -> Result<FileEntry, ConfigManagementError> {
let file_entry = self
.files
.get(&config_type.to_string())
@@ -133,7 +166,7 @@ impl PluginConfig {
config_type: config_type.to_owned(),
})?
.to_owned();
- Ok(file_entry.path)
+ Ok(file_entry)
}
// 119,typeA,typeB,...
@@ -181,23 +214,23 @@ mod tests {
assert_eq!(
config.files,
vec![
- RawFileEntry::new(
+ RawFileEntry::new_with_path_and_type(
"/etc/tedge/tedge.toml".to_string(),
Some("tedge.toml".to_string())
),
- RawFileEntry::new(
+ RawFileEntry::new_with_path_and_type(
"/etc/tedge/tedge.toml".to_string(),
Some("tedge.toml".to_string())
),
- RawFileEntry::new(
+ RawFileEntry::new_with_path_and_type(
"/etc/tedge/mosquitto-conf/c8y-bridge.conf".to_string(),
None
),
- RawFileEntry::new(
+ RawFileEntry::new_with_path_and_type(
"/etc/tedge/mosquitto-conf/tedge-mosquitto.conf".to_string(),
Some("\"double quotation\"".to_string())
),
- RawFileEntry::new(
+ RawFileEntry::new_with_path_and_type(
"/etc/mosquitto/mosquitto.conf".to_string(),
Some("'single quotation'".to_string())
)
@@ -223,23 +256,23 @@ mod tests {
assert_eq!(
config.files,
vec![
- RawFileEntry::new(
+ RawFileEntry::new_with_path_and_type(
"/etc/tedge/tedge.toml".to_string(),
Some("tedge.toml".to_string())
),
- RawFileEntry::new(
+ RawFileEntry::new_with_path_and_type(
"/etc/tedge/tedge.toml".to_string(),
Some("tedge.toml".to_string())
),
- RawFileEntry::new(
+ RawFileEntry::new_with_path_and_type(
"/etc/tedge/mosquitto-conf/c8y-bridge.conf".to_string(),
None
),
- RawFileEntry::new(
+ RawFileEntry::new_with_path_and_type(
"/etc/tedge/mosquitto-conf/tedge-mosquitto.conf".to_string(),
Some("\"double quotation\"".to_string())
),
- RawFileEntry::new(
+ RawFileEntry::new_with_path_and_type(
"/etc/mosquitto/mosquitto.conf".to_string(),
Some("'single quotation'".to_string())
)
@@ -257,8 +290,8 @@ mod tests {
"#,
PluginConfig {
files: HashSet::from([
- FileEntry::new("/etc/tedge/tedge.toml".to_string(), "tedge".to_string()),
- FileEntry::new("/etc/tedge/mosquitto-conf/c8y-bridge.conf".to_string(), "/etc/tedge/mosquitto-conf/c8y-bridge.conf".to_string()),
+ FileEntry::new_with_path_and_type("/etc/tedge/tedge.toml".to_string(), "tedge".to_string()),
+ FileEntry::new_with_path_and_type("/etc/tedge/mosquitto-conf/c8y-bridge.conf".to_string(), "/etc/tedge/mosquitto-conf/c8y-bridge.conf".to_string()),
])
}; "standard case"
)]
@@ -273,8 +306,8 @@ mod tests {
"#,
PluginConfig {
files: HashSet::from([
- FileEntry::new("/etc/tedge/tedge.toml".to_string(), "tedge".to_string()),
- FileEntry::new("/etc/tedge/tedge.toml".to_string(), "tedge2".to_string()),
+ FileEntry::new_with_path_and_type("/etc/tedge/tedge.toml".to_string(), "tedge".to_string()),
+ FileEntry::new_with_path_and_type("/etc/tedge/tedge.toml".to_string(), "tedge2".to_string()),
])
}; "file path duplication"
)]
@@ -288,14 +321,36 @@ mod tests {
type = "tedge"
"#,
PluginConfig {
- files: HashSet::new()
- }; "file type duplication"
+ files: HashSet::new()
+ }; "file type duplication"
)]
#[test_case(
- r#"files = []"#,
+ r#"
+ [[files]]
+ path = "/etc/tedge/tedge.toml"
+ type = "tedge#"
+ "#,
PluginConfig {
files: HashSet::new()
}
+ ;"type contains sharp"
+ )]
+ #[test_case(
+ r#"
+ [[files]]
+ path = "/etc/tedge/tedge.toml"
+ type = "tedge+"
+ "#,
+ PluginConfig {
+ files: HashSet::new()
+ }
+ ;"type contains plus"
+ )]
+ #[test_case(
+ r#"files = []"#,
+ PluginConfig {
+ files: HashSet::new()
+ }
;"empty case"
)]
#[test_case(
@@ -338,8 +393,24 @@ mod tests {
}
impl RawFileEntry {
- pub fn new(path: String, config_type: Option<String>) -> Self {
- Self { path, config_type }
+ pub fn new_with_path_and_type(path: String, config_type: Option<String>) -> Self {
+ Self {
+ path,
+ config_type,
+ user: None,
+ group: None,
+ mode: None,
+ }
+ }
+ }
+
+ impl FileEntry {
+ pub fn new_with_path_and_type(path: String, config_type: String) -> Self {
+ Self {
+ path,
+ config_type,
+ file_permissions: FilePermissions::default(),
+ }
}
}
@@ -347,7 +418,7 @@ mod tests {
impl PluginConfig {
fn add_file_entry(&self, path: String, config_type: String) -> Self {
let mut files = self.files.clone();
- let _ = files.insert(FileEntry::new(path, config_type));
+ let _ = files.insert(FileEntry::new(path, config_type, None, None, None));
Self { files }
}
}
@@ -357,7 +428,7 @@ mod tests {
let temp_dir = TempDir::new()?;
let config_root = temp_dir.path().to_path_buf();
let config_file_path = config_root.join(PLUGIN_CONFIG_FILE);
- let mut file = std::fs::File::create(config_file_path.as_path())?;
+ let mut file = fs::File::create(config_file_path.as_path())?;
file.write_all(content.as_bytes())?;
Ok((temp_dir, config_root))
}
@@ -365,7 +436,7 @@ mod tests {
#[test]
fn get_smartrest_single_type() {
let plugin_config = PluginConfig {
- files: HashSet::from([FileEntry::new(
+ files: HashSet::from([FileEntry::new_with_path_and_type(
"/path/to/file".to_string(),
"typeA".to_string(),
)]),
@@ -378,9 +449,9 @@ mod tests {
fn get_smartrest_multiple_types() {
let plugin_config = PluginConfig {
files: HashSet::from([
- FileEntry::new("path1".to_string(), "typeA".to_string()),
- FileEntry::new("path2".to_string(), "typeB".to_string()),
- FileEntry::new("path3".to_string(), "typeC".to_string()),
+ FileEntry::new_with_path_and_type("path1".to_string(), "typeA".to_string()),
+ FileEntry::new_with_path_and_type("path2".to_string(), "typeB".to_string()),
+ FileEntry::new_with_path_and_type("path3".to_string(), "typeC".to_string()),
]),
};
let output = plugin_config.to_smartrest_payload();
diff --git a/plugins/c8y_configuration_plugin/src/download.rs b/plugins/c8y_configuration_plugin/src/download.rs
index e58ef1f6..9a345385 100644
--- a/plugins/c8y_configuration_plugin/src/download.rs
+++ b/plugins/c8y_configuration_plugin/src/download.rs
@@ -10,11 +10,11 @@ use c8y_smartrest::smartrest_serializer::{
};
use download::{Auth, DownloadInfo, Downloader};
use mqtt_channel::{Connection, Message, SinkExt, Topic};
-use serde::{Deserialize, Serialize};
use serde_json::json;
use std::fs;
use std::os::unix::fs::PermissionsExt;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
+use tedge_utils::file::{get_filename, get_metadata, FilePermissions};
use tracing::{info, warn};
pub async fn handle_config_download_request(
@@ -27,21 +27,31 @@ pub async fn handle_config_download_request(
let executing_message = DownloadConfigFileStatusMessage::executing()?;
let () = mqtt_client.published.send(executing_message).await?;
- let changed_config_type = smartrest_request.config_type.clone();
-
- match download_config_file(plugin_config, smartrest_request, tmp_dir, http_client).await {
+ let target_config_type = smartrest_request.config_type.clone();
+ let target_file_entry = plugin_config.get_file_entry_from_type(&target_config_type)?;
+
+ match download_config_file(
+ smartrest_request.url.as_str(),
+ PathBuf::from(&target_file_entry.path),
+ tmp_dir,
+ target_file_entry.file_permissions,
+ http_client,
+ )
+ .await
+ {
Ok(_) => {
- info!("The configuration download for '{changed_config_type}' is successful.");
+ info!("The configuration download for '{target_config_type}' is successful.");
let successful_message = DownloadConfigFileStatusMessage::successful(None)?;
let () = mqtt_client.published.send(successful_message).await?;
- let notification_message = get_file_change_notification_message(changed_config_type);
+ let notification_message =
+ get_file_change_notification_message(&target_file_entry.path, &target_config_type);
let () = mqtt_client.published.send(notification_message).await?;
Ok(())
}
Err(err) => {
- error!("The configuration download for '{changed_config_type}' is failed.",);
+ error!("The configuration download for '{target_config_type}' is failed.",);
let failed_message = DownloadConfigFileStatusMessage::failed(err.to_string())?;
let () = mqtt_client.published.send(failed_message).await?;
@@ -51,14 +61,15 @@ pub async fn handle_config_download_request(
}
async fn download_config_file(
- plugin_config: &PluginConfig,
- smartrest_request: SmartRestConfigDownloadRequest,
+ download_url: &str,
+ file_path: PathBuf,
tmp_dir: PathBuf,
+ file_permissions: FilePermissions,
http_client: &mut impl C8YHttpProxy,
) -> Result<(), anyhow::Error> {
// Convert smartrest request to config download request struct
let mut config_download_request =
- ConfigDownloadRequest::try_new(smartrest_request, plugin_config, tmp_dir)?;
+ ConfigDownloadRequest::try_new(download_url, file_path, tmp_dir, file_permissions)?;
// Confirm that the file has write access before any http request attempt
let () = config_download_request.has_write_access()?;
@@ -81,55 +92,59 @@ async fn download_config_file(
Ok(())
}
-#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
+#[derive(Debug, Clone, PartialEq)]
pub struct ConfigDownloadRequest {
pub download_info: DownloadInfo,
- pub destination_path: PathBuf,
+ pub file_path: PathBuf,
pub tmp_dir: PathBuf,
+ pub file_permissions: FilePermissions,
pub file_name: String,
}
impl ConfigDownloadRequest {
fn try_new(
- request: SmartRestConfigDownloadRequest,
- plugin_config: &PluginConfig,
+ download_url: &str,
+ file_path: PathBuf,
tmp_dir: PathBuf,
+ file_permissions: FilePermissions,
) -> Result<Self, ConfigManagementError> {
- let destination_path_string = plugin_config.get_path_from_type(&request.config_type)?;
- let destination_path = PathBuf::from(destination_path_string);
- let file_name = Self::get_filename(destination_path.clone())?;
+ let file_name = get_filename(file_path.clone()).ok_or_else(|| {
+ ConfigManagementError::FileNameNotFound {
+ path: file_path.clone(),
+ }
+ })?;
Ok(Self {
download_info: DownloadInfo {
- url: request.url,
+ url: download_url.into(),
auth: None,
},
- destination_path,
+ file_path,
tmp_dir,
+ file_permissions,
file_name,
})
}
- fn get_filename(path: PathBuf) -> Result<String, ConfigManagementError> {
- let filename = path
- .file_name()
- .ok_or_else(|| ConfigManagementError::FileNameNotFound { path: path.clone() })?
- .to_str()
- .ok_or_else(|| ConfigManagementError::InvalidFileName { path: path.clone() })?
- .to_string();
- Ok(filename)
- }
-
fn has_write_access(&self) -> Result<(), ConfigManagementError> {
// The file does not exist before downloading a file
- if !&self.destination_path.is_file() {
- return Ok(());
- }
- // Need a permission check when the file exists already
- let metadata = Self::get_metadata(&self.destination_path)?;
+ let metadata =
+ if self.file_path.is_file() {
+ get_metadata(&self.file_path)?
+ } else {
+ // If the file does not exist before downloading file, check the directory perms
+ let parent_dir = &self.file_path.parent().ok_or_else(|| {
+ ConfigManagementError::NoWriteAccess {
+ path: self.file_path.clone(),
+ }
+ })?;
+ get_metadata(parent_dir)?
+ };
+
+ // Write permission check
if metadata.permissions().readonly() {
- Err(error::ConfigManagementError::ReadOnlyFile {
- path: self.destination_path.clone(),
+ Err(ConfigManagementError::NoWriteAccess {
+ path: self.file_path.clone(),
})
} else {
Ok(())
@@ -140,19 +155,13 @@ impl ConfigDownloadRequest {
Downloader::new(&self.file_name, &None, &self.tmp_dir)
}
- fn get_metadata(path: &Path) -> Result<std::fs::Metadata, ConfigManagementError> {
- fs::metadata(&path).map_err(|_| ConfigManagementError::FileNotAccessible {
- path: path.to_path_buf(),
- })
- }
-
fn move_file(&self) -> Result<(), ConfigManagementError> {
let src = &self.tmp_dir.join(&self.file_name);
- let dest = &self.destination_path;
+ let dest = &self.file_path;
- let original_permission_mode = match self.destination_path.is_file() {
+ let original_permission_mode = match self.file_path.is_file() {
true => {
- let metadata = Self::get_metadata(&self.destination_path)?;
+ let metadata = get_metadata(&self.file_path)?;
let mode = metadata.permissions().mode();
Some(mode)
}
@@ -164,19 +173,22 @@ impl ConfigDownloadRequest {
dest: dest.to_path_buf(),
})?;
- // Change the file permission back to the original one
- if let Some(mode) = original_permission_mode {
- let mut permissions = Self::get_metadata(&self.destination_path)?.permissions();
- let _ = permissions.set_mode(mode);
- let _ = std::fs::set_permissions(&self.destination_path, permissions);
- }
+ let file_permissions = if let Some(mode) = original_permission_mode {
+ // Use the same file permission as the original one
+ FilePermissions::new(None, None, Some(mode))
+ } else {
+ // Set the user, group, and mode as given for a new file
+ self.file_permissions.clone()
+ };
+
+ let () = file_permissions.change_permissions(&self.file_path.display().to_string())?;
Ok(())
}
}
-pub fn get_file_change_notification_message(config_type: String) -> Message {
- let notification = json!({ "changedFile": config_type }).to_string();
+pub fn get_file_change_notification_message(file_path: &str, config_type: &str) -> Message {
+ let notification = json!({ "path": file_path }).to_string();
let topic = Topic::new(format!("{CONFIG_CHANGE_TOPIC}/{config_type}").as_str())
.unwrap_or_else(|_err| {
warn!("The type cannot be used as a part of the topic name. Using {CONFIG_CHANGE_TOPIC} instead.");
@@ -212,29 +224,17 @@ impl TryIntoOperationStatusMessage for DownloadConfigFileStatusMessage {
#[cfg(test)]
mod tests {
use super::*;
-