config: rustfmt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
		| @ -10,7 +10,7 @@ use lazy_static::lazy_static; | ||||
|  | ||||
| use proxmox_schema::{ApiStringFormat, ApiType, Schema, StringSchema}; | ||||
|  | ||||
| use pbs_api_types::{Authid, Userid, Role, ROLE_NAME_NO_ACCESS}; | ||||
| use pbs_api_types::{Authid, Role, Userid, ROLE_NAME_NO_ACCESS}; | ||||
|  | ||||
| use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | ||||
|  | ||||
| @ -328,10 +328,7 @@ impl AclTree { | ||||
|     fn get_or_insert_node(&mut self, path: &[&str]) -> &mut AclTreeNode { | ||||
|         let mut node = &mut self.root; | ||||
|         for comp in path { | ||||
|             node = node | ||||
|                 .children | ||||
|                 .entry(String::from(*comp)) | ||||
|                 .or_default(); | ||||
|             node = node.children.entry(String::from(*comp)).or_default(); | ||||
|         } | ||||
|         node | ||||
|     } | ||||
|  | ||||
| @ -1,15 +1,15 @@ | ||||
| //! Cached user info for fast ACL permission checks | ||||
|  | ||||
| use std::sync::{RwLock, Arc}; | ||||
| use std::sync::{Arc, RwLock}; | ||||
|  | ||||
| use anyhow::{Error, bail}; | ||||
| use anyhow::{bail, Error}; | ||||
| use lazy_static::lazy_static; | ||||
|  | ||||
| use proxmox_router::UserInformation; | ||||
| use proxmox_section_config::SectionConfigData; | ||||
| use proxmox_time::epoch_i64; | ||||
|  | ||||
| use pbs_api_types::{Authid, Userid, User, ApiToken, ROLE_ADMIN}; | ||||
| use pbs_api_types::{ApiToken, Authid, User, Userid, ROLE_ADMIN}; | ||||
|  | ||||
| use crate::acl::{AclTree, ROLE_NAMES}; | ||||
| use crate::ConfigVersionCache; | ||||
| @ -27,13 +27,14 @@ struct ConfigCache { | ||||
| } | ||||
|  | ||||
| lazy_static! { | ||||
|     static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new( | ||||
|         ConfigCache { data: None, last_update: 0, last_user_cache_generation: 0 } | ||||
|     ); | ||||
|     static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new(ConfigCache { | ||||
|         data: None, | ||||
|         last_update: 0, | ||||
|         last_user_cache_generation: 0 | ||||
|     }); | ||||
| } | ||||
|  | ||||
| impl CachedUserInfo { | ||||
|  | ||||
|     /// Returns a cached instance (up to 5 seconds old). | ||||
|     pub fn new() -> Result<Arc<Self>, Error> { | ||||
|         let now = epoch_i64(); | ||||
| @ -41,10 +42,11 @@ impl CachedUserInfo { | ||||
|         let version_cache = ConfigVersionCache::new()?; | ||||
|         let user_cache_generation = version_cache.user_cache_generation(); | ||||
|  | ||||
|         { // limit scope | ||||
|         { | ||||
|             // limit scope | ||||
|             let cache = CACHED_CONFIG.read().unwrap(); | ||||
|             if (user_cache_generation == cache.last_user_cache_generation) && | ||||
|                 ((now - cache.last_update) < 5) | ||||
|             if (user_cache_generation == cache.last_user_cache_generation) | ||||
|                 && ((now - cache.last_update) < 5) | ||||
|             { | ||||
|                 if let Some(ref config) = cache.data { | ||||
|                     return Ok(config.clone()); | ||||
| @ -92,7 +94,10 @@ impl CachedUserInfo { | ||||
|         } | ||||
|  | ||||
|         if auth_id.is_token() { | ||||
|             if let Ok(info) = self.user_cfg.lookup::<ApiToken>("token", &auth_id.to_string()) { | ||||
|             if let Ok(info) = self | ||||
|                 .user_cfg | ||||
|                 .lookup::<ApiToken>("token", &auth_id.to_string()) | ||||
|             { | ||||
|                 return info.is_active(); | ||||
|             } else { | ||||
|                 return false; | ||||
| @ -157,14 +162,14 @@ impl CachedUserInfo { | ||||
|             // limit privs to that of owning user | ||||
|             let user_auth_id = Authid::from(auth_id.user().clone()); | ||||
|             privs &= self.lookup_privs(&user_auth_id, path); | ||||
|             let (owner_privs, owner_propagated_privs) = self.lookup_privs_details(&user_auth_id, path); | ||||
|             let (owner_privs, owner_propagated_privs) = | ||||
|                 self.lookup_privs_details(&user_auth_id, path); | ||||
|             privs &= owner_privs; | ||||
|             propagated_privs &= owner_propagated_privs; | ||||
|         } | ||||
|  | ||||
|         (privs, propagated_privs) | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| impl UserInformation for CachedUserInfo { | ||||
|  | ||||
| @ -1,16 +1,17 @@ | ||||
| use std::mem::{ManuallyDrop, MaybeUninit}; | ||||
| use std::path::Path; | ||||
| use std::sync::Arc; | ||||
| use std::sync::atomic::{AtomicUsize, Ordering}; | ||||
| use std::mem::{MaybeUninit, ManuallyDrop}; | ||||
| use std::sync::Arc; | ||||
|  | ||||
| use anyhow::{bail, Error}; | ||||
| use once_cell::sync::OnceCell; | ||||
| use nix::sys::stat::Mode; | ||||
| use once_cell::sync::OnceCell; | ||||
|  | ||||
| use proxmox_sys::fs::{create_path, CreateOptions}; | ||||
|  | ||||
| // openssl::sha::sha256(b"Proxmox Backup ConfigVersionCache v1.0")[0..8]; | ||||
| pub const PROXMOX_BACKUP_CONFIG_VERSION_CACHE_MAGIC_1_0: [u8; 8] = [25, 198, 168, 230, 154, 132, 143, 131]; | ||||
| pub const PROXMOX_BACKUP_CONFIG_VERSION_CACHE_MAGIC_1_0: [u8; 8] = | ||||
|     [25, 198, 168, 230, 154, 132, 143, 131]; | ||||
|  | ||||
| const FILE_PATH: &str = pbs_buildcfg::rundir!("/shmem/config-versions"); | ||||
|  | ||||
| @ -26,7 +27,6 @@ struct ConfigVersionCacheDataInner { | ||||
|     traffic_control_generation: AtomicUsize, | ||||
|     // datastore (datastore.cfg) generation/version | ||||
|     datastore_generation: AtomicUsize, | ||||
|  | ||||
|     // Add further atomics here | ||||
| } | ||||
|  | ||||
| @ -76,15 +76,13 @@ impl Init for ConfigVersionCacheData { | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| pub struct ConfigVersionCache { | ||||
|     shmem: SharedMemory<ConfigVersionCacheData> | ||||
|     shmem: SharedMemory<ConfigVersionCacheData>, | ||||
| } | ||||
|  | ||||
| static INSTANCE: OnceCell<Arc< ConfigVersionCache>> = OnceCell::new(); | ||||
| static INSTANCE: OnceCell<Arc<ConfigVersionCache>> = OnceCell::new(); | ||||
|  | ||||
| impl ConfigVersionCache { | ||||
|  | ||||
|     /// Open the memory based communication channel singleton. | ||||
|     pub fn new() -> Result<Arc<Self>, Error> { | ||||
|         INSTANCE.get_or_try_init(Self::open).map(Arc::clone) | ||||
| @ -101,45 +99,47 @@ impl ConfigVersionCache { | ||||
|  | ||||
|         let file_path = Path::new(FILE_PATH); | ||||
|         let dir_path = file_path.parent().unwrap(); | ||||
|          | ||||
|         create_path( | ||||
|             dir_path, | ||||
|             Some(dir_opts.clone()), | ||||
|             Some(dir_opts))?; | ||||
|  | ||||
|         create_path(dir_path, Some(dir_opts.clone()), Some(dir_opts))?; | ||||
|  | ||||
|         let file_opts = CreateOptions::new() | ||||
|             .perm(Mode::from_bits_truncate(0o660)) | ||||
|             .owner(user.uid) | ||||
|             .group(user.gid); | ||||
|  | ||||
|         let shmem: SharedMemory<ConfigVersionCacheData> = | ||||
|             SharedMemory::open(file_path, file_opts)?; | ||||
|         let shmem: SharedMemory<ConfigVersionCacheData> = SharedMemory::open(file_path, file_opts)?; | ||||
|  | ||||
|         Ok(Arc::new(Self { shmem })) | ||||
|     } | ||||
|  | ||||
|     /// Returns the user cache generation number. | ||||
|     pub fn user_cache_generation(&self) -> usize { | ||||
|         self.shmem.data() | ||||
|             .user_cache_generation.load(Ordering::Acquire) | ||||
|         self.shmem | ||||
|             .data() | ||||
|             .user_cache_generation | ||||
|             .load(Ordering::Acquire) | ||||
|     } | ||||
|  | ||||
|     /// Increase the user cache generation number. | ||||
|     pub fn increase_user_cache_generation(&self) { | ||||
|         self.shmem.data() | ||||
|         self.shmem | ||||
|             .data() | ||||
|             .user_cache_generation | ||||
|             .fetch_add(1, Ordering::AcqRel); | ||||
|     } | ||||
|  | ||||
|     /// Returns the traffic control generation number. | ||||
|     pub fn traffic_control_generation(&self) -> usize { | ||||
|         self.shmem.data() | ||||
|             .traffic_control_generation.load(Ordering::Acquire) | ||||
|         self.shmem | ||||
|             .data() | ||||
|             .traffic_control_generation | ||||
|             .load(Ordering::Acquire) | ||||
|     } | ||||
|  | ||||
|     /// Increase the traffic control generation number. | ||||
|     pub fn increase_traffic_control_generation(&self) { | ||||
|         self.shmem.data() | ||||
|         self.shmem | ||||
|             .data() | ||||
|             .traffic_control_generation | ||||
|             .fetch_add(1, Ordering::AcqRel); | ||||
|     } | ||||
| @ -156,6 +156,7 @@ impl ConfigVersionCache { | ||||
|     pub fn increase_datastore_generation(&self) -> usize { | ||||
|         self.shmem | ||||
|             .data() | ||||
|             .datastore_generation.fetch_add(1, Ordering::Acquire) | ||||
|             .datastore_generation | ||||
|             .fetch_add(1, Ordering::Acquire) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| use anyhow::{Error}; | ||||
| use anyhow::Error; | ||||
| use lazy_static::lazy_static; | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| @ -19,7 +19,11 @@ fn init() -> SectionConfig { | ||||
|         _ => unreachable!(), | ||||
|     }; | ||||
|  | ||||
|     let plugin = SectionConfigPlugin::new("datastore".to_string(), Some(String::from("name")), obj_schema); | ||||
|     let plugin = SectionConfigPlugin::new( | ||||
|         "datastore".to_string(), | ||||
|         Some(String::from("name")), | ||||
|         obj_schema, | ||||
|     ); | ||||
|     let mut config = SectionConfig::new(&DATASTORE_SCHEMA); | ||||
|     config.register_plugin(plugin); | ||||
|  | ||||
| @ -34,8 +38,7 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | ||||
|     open_backup_lockfile(DATASTORE_CFG_LOCKFILE, None, true) | ||||
| } | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||
|     let content = proxmox_sys::fs::file_read_optional_string(DATASTORE_CFG_FILENAME)? | ||||
|         .unwrap_or_else(|| "".to_string()); | ||||
|  | ||||
| @ -96,5 +99,7 @@ pub fn complete_acl_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<St | ||||
| pub fn complete_calendar_event(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||
|     // just give some hints about possible values | ||||
|     ["minutely", "hourly", "daily", "mon..fri", "0:0"] | ||||
|         .iter().map(|s| String::from(*s)).collect() | ||||
|         .iter() | ||||
|         .map(|s| String::from(*s)) | ||||
|         .collect() | ||||
| } | ||||
|  | ||||
| @ -1,26 +1,29 @@ | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use anyhow::{Error}; | ||||
| use anyhow::Error; | ||||
| use lazy_static::lazy_static; | ||||
|  | ||||
| use proxmox_schema::{ApiType, Schema}; | ||||
| use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | ||||
|  | ||||
| use pbs_api_types::{OpenIdRealmConfig, REALM_ID_SCHEMA}; | ||||
| use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | ||||
| use pbs_api_types::{OpenIdRealmConfig, REALM_ID_SCHEMA}; | ||||
|  | ||||
| lazy_static! { | ||||
|     pub static ref CONFIG: SectionConfig = init(); | ||||
| } | ||||
|  | ||||
|  | ||||
| fn init() -> SectionConfig { | ||||
|     let obj_schema = match OpenIdRealmConfig::API_SCHEMA { | ||||
|         Schema::Object(ref obj_schema) => obj_schema, | ||||
|         _ => unreachable!(), | ||||
|     }; | ||||
|  | ||||
|     let plugin = SectionConfigPlugin::new("openid".to_string(), Some(String::from("realm")), obj_schema); | ||||
|     let plugin = SectionConfigPlugin::new( | ||||
|         "openid".to_string(), | ||||
|         Some(String::from("realm")), | ||||
|         obj_schema, | ||||
|     ); | ||||
|     let mut config = SectionConfig::new(&REALM_ID_SCHEMA); | ||||
|     config.register_plugin(plugin); | ||||
|  | ||||
| @ -35,8 +38,7 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | ||||
|     open_backup_lockfile(DOMAINS_CFG_LOCKFILE, None, true) | ||||
| } | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||
|     let content = proxmox_sys::fs::file_read_optional_string(DOMAINS_CFG_FILENAME)? | ||||
|         .unwrap_or_else(|| "".to_string()); | ||||
|  | ||||
| @ -60,8 +62,16 @@ pub fn complete_realm_name(_arg: &str, _param: &HashMap<String, String>) -> Vec< | ||||
|  | ||||
| pub fn complete_openid_realm_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||
|     match config() { | ||||
|         Ok((data, _digest)) => data.sections.iter() | ||||
|             .filter_map(|(id, (t, _))| if t == "openid" { Some(id.to_string()) } else { None }) | ||||
|         Ok((data, _digest)) => data | ||||
|             .sections | ||||
|             .iter() | ||||
|             .filter_map(|(id, (t, _))| { | ||||
|                 if t == "openid" { | ||||
|                     Some(id.to_string()) | ||||
|                 } else { | ||||
|                     None | ||||
|                 } | ||||
|             }) | ||||
|             .collect(), | ||||
|         Err(_) => return vec![], | ||||
|     } | ||||
|  | ||||
| @ -21,17 +21,13 @@ use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlug | ||||
|  | ||||
| use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | ||||
|  | ||||
| use pbs_api_types::{ | ||||
|     DRIVE_NAME_SCHEMA, VirtualTapeDrive, LtoTapeDrive, ScsiTapeChanger, | ||||
| }; | ||||
|  | ||||
| use pbs_api_types::{LtoTapeDrive, ScsiTapeChanger, VirtualTapeDrive, DRIVE_NAME_SCHEMA}; | ||||
|  | ||||
| lazy_static! { | ||||
|     /// Static [`SectionConfig`] to access parser/writer functions. | ||||
|     pub static ref CONFIG: SectionConfig = init(); | ||||
| } | ||||
|  | ||||
|  | ||||
| fn init() -> SectionConfig { | ||||
|     let mut config = SectionConfig::new(&DRIVE_NAME_SCHEMA); | ||||
|  | ||||
| @ -39,7 +35,8 @@ fn init() -> SectionConfig { | ||||
|         Schema::Object(ref obj_schema) => obj_schema, | ||||
|         _ => unreachable!(), | ||||
|     }; | ||||
|     let plugin = SectionConfigPlugin::new("virtual".to_string(), Some("name".to_string()), obj_schema); | ||||
|     let plugin = | ||||
|         SectionConfigPlugin::new("virtual".to_string(), Some("name".to_string()), obj_schema); | ||||
|     config.register_plugin(plugin); | ||||
|  | ||||
|     let obj_schema = match LtoTapeDrive::API_SCHEMA { | ||||
| @ -53,7 +50,8 @@ fn init() -> SectionConfig { | ||||
|         Schema::Object(ref obj_schema) => obj_schema, | ||||
|         _ => unreachable!(), | ||||
|     }; | ||||
|     let plugin = SectionConfigPlugin::new("changer".to_string(), Some("name".to_string()), obj_schema); | ||||
|     let plugin = | ||||
|         SectionConfigPlugin::new("changer".to_string(), Some("name".to_string()), obj_schema); | ||||
|     config.register_plugin(plugin); | ||||
|     config | ||||
| } | ||||
| @ -69,8 +67,7 @@ pub fn lock() -> Result<BackupLockGuard, Error> { | ||||
| } | ||||
|  | ||||
| /// Read and parse the configuration file | ||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||
|     let content = proxmox_sys::fs::file_read_optional_string(DRIVE_CFG_FILENAME)? | ||||
|         .unwrap_or_else(|| "".to_string()); | ||||
|  | ||||
| @ -98,15 +95,12 @@ pub fn check_drive_exists(config: &SectionConfigData, drive: &str) -> Result<(), | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| // shell completion helper | ||||
|  | ||||
| /// List all drive names | ||||
| pub fn complete_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||
|     match config() { | ||||
|         Ok((data, _digest)) => data.sections.iter() | ||||
|             .map(|(id, _)| id.to_string()) | ||||
|             .collect(), | ||||
|         Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(), | ||||
|         Err(_) => return vec![], | ||||
|     } | ||||
| } | ||||
| @ -114,10 +108,10 @@ pub fn complete_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec< | ||||
| /// List Lto tape drives | ||||
| pub fn complete_lto_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||
|     match config() { | ||||
|         Ok((data, _digest)) => data.sections.iter() | ||||
|             .filter(|(_id, (section_type, _))| { | ||||
|                 section_type == "lto" | ||||
|             }) | ||||
|         Ok((data, _digest)) => data | ||||
|             .sections | ||||
|             .iter() | ||||
|             .filter(|(_id, (section_type, _))| section_type == "lto") | ||||
|             .map(|(id, _)| id.to_string()) | ||||
|             .collect(), | ||||
|         Err(_) => return vec![], | ||||
| @ -127,10 +121,10 @@ pub fn complete_lto_drive_name(_arg: &str, _param: &HashMap<String, String>) -> | ||||
| /// List Scsi tape changer names | ||||
| pub fn complete_changer_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||
|     match config() { | ||||
|         Ok((data, _digest)) => data.sections.iter() | ||||
|             .filter(|(_id, (section_type, _))| { | ||||
|                 section_type == "changer" | ||||
|             }) | ||||
|         Ok((data, _digest)) => data | ||||
|             .sections | ||||
|             .iter() | ||||
|             .filter(|(_id, (section_type, _))| section_type == "changer") | ||||
|             .map(|(id, _)| id.to_string()) | ||||
|             .collect(), | ||||
|         Err(_) => return vec![], | ||||
|  | ||||
| @ -4,10 +4,10 @@ use std::path::Path; | ||||
| use anyhow::{bail, format_err, Context, Error}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use proxmox_sys::fs::{file_get_contents, replace_file, CreateOptions}; | ||||
| use proxmox_lang::try_block; | ||||
| use proxmox_sys::fs::{file_get_contents, replace_file, CreateOptions}; | ||||
|  | ||||
| use pbs_api_types::{Kdf, KeyInfo, Fingerprint}; | ||||
| use pbs_api_types::{Fingerprint, Kdf, KeyInfo}; | ||||
|  | ||||
| use pbs_tools::crypt_config::CryptConfig; | ||||
|  | ||||
| @ -29,28 +29,19 @@ pub enum KeyDerivationConfig { | ||||
| } | ||||
|  | ||||
| impl KeyDerivationConfig { | ||||
|  | ||||
|     /// Derive a key from provided passphrase | ||||
|     pub fn derive_key(&self, passphrase: &[u8]) -> Result<[u8; 32], Error> { | ||||
|  | ||||
|         let mut key = [0u8; 32]; | ||||
|  | ||||
|         match self { | ||||
|             KeyDerivationConfig::Scrypt { n, r, p, salt } => { | ||||
|                 // estimated scrypt memory usage is 128*r*n*p | ||||
|                 openssl::pkcs5::scrypt( | ||||
|                     passphrase, | ||||
|                     salt, | ||||
|                     *n, *r, *p, | ||||
|                     1025*1024*1024, | ||||
|                     &mut key, | ||||
|                 )?; | ||||
|                 openssl::pkcs5::scrypt(passphrase, salt, *n, *r, *p, 1025 * 1024 * 1024, &mut key)?; | ||||
|  | ||||
|                 Ok(key) | ||||
|             } | ||||
|             KeyDerivationConfig::PBKDF2 { iter, salt } => { | ||||
|  | ||||
|                  openssl::pkcs5::pbkdf2_hmac( | ||||
|                 openssl::pkcs5::pbkdf2_hmac( | ||||
|                     passphrase, | ||||
|                     salt, | ||||
|                     *iter, | ||||
| @ -97,19 +88,15 @@ impl From<&KeyConfig> for KeyInfo { | ||||
|             }, | ||||
|             created: key_config.created, | ||||
|             modified: key_config.modified, | ||||
|             fingerprint: key_config | ||||
|                 .fingerprint | ||||
|                 .as_ref() | ||||
|                 .map(|fp| fp.signature()), | ||||
|             fingerprint: key_config.fingerprint.as_ref().map(|fp| fp.signature()), | ||||
|             hint: key_config.hint.clone(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl KeyConfig  { | ||||
|  | ||||
| impl KeyConfig { | ||||
|     /// Creates a new key using random data, protected by passphrase. | ||||
|     pub fn new(passphrase: &[u8], kdf: Kdf) -> Result<([u8;32], Self), Error> { | ||||
|     pub fn new(passphrase: &[u8], kdf: Kdf) -> Result<([u8; 32], Self), Error> { | ||||
|         let mut key = [0u8; 32]; | ||||
|         proxmox_sys::linux::fill_with_random_data(&mut key)?; | ||||
|         let key_config = Self::with_key(&key, passphrase, kdf)?; | ||||
| @ -134,12 +121,7 @@ impl KeyConfig  { | ||||
|     } | ||||
|  | ||||
|     /// Creates a new instance, protect raw_key with passphrase. | ||||
|     pub fn with_key( | ||||
|         raw_key: &[u8; 32], | ||||
|         passphrase: &[u8], | ||||
|         kdf: Kdf, | ||||
|     ) -> Result<Self, Error> { | ||||
|  | ||||
|     pub fn with_key(raw_key: &[u8; 32], passphrase: &[u8], kdf: Kdf) -> Result<Self, Error> { | ||||
|         if raw_key.len() != 32 { | ||||
|             bail!("got strange key length ({} != 32)", raw_key.len()) | ||||
|         } | ||||
| @ -153,10 +135,7 @@ impl KeyConfig  { | ||||
|                 p: 1, | ||||
|                 salt, | ||||
|             }, | ||||
|             Kdf::PBKDF2 => KeyDerivationConfig::PBKDF2 { | ||||
|                 iter: 65535, | ||||
|                 salt, | ||||
|             }, | ||||
|             Kdf::PBKDF2 => KeyDerivationConfig::PBKDF2 { iter: 65535, salt }, | ||||
|             Kdf::None => { | ||||
|                 bail!("No key derivation function specified"); | ||||
|             } | ||||
| @ -169,14 +148,8 @@ impl KeyConfig  { | ||||
|         let iv = proxmox_sys::linux::random_data(16)?; | ||||
|         let mut tag = [0u8; 16]; | ||||
|  | ||||
|         let encrypted_key = openssl::symm::encrypt_aead( | ||||
|             cipher, | ||||
|             &derived_key, | ||||
|             Some(&iv), | ||||
|             b"", | ||||
|             raw_key, | ||||
|             &mut tag, | ||||
|         )?; | ||||
|         let encrypted_key = | ||||
|             openssl::symm::encrypt_aead(cipher, &derived_key, Some(&iv), b"", raw_key, &mut tag)?; | ||||
|  | ||||
|         let mut enc_data = vec![]; | ||||
|         enc_data.extend_from_slice(&iv); | ||||
| @ -210,12 +183,10 @@ impl KeyConfig  { | ||||
|     pub fn decrypt( | ||||
|         &self, | ||||
|         passphrase: &dyn Fn() -> Result<Vec<u8>, Error>, | ||||
|     ) -> Result<([u8;32], i64, Fingerprint), Error> { | ||||
|  | ||||
|     ) -> Result<([u8; 32], i64, Fingerprint), Error> { | ||||
|         let raw_data = &self.data; | ||||
|  | ||||
|         let key = if let Some(ref kdf) = self.kdf { | ||||
|  | ||||
|             let passphrase = passphrase()?; | ||||
|             if passphrase.len() < 5 { | ||||
|                 bail!("Passphrase is too short!"); | ||||
| @ -232,24 +203,15 @@ impl KeyConfig  { | ||||
|  | ||||
|             let cipher = openssl::symm::Cipher::aes_256_gcm(); | ||||
|  | ||||
|             openssl::symm::decrypt_aead( | ||||
|                 cipher, | ||||
|                 &derived_key, | ||||
|                 Some(iv), | ||||
|                 b"", | ||||
|                 enc_data, | ||||
|                 tag, | ||||
|             ).map_err(|err| { | ||||
|                 match self.hint { | ||||
|             openssl::symm::decrypt_aead(cipher, &derived_key, Some(iv), b"", enc_data, tag) | ||||
|                 .map_err(|err| match self.hint { | ||||
|                     Some(ref hint) => { | ||||
|                         format_err!("Unable to decrypt key (password hint: {})", hint) | ||||
|                     } | ||||
|                     None => { | ||||
|                         format_err!("Unable to decrypt key (wrong password?) - {}", err) | ||||
|                     } | ||||
|                 } | ||||
|             })? | ||||
|  | ||||
|                 })? | ||||
|         } else { | ||||
|             raw_data.clone() | ||||
|         }; | ||||
| @ -263,7 +225,8 @@ impl KeyConfig  { | ||||
|             if &fingerprint != stored_fingerprint { | ||||
|                 bail!( | ||||
|                     "KeyConfig contains wrong fingerprint {}, contained key has fingerprint {}", | ||||
|                     stored_fingerprint, fingerprint | ||||
|                     stored_fingerprint, | ||||
|                     fingerprint | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
| @ -273,7 +236,6 @@ impl KeyConfig  { | ||||
|  | ||||
|     /// Store a KeyConfig to path | ||||
|     pub fn store<P: AsRef<Path>>(&self, path: P, replace: bool) -> Result<(), Error> { | ||||
|  | ||||
|         let path: &Path = path.as_ref(); | ||||
|  | ||||
|         let data = serde_json::to_string(self)?; | ||||
| @ -295,7 +257,8 @@ impl KeyConfig  { | ||||
|             } | ||||
|  | ||||
|             Ok(()) | ||||
|         }).map_err(|err: Error| format_err!("Unable to store key file {:?} - {}", path, err))?; | ||||
|         }) | ||||
|         .map_err(|err: Error| format_err!("Unable to store key file {:?} - {}", path, err))?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
| @ -305,7 +268,7 @@ impl KeyConfig  { | ||||
| pub fn load_and_decrypt_key( | ||||
|     path: &std::path::Path, | ||||
|     passphrase: &dyn Fn() -> Result<Vec<u8>, Error>, | ||||
| ) -> Result<([u8;32], i64, Fingerprint), Error> { | ||||
| ) -> Result<([u8; 32], i64, Fingerprint), Error> { | ||||
|     decrypt_key(&file_get_contents(&path)?, passphrase) | ||||
|         .with_context(|| format!("failed to load decryption key from {:?}", path)) | ||||
| } | ||||
| @ -314,7 +277,7 @@ pub fn load_and_decrypt_key( | ||||
| pub fn decrypt_key( | ||||
|     mut keydata: &[u8], | ||||
|     passphrase: &dyn Fn() -> Result<Vec<u8>, Error>, | ||||
| ) -> Result<([u8;32], i64, Fingerprint), Error> { | ||||
| ) -> Result<([u8; 32], i64, Fingerprint), Error> { | ||||
|     let key_config: KeyConfig = serde_json::from_reader(&mut keydata)?; | ||||
|     key_config.decrypt(passphrase) | ||||
| } | ||||
| @ -382,8 +345,7 @@ fn encrypt_decrypt_test() -> Result<(), Error> { | ||||
|  | ||||
|     let encrypted = rsa_encrypt_key_config(public, &key).expect("encryption failed"); | ||||
|     let (decrypted, created, fingerprint) = | ||||
|         rsa_decrypt_key_config(private, &encrypted, &passphrase) | ||||
|             .expect("decryption failed"); | ||||
|         rsa_decrypt_key_config(private, &encrypted, &passphrase).expect("decryption failed"); | ||||
|  | ||||
|     assert_eq!(key.created, created); | ||||
|     assert_eq!(key.data, decrypted); | ||||
| @ -404,12 +366,13 @@ fn fingerprint_checks() -> Result<(), Error> { | ||||
|     }; | ||||
|  | ||||
|     let expected_fingerprint = Fingerprint::new([ | ||||
|             14, 171, 212, 70, 11, 110, 185, 202, 52, 80, 35, 222, 226, 183, 120, 199, 144, 229, 74, | ||||
|             22, 131, 185, 101, 156, 10, 87, 174, 25, 144, 144, 21, 155, | ||||
|         ]); | ||||
|         14, 171, 212, 70, 11, 110, 185, 202, 52, 80, 35, 222, 226, 183, 120, 199, 144, 229, 74, 22, | ||||
|         131, 185, 101, 156, 10, 87, 174, 25, 144, 144, 21, 155, | ||||
|     ]); | ||||
|  | ||||
|     let mut data = serde_json::to_vec(&key).expect("encoding KeyConfig failed"); | ||||
|     decrypt_key(&mut data, &{ || { Ok(Vec::new()) }}).expect_err("decoding KeyConfig with wrong fingerprint worked"); | ||||
|     decrypt_key(&mut data, &{ || Ok(Vec::new()) }) | ||||
|         .expect_err("decoding KeyConfig with wrong fingerprint worked"); | ||||
|  | ||||
|     let key = KeyConfig { | ||||
|         kdf: None, | ||||
| @ -420,9 +383,9 @@ fn fingerprint_checks() -> Result<(), Error> { | ||||
|         hint: None, | ||||
|     }; | ||||
|  | ||||
|  | ||||
|     let mut data = serde_json::to_vec(&key).expect("encoding KeyConfig failed"); | ||||
|     let (key_data, created, fingerprint) = decrypt_key(&mut data, &{ || { Ok(Vec::new()) }}).expect("decoding KeyConfig without fingerprint failed"); | ||||
|     let (key_data, created, fingerprint) = decrypt_key(&mut data, &{ || Ok(Vec::new()) }) | ||||
|         .expect("decoding KeyConfig without fingerprint failed"); | ||||
|  | ||||
|     assert_eq!(key.data, key_data); | ||||
|     assert_eq!(key.created, created); | ||||
|  | ||||
| @ -22,7 +22,7 @@ pub use config_version_cache::ConfigVersionCache; | ||||
| use anyhow::{format_err, Error}; | ||||
| use nix::unistd::{Gid, Group, Uid, User}; | ||||
|  | ||||
| pub use pbs_buildcfg::{BACKUP_USER_NAME, BACKUP_GROUP_NAME}; | ||||
| pub use pbs_buildcfg::{BACKUP_GROUP_NAME, BACKUP_USER_NAME}; | ||||
|  | ||||
| /// Return User info for the 'backup' user (``getpwnam_r(3)``) | ||||
| pub fn backup_user() -> Result<nix::unistd::User, Error> { | ||||
| @ -79,10 +79,7 @@ pub fn open_backup_lockfile<P: AsRef<std::path::Path>>( | ||||
| /// Atomically write data to file owned by "root:backup" with permission "0640" | ||||
| /// | ||||
| /// Only the superuser can write those files, but group 'backup' can read them. | ||||
| pub fn replace_backup_config<P: AsRef<std::path::Path>>( | ||||
|     path: P, | ||||
|     data: &[u8], | ||||
| ) -> Result<(), Error> { | ||||
| pub fn replace_backup_config<P: AsRef<std::path::Path>>(path: P, data: &[u8]) -> Result<(), Error> { | ||||
|     let backup_user = backup_user()?; | ||||
|     let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); | ||||
|     // set the correct owner/group/permissions while saving file | ||||
| @ -100,10 +97,7 @@ pub fn replace_backup_config<P: AsRef<std::path::Path>>( | ||||
| /// Atomically write data to file owned by "root:root" with permission "0600" | ||||
| /// | ||||
| /// Only the superuser can read and write those files. | ||||
| pub fn replace_secret_config<P: AsRef<std::path::Path>>( | ||||
|     path: P, | ||||
|     data: &[u8], | ||||
| ) -> Result<(), Error> { | ||||
| pub fn replace_secret_config<P: AsRef<std::path::Path>>(path: P, data: &[u8]) -> Result<(), Error> { | ||||
|     let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600); | ||||
|     // set the correct owner/group/permissions while saving file | ||||
|     // owner(rw) = root, group(r)= root | ||||
|  | ||||
| @ -14,7 +14,7 @@ use lazy_static::lazy_static; | ||||
| use proxmox_schema::*; | ||||
| use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | ||||
|  | ||||
| use pbs_api_types::{MEDIA_POOL_NAME_SCHEMA, MediaPoolConfig}; | ||||
| use pbs_api_types::{MediaPoolConfig, MEDIA_POOL_NAME_SCHEMA}; | ||||
|  | ||||
| use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | ||||
|  | ||||
| @ -47,8 +47,7 @@ pub fn lock() -> Result<BackupLockGuard, Error> { | ||||
| } | ||||
|  | ||||
| /// Read and parse the configuration file | ||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||
|     let content = proxmox_sys::fs::file_read_optional_string(MEDIA_POOL_CFG_FILENAME)? | ||||
|         .unwrap_or_else(|| "".to_string()); | ||||
|  | ||||
|  | ||||
| @ -1,12 +1,12 @@ | ||||
| use std::path::Path; | ||||
| use std::process::Command; | ||||
| use std::collections::HashMap; | ||||
| use std::os::unix::io::{AsRawFd, FromRawFd}; | ||||
| use std::path::Path; | ||||
| use std::process::Command; | ||||
|  | ||||
| use anyhow::{Error, bail, format_err}; | ||||
| use anyhow::{bail, format_err, Error}; | ||||
| use lazy_static::lazy_static; | ||||
| use nix::ioctl_read_bad; | ||||
| use nix::sys::socket::{socket, AddressFamily, SockType, SockFlag}; | ||||
| use nix::sys::socket::{socket, AddressFamily, SockFlag, SockType}; | ||||
| use regex::Regex; | ||||
|  | ||||
| use pbs_api_types::*; // for IP macros | ||||
| @ -77,7 +77,13 @@ pub fn check_netmask(mask: u8, is_v6: bool) -> Result<(), Error> { | ||||
|     }; | ||||
|  | ||||
|     if !(mask >= min && mask <= max) { | ||||
|         bail!("{} mask '{}' is out of range ({}..{}).", ver, mask, min, max); | ||||
|         bail!( | ||||
|             "{} mask '{}' is out of range ({}..{}).", | ||||
|             ver, | ||||
|             mask, | ||||
|             min, | ||||
|             max | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     Ok(()) | ||||
| @ -85,14 +91,11 @@ pub fn check_netmask(mask: u8, is_v6: bool) -> Result<(), Error> { | ||||
|  | ||||
| // parse ip address with optional cidr mask | ||||
| pub fn parse_address_or_cidr(cidr: &str) -> Result<(String, Option<u8>, bool), Error> { | ||||
|  | ||||
|     lazy_static! { | ||||
|         pub static ref CIDR_V4_REGEX: Regex = Regex::new( | ||||
|             concat!(r"^(", IPV4RE!(), r")(?:/(\d{1,2}))?$") | ||||
|         ).unwrap(); | ||||
|         pub static ref CIDR_V6_REGEX: Regex = Regex::new( | ||||
|             concat!(r"^(", IPV6RE!(), r")(?:/(\d{1,3}))?$") | ||||
|         ).unwrap(); | ||||
|         pub static ref CIDR_V4_REGEX: Regex = | ||||
|             Regex::new(concat!(r"^(", IPV4RE!(), r")(?:/(\d{1,2}))?$")).unwrap(); | ||||
|         pub static ref CIDR_V6_REGEX: Regex = | ||||
|             Regex::new(concat!(r"^(", IPV6RE!(), r")(?:/(\d{1,3}))?$")).unwrap(); | ||||
|     } | ||||
|  | ||||
|     if let Some(caps) = CIDR_V4_REGEX.captures(cidr) { | ||||
| @ -119,7 +122,6 @@ pub fn parse_address_or_cidr(cidr: &str) -> Result<(String, Option<u8>, bool), E | ||||
| } | ||||
|  | ||||
| pub fn get_network_interfaces() -> Result<HashMap<String, bool>, Error> { | ||||
|  | ||||
|     const PROC_NET_DEV: &str = "/proc/net/dev"; | ||||
|  | ||||
|     #[repr(C)] | ||||
| @ -130,7 +132,7 @@ pub fn get_network_interfaces() -> Result<HashMap<String, bool>, Error> { | ||||
|  | ||||
|     ioctl_read_bad!(get_interface_flags, libc::SIOCGIFFLAGS, ifreq); | ||||
|  | ||||
|     lazy_static!{ | ||||
|     lazy_static! { | ||||
|         static ref IFACE_LINE_REGEX: Regex = Regex::new(r"^\s*([^:\s]+):").unwrap(); | ||||
|     } | ||||
|     let raw = std::fs::read_to_string(PROC_NET_DEV) | ||||
| @ -163,13 +165,26 @@ pub fn get_network_interfaces() -> Result<HashMap<String, bool>, Error> { | ||||
|         if let Some(cap) = IFACE_LINE_REGEX.captures(line) { | ||||
|             let ifname = &cap[1]; | ||||
|  | ||||
|             let mut req = ifreq { ifr_name: *b"0000000000000000", ifru_flags: 0 }; | ||||
|             for (i, b) in std::ffi::CString::new(ifname)?.as_bytes_with_nul().iter().enumerate() { | ||||
|                 if i < (libc::IFNAMSIZ-1) { req.ifr_name[i] = *b as libc::c_uchar; } | ||||
|             let mut req = ifreq { | ||||
|                 ifr_name: *b"0000000000000000", | ||||
|                 ifru_flags: 0, | ||||
|             }; | ||||
|             for (i, b) in std::ffi::CString::new(ifname)? | ||||
|                 .as_bytes_with_nul() | ||||
|                 .iter() | ||||
|                 .enumerate() | ||||
|             { | ||||
|                 if i < (libc::IFNAMSIZ - 1) { | ||||
|                     req.ifr_name[i] = *b as libc::c_uchar; | ||||
|                 } | ||||
|             } | ||||
|             let res = unsafe { get_interface_flags(sock.as_raw_fd(), &mut req)? }; | ||||
|             if res != 0 { | ||||
|                 bail!("ioctl get_interface_flags for '{}' failed ({})", ifname, res); | ||||
|                 bail!( | ||||
|                     "ioctl get_interface_flags for '{}' failed ({})", | ||||
|                     ifname, | ||||
|                     res | ||||
|                 ); | ||||
|             } | ||||
|             let is_up = (req.ifru_flags & (libc::IFF_UP as libc::c_short)) != 0; | ||||
|             interface_list.insert(ifname.to_string(), is_up); | ||||
| @ -180,7 +195,6 @@ pub fn get_network_interfaces() -> Result<HashMap<String, bool>, Error> { | ||||
| } | ||||
|  | ||||
| pub fn compute_file_diff(filename: &str, shadow: &str) -> Result<String, Error> { | ||||
|  | ||||
|     let output = Command::new("diff") | ||||
|         .arg("-b") | ||||
|         .arg("-u") | ||||
| @ -204,7 +218,6 @@ pub fn assert_ifupdown2_installed() -> Result<(), Error> { | ||||
| } | ||||
|  | ||||
| pub fn network_reload() -> Result<(), Error> { | ||||
|  | ||||
|     let output = Command::new("ifreload") | ||||
|         .arg("-a") | ||||
|         .output() | ||||
| @ -213,6 +226,5 @@ pub fn network_reload() -> Result<(), Error> { | ||||
|     proxmox_sys::command::command_output(output, None) | ||||
|         .map_err(|err| format_err!("ifreload failed: {}", err))?; | ||||
|  | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| use std::collections::{HashMap, VecDeque}; | ||||
| use std::io::BufRead; | ||||
| use std::iter::Iterator; | ||||
| use std::collections::{HashMap, VecDeque}; | ||||
|  | ||||
| use lazy_static::lazy_static; | ||||
|  | ||||
| @ -67,10 +67,13 @@ pub struct Lexer<R> { | ||||
|     cur_line: Option<VecDeque<(Token, String)>>, | ||||
| } | ||||
|  | ||||
| impl <R: BufRead> Lexer<R> { | ||||
|  | ||||
| impl<R: BufRead> Lexer<R> { | ||||
|     pub fn new(input: R) -> Self { | ||||
|         Self { input, eof_count: 0, cur_line: None } | ||||
|         Self { | ||||
|             input, | ||||
|             eof_count: 0, | ||||
|             cur_line: None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn split_line(line: &str) -> VecDeque<(Token, String)> { | ||||
| @ -79,10 +82,13 @@ impl <R: BufRead> Lexer<R> { | ||||
|             res.push_back((Token::Comment, comment.trim().to_string())); | ||||
|             return res; | ||||
|         } | ||||
|         let mut list: VecDeque<(Token, String)> = line.split_ascii_whitespace().map(|text| { | ||||
|             let token = KEYWORDS.get(text).unwrap_or(&Token::Text); | ||||
|             (*token, text.to_string()) | ||||
|         }).collect(); | ||||
|         let mut list: VecDeque<(Token, String)> = line | ||||
|             .split_ascii_whitespace() | ||||
|             .map(|text| { | ||||
|                 let token = KEYWORDS.get(text).unwrap_or(&Token::Text); | ||||
|                 (*token, text.to_string()) | ||||
|             }) | ||||
|             .collect(); | ||||
|  | ||||
|         if line.starts_with(|c: char| c.is_ascii_whitespace() && c != '\n') { | ||||
|             list.push_front((Token::Attribute, String::from("\t"))); | ||||
| @ -91,8 +97,7 @@ impl <R: BufRead> Lexer<R> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl <R: BufRead> Iterator for Lexer<R> { | ||||
|  | ||||
| impl<R: BufRead> Iterator for Lexer<R> { | ||||
|     type Item = Result<(Token, String), std::io::Error>; | ||||
|  | ||||
|     fn next(&mut self) -> Option<Self::Item> { | ||||
| @ -102,7 +107,9 @@ impl <R: BufRead> Iterator for Lexer<R> { | ||||
|                 Err(err) => return Some(Err(err)), | ||||
|                 Ok(0) => { | ||||
|                     self.eof_count += 1; | ||||
|                     if self.eof_count == 1 { return Some(Ok((Token::EOF, String::new()))); } | ||||
|                     if self.eof_count == 1 { | ||||
|                         return Some(Ok((Token::EOF, String::new()))); | ||||
|                     } | ||||
|                     return None; | ||||
|                 } | ||||
|                 _ => {} | ||||
| @ -111,7 +118,7 @@ impl <R: BufRead> Iterator for Lexer<R> { | ||||
|         } | ||||
|  | ||||
|         match self.cur_line { | ||||
|             Some(ref mut  cur_line) => { | ||||
|             Some(ref mut cur_line) => { | ||||
|                 if cur_line.is_empty() { | ||||
|                     self.cur_line = None; | ||||
|                     Some(Ok((Token::Newline, String::from("\n")))) | ||||
| @ -120,9 +127,7 @@ impl <R: BufRead> Iterator for Lexer<R> { | ||||
|                     Some(Ok((token, text))) | ||||
|                 } | ||||
|             } | ||||
|             None => { | ||||
|                 None | ||||
|             } | ||||
|             None => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| use std::io::{Write}; | ||||
| use std::collections::{HashSet, HashMap, BTreeMap}; | ||||
| use std::collections::{BTreeMap, HashMap, HashSet}; | ||||
| use std::io::Write; | ||||
|  | ||||
| use anyhow::{Error, format_err, bail}; | ||||
| use serde::de::{value, IntoDeserializer, Deserialize}; | ||||
| use anyhow::{bail, format_err, Error}; | ||||
| use lazy_static::lazy_static; | ||||
| use regex::Regex; | ||||
| use serde::de::{value, Deserialize, IntoDeserializer}; | ||||
|  | ||||
| use proxmox_sys::{fs::replace_file, fs::CreateOptions}; | ||||
|  | ||||
| @ -17,11 +17,13 @@ pub use lexer::*; | ||||
| mod parser; | ||||
| pub use parser::*; | ||||
|  | ||||
| use pbs_api_types::{Interface, NetworkConfigMethod, NetworkInterfaceType, LinuxBondMode, BondXmitHashPolicy}; | ||||
| use pbs_api_types::{ | ||||
|     BondXmitHashPolicy, Interface, LinuxBondMode, NetworkConfigMethod, NetworkInterfaceType, | ||||
| }; | ||||
|  | ||||
| use crate::{open_backup_lockfile, BackupLockGuard}; | ||||
|  | ||||
| lazy_static!{ | ||||
| lazy_static! { | ||||
|     static ref PHYSICAL_NIC_REGEX: Regex = Regex::new(r"^(?:eth\d+|en[^:.]+|ib\d+)$").unwrap(); | ||||
| } | ||||
|  | ||||
| @ -61,7 +63,6 @@ pub fn bond_xmit_hash_policy_to_str(policy: &BondXmitHashPolicy) -> &'static str | ||||
|  | ||||
| // Write attributes not depending on address family | ||||
| fn write_iface_attributes(iface: &Interface, w: &mut dyn Write) -> Result<(), Error> { | ||||
|  | ||||
|     static EMPTY_LIST: Vec<String> = Vec::new(); | ||||
|  | ||||
|     match iface.interface_type { | ||||
| @ -86,10 +87,12 @@ fn write_iface_attributes(iface: &Interface, w: &mut dyn Write) -> Result<(), Er | ||||
|             } | ||||
|  | ||||
|             if let Some(xmit_policy) = &iface.bond_xmit_hash_policy { | ||||
|                 if mode == LinuxBondMode::ieee802_3ad || | ||||
|                     mode == LinuxBondMode::balance_xor | ||||
|                 { | ||||
|                     writeln!(w, "\tbond_xmit_hash_policy {}", bond_xmit_hash_policy_to_str(xmit_policy))?; | ||||
|                 if mode == LinuxBondMode::ieee802_3ad || mode == LinuxBondMode::balance_xor { | ||||
|                     writeln!( | ||||
|                         w, | ||||
|                         "\tbond_xmit_hash_policy {}", | ||||
|                         bond_xmit_hash_policy_to_str(xmit_policy) | ||||
|                     )?; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @ -111,7 +114,11 @@ fn write_iface_attributes(iface: &Interface, w: &mut dyn Write) -> Result<(), Er | ||||
| } | ||||
|  | ||||
| // Write attributes depending on address family inet (IPv4) | ||||
| fn write_iface_attributes_v4(iface: &Interface, w: &mut dyn Write, method: NetworkConfigMethod) -> Result<(), Error> { | ||||
| fn write_iface_attributes_v4( | ||||
|     iface: &Interface, | ||||
|     w: &mut dyn Write, | ||||
|     method: NetworkConfigMethod, | ||||
| ) -> Result<(), Error> { | ||||
|     if method == NetworkConfigMethod::Static { | ||||
|         if let Some(address) = &iface.cidr { | ||||
|             writeln!(w, "\taddress {}", address)?; | ||||
| @ -135,7 +142,11 @@ fn write_iface_attributes_v4(iface: &Interface, w: &mut dyn Write, method: Netwo | ||||
| } | ||||
|  | ||||
| /// Write attributes depending on address family inet6 (IPv6) | ||||
| fn write_iface_attributes_v6(iface: &Interface, w: &mut dyn Write, method: NetworkConfigMethod) -> Result<(), Error> { | ||||
| fn write_iface_attributes_v6( | ||||
|     iface: &Interface, | ||||
|     w: &mut dyn Write, | ||||
|     method: NetworkConfigMethod, | ||||
| ) -> Result<(), Error> { | ||||
|     if method == NetworkConfigMethod::Static { | ||||
|         if let Some(address) = &iface.cidr6 { | ||||
|             writeln!(w, "\taddress {}", address)?; | ||||
| @ -159,7 +170,6 @@ fn write_iface_attributes_v6(iface: &Interface, w: &mut dyn Write, method: Netwo | ||||
| } | ||||
|  | ||||
| fn write_iface(iface: &Interface, w: &mut dyn Write) -> Result<(), Error> { | ||||
|  | ||||
|     fn method_to_str(method: NetworkConfigMethod) -> &'static str { | ||||
|         match method { | ||||
|             NetworkConfigMethod::Static => "static", | ||||
| @ -169,7 +179,9 @@ fn write_iface(iface: &Interface, w: &mut dyn Write) -> Result<(), Error> { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if iface.method.is_none() && iface.method6.is_none() { return Ok(()); } | ||||
|     if iface.method.is_none() && iface.method6.is_none() { | ||||
|         return Ok(()); | ||||
|     } | ||||
|  | ||||
|     if iface.autostart { | ||||
|         writeln!(w, "auto {}", iface.name)?; | ||||
| @ -195,7 +207,8 @@ fn write_iface(iface: &Interface, w: &mut dyn Write) -> Result<(), Error> { | ||||
|         if !skip_v6 { | ||||
|             writeln!(w, "iface {} inet6 {}", iface.name, method_to_str(method6))?; | ||||
|             write_iface_attributes_v6(iface, w, method6)?; | ||||
|             if iface.method.is_none() { // only write common attributes once | ||||
|             if iface.method.is_none() { | ||||
|                 // only write common attributes once | ||||
|                 write_iface_attributes(iface, w)?; | ||||
|             } | ||||
|             writeln!(w)?; | ||||
| @ -220,8 +233,7 @@ pub struct NetworkConfig { | ||||
|  | ||||
| use std::convert::TryFrom; | ||||
|  | ||||
| impl TryFrom<NetworkConfig> for String  { | ||||
|  | ||||
| impl TryFrom<NetworkConfig> for String { | ||||
|     type Error = Error; | ||||
|  | ||||
|     fn try_from(config: NetworkConfig) -> Result<Self, Self::Error> { | ||||
| @ -233,7 +245,6 @@ impl TryFrom<NetworkConfig> for String  { | ||||
| } | ||||
|  | ||||
| impl NetworkConfig { | ||||
|  | ||||
|     pub fn new() -> Self { | ||||
|         Self { | ||||
|             interfaces: BTreeMap::new(), | ||||
| @ -242,16 +253,18 @@ impl NetworkConfig { | ||||
|     } | ||||
|  | ||||
|     pub fn lookup(&self, name: &str) -> Result<&Interface, Error> { | ||||
|         let interface = self.interfaces.get(name).ok_or_else(|| { | ||||
|             format_err!("interface '{}' does not exist.", name) | ||||
|         })?; | ||||
|         let interface = self | ||||
|             .interfaces | ||||
|             .get(name) | ||||
|             .ok_or_else(|| format_err!("interface '{}' does not exist.", name))?; | ||||
|         Ok(interface) | ||||
|     } | ||||
|  | ||||
|     pub fn lookup_mut(&mut self, name: &str) -> Result<&mut Interface, Error> { | ||||
|         let interface = self.interfaces.get_mut(name).ok_or_else(|| { | ||||
|             format_err!("interface '{}' does not exist.", name) | ||||
|         })?; | ||||
|         let interface = self | ||||
|             .interfaces | ||||
|             .get_mut(name) | ||||
|             .ok_or_else(|| format_err!("interface '{}' does not exist.", name))?; | ||||
|         Ok(interface) | ||||
|     } | ||||
|  | ||||
| @ -261,8 +274,12 @@ impl NetworkConfig { | ||||
|         let mut check_port_usage = |iface, ports: &Vec<String>| { | ||||
|             for port in ports.iter() { | ||||
|                 if let Some(prev_iface) = used_ports.get(port) { | ||||
|                     bail!("iface '{}' port '{}' is already used on interface '{}'", | ||||
|                           iface, port, prev_iface); | ||||
|                     bail!( | ||||
|                         "iface '{}' port '{}' is already used on interface '{}'", | ||||
|                         iface, | ||||
|                         port, | ||||
|                         prev_iface | ||||
|                     ); | ||||
|                 } | ||||
|                 used_ports.insert(port.to_string(), iface); | ||||
|             } | ||||
| @ -270,18 +287,25 @@ impl NetworkConfig { | ||||
|         }; | ||||
|  | ||||
|         for (iface, interface) in self.interfaces.iter() { | ||||
|             if let Some(ports) = &interface.bridge_ports { check_port_usage(iface, ports)?; } | ||||
|             if let Some(slaves) = &interface.slaves { check_port_usage(iface, slaves)?; } | ||||
|             if let Some(ports) = &interface.bridge_ports { | ||||
|                 check_port_usage(iface, ports)?; | ||||
|             } | ||||
|             if let Some(slaves) = &interface.slaves { | ||||
|                 check_port_usage(iface, slaves)?; | ||||
|             } | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Check if child mtu is less or equal than parent mtu | ||||
|     pub fn check_mtu(&self, parent_name: &str, child_name: &str) -> Result<(), Error> { | ||||
|  | ||||
|         let parent = self.interfaces.get(parent_name) | ||||
|         let parent = self | ||||
|             .interfaces | ||||
|             .get(parent_name) | ||||
|             .ok_or_else(|| format_err!("check_mtu - missing parent interface '{}'", parent_name))?; | ||||
|         let child = self.interfaces.get(child_name) | ||||
|         let child = self | ||||
|             .interfaces | ||||
|             .get(child_name) | ||||
|             .ok_or_else(|| format_err!("check_mtu - missing child interface '{}'", child_name))?; | ||||
|  | ||||
|         let child_mtu = match child.mtu { | ||||
| @ -301,8 +325,13 @@ impl NetworkConfig { | ||||
|         }; | ||||
|  | ||||
|         if parent_mtu < child_mtu { | ||||
|             bail!("interface '{}' - mtu {} is lower than '{}' - mtu {}\n", | ||||
|                   parent_name, parent_mtu, child_name, child_mtu); | ||||
|             bail!( | ||||
|                 "interface '{}' - mtu {} is lower than '{}' - mtu {}\n", | ||||
|                 parent_name, | ||||
|                 parent_mtu, | ||||
|                 child_name, | ||||
|                 child_mtu | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
| @ -316,8 +345,13 @@ impl NetworkConfig { | ||||
|                     match self.interfaces.get(slave) { | ||||
|                         Some(entry) => { | ||||
|                             if entry.interface_type != NetworkInterfaceType::Eth { | ||||
|                                 bail!("bond '{}' - wrong interface type on slave '{}' ({:?} != {:?})", | ||||
|                                       iface, slave, entry.interface_type, NetworkInterfaceType::Eth); | ||||
|                                 bail!( | ||||
|                                     "bond '{}' - wrong interface type on slave '{}' ({:?} != {:?})", | ||||
|                                     iface, | ||||
|                                     slave, | ||||
|                                     entry.interface_type, | ||||
|                                     NetworkInterfaceType::Eth | ||||
|                                 ); | ||||
|                             } | ||||
|                         } | ||||
|                         None => { | ||||
| @ -333,7 +367,7 @@ impl NetworkConfig { | ||||
|  | ||||
|     /// Check if bridge ports exists | ||||
|     pub fn check_bridge_ports(&self) -> Result<(), Error> { | ||||
|         lazy_static!{ | ||||
|         lazy_static! { | ||||
|             static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^(\S+)\.(\d+)$").unwrap(); | ||||
|         } | ||||
|  | ||||
| @ -341,7 +375,11 @@ impl NetworkConfig { | ||||
|             if let Some(ports) = &interface.bridge_ports { | ||||
|                 for port in ports.iter() { | ||||
|                     let captures = VLAN_INTERFACE_REGEX.captures(port); | ||||
|                     let port = if let Some(ref caps) = captures { &caps[1] } else { port.as_str() }; | ||||
|                     let port = if let Some(ref caps) = captures { | ||||
|                         &caps[1] | ||||
|                     } else { | ||||
|                         port.as_str() | ||||
|                     }; | ||||
|                     if !self.interfaces.contains_key(port) { | ||||
|                         bail!("bridge '{}' - unable to find port '{}'", iface, port); | ||||
|                     } | ||||
| @ -353,7 +391,6 @@ impl NetworkConfig { | ||||
|     } | ||||
|  | ||||
|     pub fn write_config(&self, w: &mut dyn Write) -> Result<(), Error> { | ||||
|  | ||||
|         self.check_port_usage()?; | ||||
|         self.check_bond_slaves()?; | ||||
|         self.check_bridge_ports()?; | ||||
| @ -363,13 +400,15 @@ impl NetworkConfig { | ||||
|         let mut last_entry_was_comment = false; | ||||
|  | ||||
|         for entry in self.order.iter() { | ||||
|              match entry { | ||||
|             match entry { | ||||
|                 NetworkOrderEntry::Comment(comment) => { | ||||
|                     writeln!(w, "#{}", comment)?; | ||||
|                     last_entry_was_comment = true; | ||||
|                 } | ||||
|                 NetworkOrderEntry::Option(option) => { | ||||
|                     if last_entry_was_comment {  writeln!(w)?; } | ||||
|                     if last_entry_was_comment { | ||||
|                         writeln!(w)?; | ||||
|                     } | ||||
|                     last_entry_was_comment = false; | ||||
|                     writeln!(w, "{}", option)?; | ||||
|                     writeln!(w)?; | ||||
| @ -380,10 +419,14 @@ impl NetworkConfig { | ||||
|                         None => continue, | ||||
|                     }; | ||||
|  | ||||
|                     if last_entry_was_comment {  writeln!(w)?; } | ||||
|                     if last_entry_was_comment { | ||||
|                         writeln!(w)?; | ||||
|                     } | ||||
|                     last_entry_was_comment = false; | ||||
|  | ||||
|                     if done.contains(name) { continue; } | ||||
|                     if done.contains(name) { | ||||
|                         continue; | ||||
|                     } | ||||
|                     done.insert(name); | ||||
|  | ||||
|                     write_iface(interface, w)?; | ||||
| @ -392,7 +435,9 @@ impl NetworkConfig { | ||||
|         } | ||||
|  | ||||
|         for (name, interface) in &self.interfaces { | ||||
|             if done.contains(name) { continue; } | ||||
|             if done.contains(name) { | ||||
|                 continue; | ||||
|             } | ||||
|             write_iface(interface, w)?; | ||||
|         } | ||||
|         Ok(()) | ||||
| @ -407,15 +452,16 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | ||||
|     open_backup_lockfile(NETWORK_LOCKFILE, None, true) | ||||
| } | ||||
|  | ||||
| pub fn config() -> Result<(NetworkConfig, [u8;32]), Error> { | ||||
|  | ||||
|     let content = match proxmox_sys::fs::file_get_optional_contents(NETWORK_INTERFACES_NEW_FILENAME)? { | ||||
|         Some(content) => content, | ||||
|         None => { | ||||
|             let content = proxmox_sys::fs::file_get_optional_contents(NETWORK_INTERFACES_FILENAME)?; | ||||
|             content.unwrap_or_default() | ||||
|         } | ||||
|     }; | ||||
| pub fn config() -> Result<(NetworkConfig, [u8; 32]), Error> { | ||||
|     let content = | ||||
|         match proxmox_sys::fs::file_get_optional_contents(NETWORK_INTERFACES_NEW_FILENAME)? { | ||||
|             Some(content) => content, | ||||
|             None => { | ||||
|                 let content = | ||||
|                     proxmox_sys::fs::file_get_optional_contents(NETWORK_INTERFACES_FILENAME)?; | ||||
|                 content.unwrap_or_default() | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|     let digest = openssl::sha::sha256(&content); | ||||
|  | ||||
| @ -427,7 +473,6 @@ pub fn config() -> Result<(NetworkConfig, [u8;32]), Error> { | ||||
| } | ||||
|  | ||||
| pub fn changes() -> Result<String, Error> { | ||||
|  | ||||
|     if !std::path::Path::new(NETWORK_INTERFACES_NEW_FILENAME).exists() { | ||||
|         return Ok(String::new()); | ||||
|     } | ||||
| @ -436,7 +481,6 @@ pub fn changes() -> Result<String, Error> { | ||||
| } | ||||
|  | ||||
| pub fn save_config(config: &NetworkConfig) -> Result<(), Error> { | ||||
|  | ||||
|     let mut raw = Vec::new(); | ||||
|     config.write_config(&mut raw)?; | ||||
|  | ||||
| @ -461,7 +505,6 @@ pub fn complete_interface_name(_arg: &str, _param: &HashMap<String, String>) -> | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| pub fn complete_port_list(arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||
|     let mut ports = Vec::new(); | ||||
|     match config() { | ||||
| @ -476,20 +519,26 @@ pub fn complete_port_list(arg: &str, _param: &HashMap<String, String>) -> Vec<St | ||||
|     }; | ||||
|  | ||||
|     let arg = arg.trim(); | ||||
|     let prefix = if let Some(idx) = arg.rfind(',') { &arg[..idx+1] } else { "" }; | ||||
|     ports.iter().map(|port| format!("{}{}", prefix, port)).collect() | ||||
|     let prefix = if let Some(idx) = arg.rfind(',') { | ||||
|         &arg[..idx + 1] | ||||
|     } else { | ||||
|         "" | ||||
|     }; | ||||
|     ports | ||||
|         .iter() | ||||
|         .map(|port| format!("{}{}", prefix, port)) | ||||
|         .collect() | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
|  | ||||
|     use anyhow::{Error}; | ||||
|     use anyhow::Error; | ||||
|  | ||||
|     use super::*; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_network_config_create_lo_1() -> Result<(), Error> { | ||||
|  | ||||
|         let input = ""; | ||||
|  | ||||
|         let mut parser = NetworkParser::new(input.as_bytes()); | ||||
| @ -515,7 +564,6 @@ mod test { | ||||
|  | ||||
|     #[test] | ||||
|     fn test_network_config_create_lo_2() -> Result<(), Error> { | ||||
|  | ||||
|         let input = "#c1\n\n#c2\n\niface test inet manual\n"; | ||||
|  | ||||
|         let mut parser = NetworkParser::new(input.as_bytes()); | ||||
|  | ||||
| @ -1,15 +1,18 @@ | ||||
| use std::io::{BufRead}; | ||||
| use std::iter::{Peekable, Iterator}; | ||||
| use std::collections::{HashMap, HashSet}; | ||||
| use std::io::BufRead; | ||||
| use std::iter::{Iterator, Peekable}; | ||||
|  | ||||
| use anyhow::{Error, bail, format_err}; | ||||
| use anyhow::{bail, format_err, Error}; | ||||
| use lazy_static::lazy_static; | ||||
| use regex::Regex; | ||||
|  | ||||
| use super::helper::*; | ||||
| use super::lexer::*; | ||||
|  | ||||
| use super::{NetworkConfig, NetworkOrderEntry, Interface, NetworkConfigMethod, NetworkInterfaceType, bond_mode_from_str, bond_xmit_hash_policy_from_str}; | ||||
| use super::{ | ||||
|     bond_mode_from_str, bond_xmit_hash_policy_from_str, Interface, NetworkConfig, | ||||
|     NetworkConfigMethod, NetworkInterfaceType, NetworkOrderEntry, | ||||
| }; | ||||
|  | ||||
| fn set_method_v4(iface: &mut Interface, method: NetworkConfigMethod) -> Result<(), Error> { | ||||
|     if iface.method.is_none() { | ||||
| @ -65,11 +68,18 @@ fn set_gateway_v6(iface: &mut Interface, gateway: String) -> Result<(), Error> { | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn set_interface_type(iface: &mut Interface, interface_type: NetworkInterfaceType) -> Result<(), Error> { | ||||
| fn set_interface_type( | ||||
|     iface: &mut Interface, | ||||
|     interface_type: NetworkInterfaceType, | ||||
| ) -> Result<(), Error> { | ||||
|     if iface.interface_type == NetworkInterfaceType::Unknown { | ||||
|         iface.interface_type = interface_type; | ||||
|     } else if iface.interface_type != interface_type { | ||||
|         bail!("interface type already defined - cannot change from {:?} to {:?}", iface.interface_type, interface_type); | ||||
|         bail!( | ||||
|             "interface type already defined - cannot change from {:?} to {:?}", | ||||
|             iface.interface_type, | ||||
|             interface_type | ||||
|         ); | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
| @ -79,8 +89,7 @@ pub struct NetworkParser<R: BufRead> { | ||||
|     line_nr: usize, | ||||
| } | ||||
|  | ||||
| impl <R: BufRead> NetworkParser<R> { | ||||
|  | ||||
| impl<R: BufRead> NetworkParser<R> { | ||||
|     pub fn new(reader: R) -> Self { | ||||
|         let input = Lexer::new(reader).peekable(); | ||||
|         Self { input, line_nr: 1 } | ||||
| @ -91,9 +100,7 @@ impl <R: BufRead> NetworkParser<R> { | ||||
|             Some(Err(err)) => { | ||||
|                 bail!("input error - {}", err); | ||||
|             } | ||||
|             Some(Ok((token, _))) => { | ||||
|                 Ok(*token) | ||||
|             } | ||||
|             Some(Ok((token, _))) => Ok(*token), | ||||
|             None => { | ||||
|                 bail!("got unexpected end of stream (inside peek)"); | ||||
|             } | ||||
| @ -106,7 +113,9 @@ impl <R: BufRead> NetworkParser<R> { | ||||
|                 bail!("input error - {}", err); | ||||
|             } | ||||
|             Some(Ok((token, text))) => { | ||||
|                 if token == Token::Newline { self.line_nr += 1; } | ||||
|                 if token == Token::Newline { | ||||
|                     self.line_nr += 1; | ||||
|                 } | ||||
|                 Ok((token, text)) | ||||
|             } | ||||
|             None => { | ||||
| @ -136,14 +145,13 @@ impl <R: BufRead> NetworkParser<R> { | ||||
|         loop { | ||||
|             match self.next()? { | ||||
|                 (Token::Text, iface) => { | ||||
|                      auto_flag.insert(iface.to_string()); | ||||
|                     auto_flag.insert(iface.to_string()); | ||||
|                 } | ||||
|                 (Token::Newline, _) => break, | ||||
|                 unexpected => { | ||||
|                     bail!("expected {:?}, got {:?}", Token::Text, unexpected); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
| @ -153,7 +161,7 @@ impl <R: BufRead> NetworkParser<R> { | ||||
|         self.eat(Token::Netmask)?; | ||||
|         let netmask = self.next_text()?; | ||||
|  | ||||
|         let mask = if let Some(mask) = IPV4_MASK_HASH_LOCALNET.get(netmask.as_str())  { | ||||
|         let mask = if let Some(mask) = IPV4_MASK_HASH_LOCALNET.get(netmask.as_str()) { | ||||
|             *mask | ||||
|         } else { | ||||
|             match u8::from_str_radix(netmask.as_str(), 10) { | ||||
| @ -236,7 +244,9 @@ impl <R: BufRead> NetworkParser<R> { | ||||
|             match self.next()? { | ||||
|                 (Token::Newline, _) => return Ok(line), | ||||
|                 (_, text) => { | ||||
|                     if !line.is_empty() { line.push(' '); } | ||||
|                     if !line.is_empty() { | ||||
|                         line.push(' '); | ||||
|                     } | ||||
|                     line.push_str(&text); | ||||
|                 } | ||||
|             } | ||||
| @ -255,7 +265,10 @@ impl <R: BufRead> NetworkParser<R> { | ||||
|                         list.push(text); | ||||
|                     } | ||||
|                 } | ||||
|                 _ => bail!("unable to parse interface list - unexpected token '{:?}'", token), | ||||
|                 _ => bail!( | ||||
|                     "unable to parse interface list - unexpected token '{:?}'", | ||||
|                     token | ||||
|                 ), | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -268,23 +281,28 @@ impl <R: BufRead> NetworkParser<R> { | ||||
|         address_family_v4: bool, | ||||
|         address_family_v6: bool, | ||||
|     ) -> Result<(), Error> { | ||||
|  | ||||
|         let mut netmask = None; | ||||
|         let mut address_list = Vec::new(); | ||||
|  | ||||
|         loop { | ||||
|             match self.peek()? { | ||||
|                 Token::Attribute => { self.eat(Token::Attribute)?; }, | ||||
|                 Token::Attribute => { | ||||
|                     self.eat(Token::Attribute)?; | ||||
|                 } | ||||
|                 Token::Comment => { | ||||
|                     let comment = self.eat(Token::Comment)?; | ||||
|                     if !address_family_v4 && address_family_v6 { | ||||
|                         let mut comments = interface.comments6.take().unwrap_or_default(); | ||||
|                         if !comments.is_empty() { comments.push('\n'); } | ||||
|                         if !comments.is_empty() { | ||||
|                             comments.push('\n'); | ||||
|                         } | ||||
|                         comments.push_str(&comment); | ||||
|                         interface.comments6 = Some(comments); | ||||
|                     } else { | ||||
|                         let mut comments = interface.comments.take().unwrap_or_default(); | ||||
|                         if !comments.is_empty() { comments.push('\n'); } | ||||
|                         if !comments.is_empty() { | ||||
|                             comments.push('\n'); | ||||
|                         } | ||||
|                         comments.push_str(&comment); | ||||
|                         interface.comments = Some(comments); | ||||
|                     } | ||||
| @ -343,7 +361,8 @@ impl <R: BufRead> NetworkParser<R> { | ||||
|                     interface.bond_xmit_hash_policy = Some(policy); | ||||
|                     self.eat(Token::Newline)?; | ||||
|                 } | ||||
|                 _ => { // parse addon attributes | ||||
|                 _ => { | ||||
|                     // parse addon attributes | ||||
|                     let option = self.parse_to_eol()?; | ||||
|                     if !option.is_empty() { | ||||
|                         if !address_family_v4 && address_family_v6 { | ||||
| @ -351,8 +370,8 @@ impl <R: BufRead> NetworkParser<R> { | ||||
|                         } else { | ||||
|                             interface.options.push(option); | ||||
|                         } | ||||
|                    }; | ||||
|                  }, | ||||
|                     }; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -362,7 +381,7 @@ impl <R: BufRead> NetworkParser<R> { | ||||
|                 bail!("unable to apply netmask to multiple addresses (please use cidr notation)"); | ||||
|             } else if address_list.len() == 1 { | ||||
|                 let (mut cidr, mask, is_v6) = address_list.pop().unwrap(); | ||||
|                 if mask.is_some()  { | ||||
|                 if mask.is_some() { | ||||
|                     // address already has a mask  - ignore netmask | ||||
|                 } else { | ||||
|                     check_netmask(netmask, is_v6)?; | ||||
| @ -449,12 +468,18 @@ impl <R: BufRead> NetworkParser<R> { | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub fn parse_interfaces(&mut self, existing_interfaces: Option<&HashMap<String, bool>>) -> Result<NetworkConfig, Error> { | ||||
|     pub fn parse_interfaces( | ||||
|         &mut self, | ||||
|         existing_interfaces: Option<&HashMap<String, bool>>, | ||||
|     ) -> Result<NetworkConfig, Error> { | ||||
|         self._parse_interfaces(existing_interfaces) | ||||
|             .map_err(|err| format_err!("line {}: {}", self.line_nr, err)) | ||||
|     } | ||||
|  | ||||
|     pub fn _parse_interfaces(&mut self, existing_interfaces: Option<&HashMap<String, bool>>) -> Result<NetworkConfig, Error> { | ||||
|     pub fn _parse_interfaces( | ||||
|         &mut self, | ||||
|         existing_interfaces: Option<&HashMap<String, bool>>, | ||||
|     ) -> Result<NetworkConfig, Error> { | ||||
|         let mut config = NetworkConfig::new(); | ||||
|  | ||||
|         let mut auto_flag: HashSet<String> = HashSet::new(); | ||||
| @ -494,31 +519,38 @@ impl <R: BufRead> NetworkParser<R> { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         lazy_static!{ | ||||
|         lazy_static! { | ||||
|             static ref INTERFACE_ALIAS_REGEX: Regex = Regex::new(r"^\S+:\d+$").unwrap(); | ||||
|             static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^\S+\.\d+$").unwrap(); | ||||
|         } | ||||
|  | ||||
|         if let Some(existing_interfaces) = existing_interfaces { | ||||
|             for (iface, active) in existing_interfaces.iter()  { | ||||
|             for (iface, active) in existing_interfaces.iter() { | ||||
|                 if let Some(interface) = config.interfaces.get_mut(iface) { | ||||
|                     interface.active = *active; | ||||
|                     if interface.interface_type == NetworkInterfaceType::Unknown && super::is_physical_nic(iface) { | ||||
|                     if interface.interface_type == NetworkInterfaceType::Unknown | ||||
|                         && super::is_physical_nic(iface) | ||||
|                     { | ||||
|                         interface.interface_type = NetworkInterfaceType::Eth; | ||||
|                     } | ||||
|                 } else if super::is_physical_nic(iface) { // also add all physical NICs | ||||
|                 } else if super::is_physical_nic(iface) { | ||||
|                     // also add all physical NICs | ||||
|                     let mut interface = Interface::new(iface.clone()); | ||||
|                     set_method_v4(&mut interface, NetworkConfigMethod::Manual)?; | ||||
|                     interface.interface_type = NetworkInterfaceType::Eth; | ||||
|                     interface.active = *active; | ||||
|                     config.interfaces.insert(interface.name.clone(), interface); | ||||
|                     config.order.push(NetworkOrderEntry::Iface(iface.to_string())); | ||||
|                     config | ||||
|                         .order | ||||
|                         .push(NetworkOrderEntry::Iface(iface.to_string())); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         for (name, interface) in config.interfaces.iter_mut() { | ||||
|             if interface.interface_type != NetworkInterfaceType::Unknown { continue; } | ||||
|             if interface.interface_type != NetworkInterfaceType::Unknown { | ||||
|                 continue; | ||||
|             } | ||||
|             if name == "lo" { | ||||
|                 interface.interface_type = NetworkInterfaceType::Loopback; | ||||
|                 continue; | ||||
| @ -548,11 +580,14 @@ impl <R: BufRead> NetworkParser<R> { | ||||
|             let mut new_order = Vec::new(); | ||||
|             let mut added_lo = false; | ||||
|             for entry in config.order { | ||||
|                 if added_lo { new_order.push(entry); continue; } // copy the rest | ||||
|                 if added_lo { | ||||
|                     new_order.push(entry); | ||||
|                     continue; | ||||
|                 } // copy the rest | ||||
|                 match entry { | ||||
|                     NetworkOrderEntry::Comment(_) => { | ||||
|                         new_order.push(entry); | ||||
|                      } | ||||
|                     } | ||||
|                     _ => { | ||||
|                         new_order.push(NetworkOrderEntry::Iface(String::from("lo"))); | ||||
|                         added_lo = true; | ||||
|  | ||||
| @ -20,7 +20,8 @@ fn init() -> SectionConfig { | ||||
|         _ => unreachable!(), | ||||
|     }; | ||||
|  | ||||
|     let plugin = SectionConfigPlugin::new("remote".to_string(), Some("name".to_string()), obj_schema); | ||||
|     let plugin = | ||||
|         SectionConfigPlugin::new("remote".to_string(), Some("name".to_string()), obj_schema); | ||||
|     let mut config = SectionConfig::new(&REMOTE_ID_SCHEMA); | ||||
|     config.register_plugin(plugin); | ||||
|  | ||||
| @ -35,8 +36,7 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | ||||
|     open_backup_lockfile(REMOTE_CFG_LOCKFILE, None, true) | ||||
| } | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||
|     let content = proxmox_sys::fs::file_read_optional_string(REMOTE_CFG_FILENAME)? | ||||
|         .unwrap_or_else(|| "".to_string()); | ||||
|  | ||||
|  | ||||
| @ -6,7 +6,7 @@ use lazy_static::lazy_static; | ||||
| use proxmox_schema::{ApiType, Schema}; | ||||
| use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | ||||
|  | ||||
| use pbs_api_types::{JOB_ID_SCHEMA, SyncJobConfig}; | ||||
| use pbs_api_types::{SyncJobConfig, JOB_ID_SCHEMA}; | ||||
|  | ||||
| use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | ||||
|  | ||||
| @ -14,7 +14,6 @@ lazy_static! { | ||||
|     pub static ref CONFIG: SectionConfig = init(); | ||||
| } | ||||
|  | ||||
|  | ||||
| fn init() -> SectionConfig { | ||||
|     let obj_schema = match SyncJobConfig::API_SCHEMA { | ||||
|         Schema::AllOf(ref allof_schema) => allof_schema, | ||||
| @ -36,8 +35,7 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | ||||
|     open_backup_lockfile(SYNC_CFG_LOCKFILE, None, true) | ||||
| } | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||
|     let content = proxmox_sys::fs::file_read_optional_string(SYNC_CFG_FILENAME)? | ||||
|         .unwrap_or_else(|| "".to_string()); | ||||
|  | ||||
|  | ||||
| @ -15,20 +15,17 @@ use std::collections::HashMap; | ||||
| use anyhow::{bail, Error}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use proxmox_sys::fs::file_read_optional_string; | ||||
| use pbs_api_types::Fingerprint; | ||||
| use proxmox_sys::fs::file_read_optional_string; | ||||
|  | ||||
| use crate::key_config::KeyConfig; | ||||
| use crate::{open_backup_lockfile, replace_secret_config, replace_backup_config}; | ||||
| use crate::{open_backup_lockfile, replace_backup_config, replace_secret_config}; | ||||
|  | ||||
| mod hex_key { | ||||
|     use serde::{self, Deserialize, Serializer, Deserializer}; | ||||
|     use hex::FromHex; | ||||
|      | ||||
|     pub fn serialize<S>( | ||||
|         csum: &[u8; 32], | ||||
|         serializer: S, | ||||
|     ) -> Result<S::Ok, S::Error> | ||||
|     use serde::{self, Deserialize, Deserializer, Serializer}; | ||||
|  | ||||
|     pub fn serialize<S>(csum: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error> | ||||
|     where | ||||
|         S: Serializer, | ||||
|     { | ||||
| @ -36,9 +33,7 @@ mod hex_key { | ||||
|         serializer.serialize_str(&s) | ||||
|     } | ||||
|  | ||||
|     pub fn deserialize<'de, D>( | ||||
|         deserializer: D, | ||||
|     ) -> Result<[u8; 32], D::Error> | ||||
|     pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error> | ||||
|     where | ||||
|         D: Deserializer<'de>, | ||||
|     { | ||||
| @ -68,8 +63,7 @@ pub const TAPE_KEY_CONFIG_FILENAME: &str = "/etc/proxmox-backup/tape-encryption- | ||||
| pub const TAPE_KEYS_LOCKFILE: &str = "/etc/proxmox-backup/.tape-encryption-keys.lck"; | ||||
|  | ||||
| /// Load tape encryption keys (plain, unprotected keys) | ||||
| pub fn load_keys() -> Result<(HashMap<Fingerprint, EncryptionKeyInfo>,  [u8;32]), Error> { | ||||
|  | ||||
| pub fn load_keys() -> Result<(HashMap<Fingerprint, EncryptionKeyInfo>, [u8; 32]), Error> { | ||||
|     let content = file_read_optional_string(TAPE_KEYS_FILENAME)?; | ||||
|     let content = content.unwrap_or_else(|| String::from("[]")); | ||||
|  | ||||
| @ -99,8 +93,7 @@ pub fn load_keys() -> Result<(HashMap<Fingerprint, EncryptionKeyInfo>,  [u8;32]) | ||||
| } | ||||
|  | ||||
| /// Load tape encryption key configurations (password protected keys) | ||||
| pub fn load_key_configs() -> Result<(HashMap<Fingerprint, KeyConfig>,  [u8;32]), Error> { | ||||
|  | ||||
| pub fn load_key_configs() -> Result<(HashMap<Fingerprint, KeyConfig>, [u8; 32]), Error> { | ||||
|     let content = file_read_optional_string(TAPE_KEY_CONFIG_FILENAME)?; | ||||
|     let content = content.unwrap_or_else(|| String::from("[]")); | ||||
|  | ||||
| @ -128,7 +121,6 @@ pub fn load_key_configs() -> Result<(HashMap<Fingerprint, KeyConfig>,  [u8;32]), | ||||
| /// | ||||
| /// The file is only accessible by user root (mode 0600). | ||||
| pub fn save_keys(map: HashMap<Fingerprint, EncryptionKeyInfo>) -> Result<(), Error> { | ||||
|  | ||||
|     let mut list = Vec::new(); | ||||
|  | ||||
|     for (_fp, item) in map { | ||||
| @ -141,7 +133,6 @@ pub fn save_keys(map: HashMap<Fingerprint, EncryptionKeyInfo>) -> Result<(), Err | ||||
|  | ||||
| /// Store tape encryption key configurations (password protected keys) | ||||
| pub fn save_key_configs(map: HashMap<Fingerprint, KeyConfig>) -> Result<(), Error> { | ||||
|  | ||||
|     let mut list = Vec::new(); | ||||
|  | ||||
|     for (_fp, item) in map { | ||||
| @ -155,8 +146,7 @@ pub fn save_key_configs(map: HashMap<Fingerprint, KeyConfig>) -> Result<(), Erro | ||||
| /// Insert a new key | ||||
| /// | ||||
| /// Get the lock, load both files, insert the new key, store files. | ||||
| pub fn insert_key(key: [u8;32], key_config: KeyConfig, force: bool) -> Result<(), Error> { | ||||
|  | ||||
| pub fn insert_key(key: [u8; 32], key_config: KeyConfig, force: bool) -> Result<(), Error> { | ||||
|     let _lock = open_backup_lockfile(TAPE_KEYS_LOCKFILE, None, true)?; | ||||
|  | ||||
|     let (mut key_map, _) = load_keys()?; | ||||
|  | ||||
| @ -1,8 +1,8 @@ | ||||
| use anyhow::{Error}; | ||||
| use anyhow::Error; | ||||
| use lazy_static::lazy_static; | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use proxmox_schema::{Schema, ApiType}; | ||||
| use proxmox_schema::{ApiType, Schema}; | ||||
| use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | ||||
|  | ||||
| use pbs_api_types::{TapeBackupJobConfig, JOB_ID_SCHEMA}; | ||||
| @ -19,7 +19,8 @@ fn init() -> SectionConfig { | ||||
|         _ => unreachable!(), | ||||
|     }; | ||||
|  | ||||
|     let plugin = SectionConfigPlugin::new("backup".to_string(), Some(String::from("id")), obj_schema); | ||||
|     let plugin = | ||||
|         SectionConfigPlugin::new("backup".to_string(), Some(String::from("id")), obj_schema); | ||||
|     let mut config = SectionConfig::new(&JOB_ID_SCHEMA); | ||||
|     config.register_plugin(plugin); | ||||
|  | ||||
| @ -31,11 +32,10 @@ pub const TAPE_JOB_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.tape-job.lck"; | ||||
|  | ||||
| /// Get exclusive lock | ||||
| pub fn lock() -> Result<BackupLockGuard, Error> { | ||||
|     open_backup_lockfile( TAPE_JOB_CFG_LOCKFILE, None, true) | ||||
|     open_backup_lockfile(TAPE_JOB_CFG_LOCKFILE, None, true) | ||||
| } | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||
|     let content = proxmox_sys::fs::file_read_optional_string(TAPE_JOB_CFG_FILENAME)? | ||||
|         .unwrap_or_else(|| "".to_string()); | ||||
|  | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use anyhow::{bail, format_err, Error}; | ||||
| use serde::{Serialize, Deserialize}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use serde_json::{from_value, Value}; | ||||
|  | ||||
| use proxmox_sys::fs::CreateOptions; | ||||
| @ -14,7 +14,7 @@ const LOCK_FILE: &str = pbs_buildcfg::configdir!("/token.shadow.lock"); | ||||
| const CONF_FILE: &str = pbs_buildcfg::configdir!("/token.shadow"); | ||||
|  | ||||
| #[derive(Serialize, Deserialize)] | ||||
| #[serde(rename_all="kebab-case")] | ||||
| #[serde(rename_all = "kebab-case")] | ||||
| /// ApiToken id / secret pair | ||||
| pub struct ApiTokenSecret { | ||||
|     pub tokenid: Authid, | ||||
| @ -48,7 +48,6 @@ fn write_file(data: HashMap<Authid, String>) -> Result<(), Error> { | ||||
|     proxmox_sys::fs::replace_file(CONF_FILE, &json, options, true) | ||||
| } | ||||
|  | ||||
|  | ||||
| /// Verifies that an entry for given tokenid / API token secret exists | ||||
| pub fn verify_secret(tokenid: &Authid, secret: &str) -> Result<(), Error> { | ||||
|     if !tokenid.is_token() { | ||||
| @ -57,9 +56,7 @@ pub fn verify_secret(tokenid: &Authid, secret: &str) -> Result<(), Error> { | ||||
|  | ||||
|     let data = read_file()?; | ||||
|     match data.get(tokenid) { | ||||
|         Some(hashed_secret) => { | ||||
|             proxmox_sys::crypt::verify_crypt_pw(secret, hashed_secret) | ||||
|         }, | ||||
|         Some(hashed_secret) => proxmox_sys::crypt::verify_crypt_pw(secret, hashed_secret), | ||||
|         None => bail!("invalid API token"), | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -42,8 +42,7 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | ||||
| } | ||||
|  | ||||
| /// Read and parse the configuration file | ||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||
|     let content = proxmox_sys::fs::file_read_optional_string(TRAFFIC_CONTROL_CFG_FILENAME)? | ||||
|         .unwrap_or_else(|| "".to_string()); | ||||
|  | ||||
| @ -65,7 +64,6 @@ pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| // shell completion helper | ||||
| pub fn complete_traffic_control_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||
|     match config() { | ||||
| @ -93,5 +91,4 @@ mod test { | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| @ -7,9 +7,7 @@ use lazy_static::lazy_static; | ||||
| use proxmox_schema::*; | ||||
| use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | ||||
|  | ||||
| use pbs_api_types::{ | ||||
|     Authid, Userid, ApiToken, User, | ||||
| }; | ||||
| use pbs_api_types::{ApiToken, Authid, User, Userid}; | ||||
|  | ||||
| use crate::ConfigVersionCache; | ||||
|  | ||||
| @ -26,14 +24,19 @@ fn init() -> SectionConfig { | ||||
|         Schema::Object(ref user_schema) => user_schema, | ||||
|         _ => unreachable!(), | ||||
|     }; | ||||
|     let user_plugin = SectionConfigPlugin::new("user".to_string(), Some("userid".to_string()), user_schema); | ||||
|     let user_plugin = | ||||
|         SectionConfigPlugin::new("user".to_string(), Some("userid".to_string()), user_schema); | ||||
|     config.register_plugin(user_plugin); | ||||
|  | ||||
|     let token_schema = match ApiToken::API_SCHEMA { | ||||
|         Schema::Object(ref token_schema) => token_schema, | ||||
|         _ => unreachable!(), | ||||
|     }; | ||||
|     let token_plugin = SectionConfigPlugin::new("token".to_string(), Some("tokenid".to_string()), token_schema); | ||||
|     let token_plugin = SectionConfigPlugin::new( | ||||
|         "token".to_string(), | ||||
|         Some("tokenid".to_string()), | ||||
|         token_schema, | ||||
|     ); | ||||
|     config.register_plugin(token_plugin); | ||||
|  | ||||
|     config | ||||
| @ -47,8 +50,7 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | ||||
|     open_backup_lockfile(USER_CFG_LOCKFILE, None, true) | ||||
| } | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||
|     let content = proxmox_sys::fs::file_read_optional_string(USER_CFG_FILENAME)? | ||||
|         .unwrap_or_else(|| "".to_string()); | ||||
|  | ||||
| @ -72,7 +74,6 @@ pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | ||||
| } | ||||
|  | ||||
| pub fn cached_config() -> Result<Arc<SectionConfigData>, Error> { | ||||
|  | ||||
|     struct ConfigCache { | ||||
|         data: Option<Arc<SectionConfigData>>, | ||||
|         last_mtime: i64, | ||||
| @ -80,8 +81,11 @@ pub fn cached_config() -> Result<Arc<SectionConfigData>, Error> { | ||||
|     } | ||||
|  | ||||
|     lazy_static! { | ||||
|         static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new( | ||||
|             ConfigCache { data: None, last_mtime: 0, last_mtime_nsec: 0 }); | ||||
|         static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new(ConfigCache { | ||||
|             data: None, | ||||
|             last_mtime: 0, | ||||
|             last_mtime_nsec: 0 | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     let stat = match nix::sys::stat::stat(USER_CFG_FILENAME) { | ||||
| @ -90,11 +94,13 @@ pub fn cached_config() -> Result<Arc<SectionConfigData>, Error> { | ||||
|         Err(err) => bail!("unable to stat '{}' - {}", USER_CFG_FILENAME, err), | ||||
|     }; | ||||
|  | ||||
|     { // limit scope | ||||
|     { | ||||
|         // limit scope | ||||
|         let cache = CACHED_CONFIG.read().unwrap(); | ||||
|         if let Some(ref config) = cache.data { | ||||
|             if let Some(stat) = stat { | ||||
|                 if stat.st_mtime == cache.last_mtime && stat.st_mtime_nsec == cache.last_mtime_nsec { | ||||
|                 if stat.st_mtime == cache.last_mtime && stat.st_mtime_nsec == cache.last_mtime_nsec | ||||
|                 { | ||||
|                     return Ok(config.clone()); | ||||
|                 } | ||||
|             } else if cache.last_mtime == 0 && cache.last_mtime_nsec == 0 { | ||||
| @ -130,26 +136,27 @@ pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { | ||||
|  | ||||
| /// Only exposed for testing | ||||
| #[doc(hidden)] | ||||
| pub fn test_cfg_from_str(raw: &str) -> Result<(SectionConfigData, [u8;32]), Error> { | ||||
| pub fn test_cfg_from_str(raw: &str) -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||
|     let cfg = init(); | ||||
|     let parsed = cfg.parse("test_user_cfg", raw)?; | ||||
|  | ||||
|     Ok((parsed, [0;32])) | ||||
|     Ok((parsed, [0; 32])) | ||||
| } | ||||
|  | ||||
| // shell completion helper | ||||
| pub fn complete_userid(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||
|     match config() { | ||||
|         Ok((data, _digest)) => { | ||||
|             data.sections.iter() | ||||
|                 .filter_map(|(id, (section_type, _))| { | ||||
|                     if section_type == "user" { | ||||
|                         Some(id.to_string()) | ||||
|                     } else { | ||||
|                         None | ||||
|                     } | ||||
|                 }).collect() | ||||
|         }, | ||||
|         Ok((data, _digest)) => data | ||||
|             .sections | ||||
|             .iter() | ||||
|             .filter_map(|(id, (section_type, _))| { | ||||
|                 if section_type == "user" { | ||||
|                     Some(id.to_string()) | ||||
|                 } else { | ||||
|                     None | ||||
|                 } | ||||
|             }) | ||||
|             .collect(), | ||||
|         Err(_) => return vec![], | ||||
|     } | ||||
| } | ||||
| @ -174,21 +181,20 @@ pub fn complete_token_name(_arg: &str, param: &HashMap<String, String>) -> Vec<S | ||||
|             let user = data.lookup::<User>("user", userid); | ||||
|             let tokens = data.convert_to_typed_array("token"); | ||||
|             match (user, tokens) { | ||||
|                 (Ok(_), Ok(tokens)) => { | ||||
|                     tokens | ||||
|                         .into_iter() | ||||
|                         .filter_map(|token: ApiToken| { | ||||
|                             let tokenid = token.tokenid; | ||||
|                             if tokenid.is_token() && tokenid.user() == userid { | ||||
|                                 Some(tokenid.tokenname().unwrap().as_str().to_string()) | ||||
|                             } else { | ||||
|                                 None | ||||
|                             } | ||||
|                         }).collect() | ||||
|                 }, | ||||
|                 (Ok(_), Ok(tokens)) => tokens | ||||
|                     .into_iter() | ||||
|                     .filter_map(|token: ApiToken| { | ||||
|                         let tokenid = token.tokenid; | ||||
|                         if tokenid.is_token() && tokenid.user() == userid { | ||||
|                             Some(tokenid.tokenname().unwrap().as_str().to_string()) | ||||
|                         } else { | ||||
|                             None | ||||
|                         } | ||||
|                     }) | ||||
|                     .collect(), | ||||
|                 _ => vec![], | ||||
|             } | ||||
|         }, | ||||
|         } | ||||
|         None => vec![], | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -6,7 +6,7 @@ use lazy_static::lazy_static; | ||||
| use proxmox_schema::*; | ||||
| use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | ||||
|  | ||||
| use pbs_api_types::{JOB_ID_SCHEMA, VerificationJobConfig}; | ||||
| use pbs_api_types::{VerificationJobConfig, JOB_ID_SCHEMA}; | ||||
|  | ||||
| use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | ||||
|  | ||||
| @ -20,7 +20,11 @@ fn init() -> SectionConfig { | ||||
|         _ => unreachable!(), | ||||
|     }; | ||||
|  | ||||
|     let plugin = SectionConfigPlugin::new("verification".to_string(), Some(String::from("id")), obj_schema); | ||||
|     let plugin = SectionConfigPlugin::new( | ||||
|         "verification".to_string(), | ||||
|         Some(String::from("id")), | ||||
|         obj_schema, | ||||
|     ); | ||||
|     let mut config = SectionConfig::new(&JOB_ID_SCHEMA); | ||||
|     config.register_plugin(plugin); | ||||
|  | ||||
| @ -35,8 +39,7 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | ||||
|     open_backup_lockfile(VERIFICATION_CFG_LOCKFILE, None, true) | ||||
| } | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||
|     let content = proxmox_sys::fs::file_read_optional_string(VERIFICATION_CFG_FILENAME)?; | ||||
|     let content = content.unwrap_or_else(String::new); | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user