* [pbs-devel] [PATCH proxmox 1/2] new helper atomic_open_or_create_file() @ 2021-07-16 8:28 Dietmar Maurer 2021-07-16 8:28 ` [pbs-devel] [PATCH proxmox-backup 1/2] use new atomic_open_or_create_file Dietmar Maurer ` (3 more replies) 0 siblings, 4 replies; 8+ messages in thread From: Dietmar Maurer @ 2021-07-16 8:28 UTC (permalink / raw) To: pbs-devel --- proxmox/src/tools/fs.rs | 86 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/proxmox/src/tools/fs.rs b/proxmox/src/tools/fs.rs index 12e96bd..2a93b30 100644 --- a/proxmox/src/tools/fs.rs +++ b/proxmox/src/tools/fs.rs @@ -12,9 +12,10 @@ use nix::errno::Errno; use nix::fcntl::OFlag; use nix::sys::stat; use nix::unistd::{self, Gid, Uid}; +use nix::NixPath; use serde_json::Value; -use crate::sys::error::SysResult; +use crate::sys::error::{SysError, SysResult}; use crate::sys::timer; use crate::tools::fd::Fd; use crate::try_block; @@ -187,6 +188,89 @@ pub fn replace_file<P: AsRef<Path>>( Ok(()) } +/// Like open(2), but allows setting initial data, perm, owner and group +/// +/// Since we need to initialize the file, we also need a solid slow +/// path where we create the file. In order to avoid races, we create +/// it in a temporary location and rotate it in place. +pub fn atomic_open_or_create_file<P: AsRef<Path>>( + path: P, + mut oflag: OFlag, + initial_data: &[u8], + options: CreateOptions, +) -> Result<File, Error> { + let path = path.as_ref(); + + if oflag.contains(OFlag::O_TMPFILE) { + bail!("open {:?} failed - unsupported OFlag O_TMPFILE", path); + } + + oflag.remove(OFlag::O_CREAT); // we want to handle CREAT ourselfes + + // Note: 'mode' is ignored, because oflag does not contain O_CREAT or O_TMPFILE + match nix::fcntl::open(path, oflag, stat::Mode::empty()) { + Ok(fd) => return Ok(unsafe { File::from_raw_fd(fd) }), + Err(err) => { + if err.not_found() { + // fall thrue - try to create the file + } else { + bail!("open {:?} failed - {}", path, err); + } + } + } + + let (mut file, temp_file_name) = make_tmp_file(path, options)?; + + if !initial_data.is_empty() { + file.write_all(initial_data).map_err(|err| { + let _ = nix::unistd::unlink(&temp_file_name); + format_err!( + "writing initial data to {:?} failed - {}", + temp_file_name, + err, + ) + })?; + } + + // rotate the file into place, but use `RENAME_NOREPLACE`, so in case 2 processes race against + // the initialization, the first one wins! + let rename_result = temp_file_name.with_nix_path(|c_file_name| { + path.with_nix_path(|new_path| unsafe { + let rc = libc::renameat2( + libc::AT_FDCWD, + c_file_name.as_ptr(), + libc::AT_FDCWD, + new_path.as_ptr(), + libc::RENAME_NOREPLACE, + ); + nix::errno::Errno::result(rc) + })? + })?; + + match rename_result { + Ok(_) => Ok(file), + Err(err) => { + // if another process has already raced ahead and created + // the file, let's just open theirs instead: + let _ = nix::unistd::unlink(&temp_file_name); + + if err.already_exists() { + match nix::fcntl::open(path, oflag, stat::Mode::empty()) { + Ok(fd) => Ok(unsafe { File::from_raw_fd(fd) }), + Err(err) => bail!("open {:?} failed - {}", path, err), + } + } else { + bail!( + "failed to move file at {:?} into place at {:?} - {}", + temp_file_name, + path, + err + ); + } + } + } +} + /// Change ownership of an open file handle pub fn fchown(fd: RawFd, owner: Option<Uid>, group: Option<Gid>) -> Result<(), Error> { // According to the POSIX specification, -1 is used to indicate that owner and group -- 2.30.2 ^ permalink raw reply [flat|nested] 8+ messages in thread
* [pbs-devel] [PATCH proxmox-backup 1/2] use new atomic_open_or_create_file 2021-07-16 8:28 [pbs-devel] [PATCH proxmox 1/2] new helper atomic_open_or_create_file() Dietmar Maurer @ 2021-07-16 8:28 ` Dietmar Maurer [not found] ` <<20210716082834.2354163-2-dietmar@proxmox.com> 2021-07-16 8:28 ` [pbs-devel] [PATCH proxmox-backup 2/2] add helpers to write configuration files Dietmar Maurer ` (2 subsequent siblings) 3 siblings, 1 reply; 8+ messages in thread From: Dietmar Maurer @ 2021-07-16 8:28 UTC (permalink / raw) To: pbs-devel Factor out open_backup_lockfile() method to acquire locks owned by user backup with permission 0660. --- src/api2/access/acl.rs | 4 +- src/api2/access/user.rs | 14 +-- src/api2/config/datastore.rs | 2 +- src/api2/config/remote.rs | 8 +- src/api2/config/sync.rs | 8 +- src/api2/config/tape_backup_job.rs | 9 +- src/api2/config/tape_encryption_keys.rs | 15 +--- src/api2/config/verify.rs | 9 +- src/api2/node/disks/directory.rs | 4 +- src/api2/node/network.rs | 9 +- src/backup/datastore.rs | 9 +- src/backup/mod.rs | 26 ++++++ src/config/acme/plugin.rs | 6 +- src/config/datastore.rs | 6 +- src/config/domains.rs | 6 +- src/config/drive.rs | 6 +- src/config/media_pool.rs | 6 +- src/config/node.rs | 8 +- src/config/tape_encryption_keys.rs | 8 +- src/config/tfa.rs | 11 ++- src/config/token_shadow.rs | 9 +- src/server/jobstate.rs | 16 ++-- src/server/worker_task.rs | 14 ++- src/tape/drive/mod.rs | 46 +++++----- src/tape/drive/virtual_tape.rs | 3 +- src/tape/inventory.rs | 49 ++--------- src/tape/media_pool.rs | 5 +- src/tools/memcom.rs | 110 +++--------------------- 28 files changed, 158 insertions(+), 268 deletions(-) diff --git a/src/api2/access/acl.rs b/src/api2/access/acl.rs index a78aabcd..88a2667c 100644 --- a/src/api2/access/acl.rs +++ b/src/api2/access/acl.rs @@ -3,12 +3,12 @@ use anyhow::{bail, Error}; use proxmox::api::{api, Router, RpcEnvironment, Permission}; -use proxmox::tools::fs::open_file_locked; use crate::api2::types::*; use crate::config::acl; use crate::config::acl::{Role, PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY}; use crate::config::cached_user_info::CachedUserInfo; +use crate::backup::open_backup_lockfile; fn extract_acl_node_data( node: &acl::AclTreeNode, @@ -200,7 +200,7 @@ pub fn update_acl( }; } - let _lock = open_file_locked(acl::ACL_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(acl::ACL_CFG_LOCKFILE, None, true)?; let (mut tree, expected_digest) = acl::config()?; diff --git a/src/api2/access/user.rs b/src/api2/access/user.rs index e080d57a..23c999b4 100644 --- a/src/api2/access/user.rs +++ b/src/api2/access/user.rs @@ -8,13 +8,13 @@ use std::collections::HashMap; use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; use proxmox::api::router::SubdirMap; use proxmox::api::schema::{Schema, StringSchema}; -use proxmox::tools::fs::open_file_locked; use crate::api2::types::*; use crate::config::user; use crate::config::token_shadow; use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY}; use crate::config::cached_user_info::CachedUserInfo; +use crate::backup::open_backup_lockfile; pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.") .format(&PASSWORD_FORMAT) @@ -226,7 +226,7 @@ pub fn create_user( rpcenv: &mut dyn RpcEnvironment ) -> Result<(), Error> { - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; let user: user::User = serde_json::from_value(param)?; @@ -368,7 +368,7 @@ pub fn update_user( rpcenv: &mut dyn RpcEnvironment, ) -> Result<(), Error> { - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = user::config()?; @@ -461,7 +461,7 @@ pub fn update_user( pub fn delete_user(userid: Userid, digest: Option<String>) -> Result<(), Error> { let _tfa_lock = crate::config::tfa::write_lock()?; - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = user::config()?; @@ -597,7 +597,7 @@ pub fn generate_token( digest: Option<String>, ) -> Result<Value, Error> { - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = user::config()?; @@ -678,7 +678,7 @@ pub fn update_token( digest: Option<String>, ) -> Result<(), Error> { - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = user::config()?; @@ -746,7 +746,7 @@ pub fn delete_token( digest: Option<String>, ) -> Result<(), Error> { - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = user::config()?; diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs index 19b822d4..a55e3bdc 100644 --- a/src/api2/config/datastore.rs +++ b/src/api2/config/datastore.rs @@ -60,7 +60,7 @@ pub fn list_datastores( } pub(crate) fn do_create_datastore( - _lock: std::fs::File, + _lock: BackupLockGuard, mut config: SectionConfigData, datastore: DataStoreConfig, worker: Option<&dyn TaskState>, diff --git a/src/api2/config/remote.rs b/src/api2/config/remote.rs index 446a2604..33c99bff 100644 --- a/src/api2/config/remote.rs +++ b/src/api2/config/remote.rs @@ -4,13 +4,13 @@ use ::serde::{Deserialize, Serialize}; use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; use proxmox::http_err; -use proxmox::tools::fs::open_file_locked; use crate::api2::types::*; use crate::client::{HttpClient, HttpClientOptions}; use crate::config::cached_user_info::CachedUserInfo; use crate::config::remote; use crate::config::acl::{PRIV_REMOTE_AUDIT, PRIV_REMOTE_MODIFY}; +use crate::backup::open_backup_lockfile; #[api( input: { @@ -94,7 +94,7 @@ pub fn list_remotes( /// Create new remote. pub fn create_remote(password: String, param: Value) -> Result<(), Error> { - let _lock = open_file_locked(remote::REMOTE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(remote::REMOTE_CFG_LOCKFILE, None, true)?; let mut data = param; data["password"] = Value::from(base64::encode(password.as_bytes())); @@ -216,7 +216,7 @@ pub fn update_remote( digest: Option<String>, ) -> Result<(), Error> { - let _lock = open_file_locked(remote::REMOTE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(remote::REMOTE_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = remote::config()?; @@ -290,7 +290,7 @@ pub fn delete_remote(name: String, digest: Option<String>) -> Result<(), Error> } } - let _lock = open_file_locked(remote::REMOTE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(remote::REMOTE_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = remote::config()?; diff --git a/src/api2/config/sync.rs b/src/api2/config/sync.rs index e784029a..bc7b9f24 100644 --- a/src/api2/config/sync.rs +++ b/src/api2/config/sync.rs @@ -3,7 +3,6 @@ use serde_json::Value; use ::serde::{Deserialize, Serialize}; use proxmox::api::{api, Permission, Router, RpcEnvironment}; -use proxmox::tools::fs::open_file_locked; use crate::api2::types::*; @@ -18,6 +17,7 @@ use crate::config::acl::{ use crate::config::cached_user_info::CachedUserInfo; use crate::config::sync::{self, SyncJobConfig}; +use crate::backup::open_backup_lockfile; pub fn check_sync_job_read_access( user_info: &CachedUserInfo, @@ -152,7 +152,7 @@ pub fn create_sync_job( let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let user_info = CachedUserInfo::new()?; - let _lock = open_file_locked(sync::SYNC_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(sync::SYNC_CFG_LOCKFILE, None, true)?; let sync_job: sync::SyncJobConfig = serde_json::from_value(param)?; if !check_sync_job_modify_access(&user_info, &auth_id, &sync_job) { @@ -296,7 +296,7 @@ pub fn update_sync_job( let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let user_info = CachedUserInfo::new()?; - let _lock = open_file_locked(sync::SYNC_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(sync::SYNC_CFG_LOCKFILE, None, true)?; // pass/compare digest let (mut config, expected_digest) = sync::config()?; @@ -379,7 +379,7 @@ pub fn delete_sync_job( let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let user_info = CachedUserInfo::new()?; - let _lock = open_file_locked(sync::SYNC_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(sync::SYNC_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = sync::config()?; diff --git a/src/api2/config/tape_backup_job.rs b/src/api2/config/tape_backup_job.rs index 22afeb6d..02fa6f7d 100644 --- a/src/api2/config/tape_backup_job.rs +++ b/src/api2/config/tape_backup_job.rs @@ -3,7 +3,6 @@ use serde_json::Value; use ::serde::{Deserialize, Serialize}; use proxmox::api::{api, Router, RpcEnvironment, Permission}; -use proxmox::tools::fs::open_file_locked; use crate::{ api2::types::{ @@ -17,6 +16,7 @@ use crate::{ MEDIA_POOL_NAME_SCHEMA, SYNC_SCHEDULE_SCHEMA, }, + backup::open_backup_lockfile, config::{ self, cached_user_info::CachedUserInfo, @@ -89,8 +89,7 @@ pub fn create_tape_backup_job( job: TapeBackupJobConfig, _rpcenv: &mut dyn RpcEnvironment, ) -> Result<(), Error> { - - let _lock = open_file_locked(TAPE_JOB_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(TAPE_JOB_CFG_LOCKFILE, None, true)?; let (mut config, _digest) = config::tape_job::config()?; @@ -233,7 +232,7 @@ pub fn update_tape_backup_job( delete: Option<Vec<DeletableProperty>>, digest: Option<String>, ) -> Result<(), Error> { - let _lock = open_file_locked(TAPE_JOB_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(TAPE_JOB_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = config::tape_job::config()?; @@ -312,7 +311,7 @@ pub fn delete_tape_backup_job( digest: Option<String>, _rpcenv: &mut dyn RpcEnvironment, ) -> Result<(), Error> { - let _lock = open_file_locked(TAPE_JOB_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(TAPE_JOB_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = config::tape_job::config()?; diff --git a/src/api2/config/tape_encryption_keys.rs b/src/api2/config/tape_encryption_keys.rs index e1b42fb8..6fbfaded 100644 --- a/src/api2/config/tape_encryption_keys.rs +++ b/src/api2/config/tape_encryption_keys.rs @@ -9,7 +9,6 @@ use proxmox::{ RpcEnvironment, Permission, }, - tools::fs::open_file_locked, }; use pbs_datastore::{KeyInfo, Kdf}; @@ -35,6 +34,7 @@ use crate::{ PASSWORD_HINT_SCHEMA, }, backup::{ + open_backup_lockfile, KeyConfig, Fingerprint, }, @@ -122,11 +122,7 @@ pub fn change_passphrase( bail!("Please specify a key derivation function (none is not allowed here)."); } - let _lock = open_file_locked( - TAPE_KEYS_LOCKFILE, - std::time::Duration::new(10, 0), - true, - )?; + let _lock = open_backup_lockfile(TAPE_KEYS_LOCKFILE, None, true)?; let (mut config_map, expected_digest) = load_key_configs()?; @@ -261,12 +257,7 @@ pub fn delete_key( digest: Option<String>, _rpcenv: &mut dyn RpcEnvironment, ) -> Result<(), Error> { - - let _lock = open_file_locked( - TAPE_KEYS_LOCKFILE, - std::time::Duration::new(10, 0), - true, - )?; + let _lock = open_backup_lockfile(TAPE_KEYS_LOCKFILE, None, true)?; let (mut config_map, expected_digest) = load_key_configs()?; let (mut key_map, _) = load_keys()?; diff --git a/src/api2/config/verify.rs b/src/api2/config/verify.rs index 477cda89..1a613327 100644 --- a/src/api2/config/verify.rs +++ b/src/api2/config/verify.rs @@ -3,7 +3,6 @@ use serde_json::Value; use ::serde::{Deserialize, Serialize}; use proxmox::api::{api, Permission, Router, RpcEnvironment}; -use proxmox::tools::fs::open_file_locked; use crate::api2::types::*; @@ -13,8 +12,8 @@ use crate::config::acl::{ }; use crate::config::cached_user_info::CachedUserInfo; - use crate::config::verify::{self, VerificationJobConfig}; +use crate::backup::open_backup_lockfile; #[api( input: { @@ -102,7 +101,7 @@ pub fn create_verification_job( user_info.check_privs(&auth_id, &["datastore", &verification_job.store], PRIV_DATASTORE_VERIFY, false)?; - let _lock = open_file_locked(verify::VERIFICATION_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(verify::VERIFICATION_CFG_LOCKFILE, None, true)?; let (mut config, _digest) = verify::config()?; @@ -230,7 +229,7 @@ pub fn update_verification_job( let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let user_info = CachedUserInfo::new()?; - let _lock = open_file_locked(verify::VERIFICATION_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(verify::VERIFICATION_CFG_LOCKFILE, None, true)?; // pass/compare digest let (mut config, expected_digest) = verify::config()?; @@ -315,7 +314,7 @@ pub fn delete_verification_job( let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let user_info = CachedUserInfo::new()?; - let _lock = open_file_locked(verify::VERIFICATION_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(verify::VERIFICATION_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = verify::config()?; diff --git a/src/api2/node/disks/directory.rs b/src/api2/node/disks/directory.rs index ae8a3974..75e0ea02 100644 --- a/src/api2/node/disks/directory.rs +++ b/src/api2/node/disks/directory.rs @@ -5,7 +5,6 @@ use ::serde::{Deserialize, Serialize}; use proxmox::api::{api, Permission, RpcEnvironment, RpcEnvironmentType}; use proxmox::api::section_config::SectionConfigData; use proxmox::api::router::Router; -use proxmox::tools::fs::open_file_locked; use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; use crate::tools::disks::{ @@ -18,6 +17,7 @@ use crate::server::WorkerTask; use crate::api2::types::*; use crate::config::datastore::{self, DataStoreConfig}; +use crate::backup::open_backup_lockfile; #[api( properties: { @@ -180,7 +180,7 @@ pub fn create_datastore_disk( systemd::start_unit(&mount_unit_name)?; if add_datastore { - let lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let lock = open_backup_lockfile(datastore::DATASTORE_CFG_LOCKFILE, None, true)?; let datastore: DataStoreConfig = serde_json::from_value(json!({ "name": name, "path": mount_point }))?; diff --git a/src/api2/node/network.rs b/src/api2/node/network.rs index 0dc321b8..ecf112a4 100644 --- a/src/api2/node/network.rs +++ b/src/api2/node/network.rs @@ -4,12 +4,12 @@ use ::serde::{Deserialize, Serialize}; use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; use proxmox::api::schema::parse_property_string; -use proxmox::tools::fs::open_file_locked; use crate::config::network::{self, NetworkConfig}; use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; use crate::api2::types::*; use crate::server::{WorkerTask}; +use crate::backup::open_backup_lockfile; fn split_interface_list(list: &str) -> Result<Vec<String>, Error> { let value = parse_property_string(&list, &NETWORK_INTERFACE_ARRAY_SCHEMA)?; @@ -238,7 +238,7 @@ pub fn create_interface( let interface_type = crate::tools::required_string_param(¶m, "type")?; let interface_type: NetworkInterfaceType = serde_json::from_value(interface_type.into())?; - let _lock = open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(network::NETWORK_LOCKFILE, None, true)?; let (mut config, _digest) = network::config()?; @@ -502,7 +502,7 @@ pub fn update_interface( param: Value, ) -> Result<(), Error> { - let _lock = open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(network::NETWORK_LOCKFILE, None, true)?; let (mut config, expected_digest) = network::config()?; @@ -642,8 +642,7 @@ pub fn update_interface( )] /// Remove network interface configuration. pub fn delete_interface(iface: String, digest: Option<String>) -> Result<(), Error> { - - let _lock = open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(network::NETWORK_LOCKFILE, None, true)?; let (mut config, expected_digest) = network::config()?; diff --git a/src/backup/datastore.rs b/src/backup/datastore.rs index d47c412b..5695adcc 100644 --- a/src/backup/datastore.rs +++ b/src/backup/datastore.rs @@ -5,12 +5,11 @@ use std::sync::{Arc, Mutex}; use std::convert::TryFrom; use std::str::FromStr; use std::time::Duration; -use std::fs::File; use anyhow::{bail, format_err, Error}; use lazy_static::lazy_static; -use proxmox::tools::fs::{replace_file, file_read_optional_string, CreateOptions, open_file_locked}; +use proxmox::tools::fs::{replace_file, file_read_optional_string, CreateOptions}; use pbs_api_types::upid::UPID; use pbs_api_types::{Authid, GarbageCollectionStatus}; @@ -32,6 +31,8 @@ use pbs_tools::fs::{lock_dir_noblock, DirLockGuard}; use crate::config::datastore::{self, DataStoreConfig}; use crate::tools; +use crate::backup::{open_backup_lockfile, BackupLockGuard}; + lazy_static! { static ref DATASTORE_MAP: Mutex<HashMap<String, Arc<DataStore>>> = Mutex::new(HashMap::new()); @@ -777,12 +778,12 @@ impl DataStore { fn lock_manifest( &self, backup_dir: &BackupDir, - ) -> Result<File, Error> { + ) -> Result<BackupLockGuard, Error> { let path = self.manifest_lock_path(backup_dir)?; // update_manifest should never take a long time, so if someone else has // the lock we can simply block a bit and should get it soon - open_file_locked(&path, Duration::from_secs(5), true) + open_backup_lockfile(&path, Some(Duration::from_secs(5)), true) .map_err(|err| { format_err!( "unable to acquire manifest lock {:?} - {}", &path, err diff --git a/src/backup/mod.rs b/src/backup/mod.rs index 4161b402..1db61cf5 100644 --- a/src/backup/mod.rs +++ b/src/backup/mod.rs @@ -108,3 +108,29 @@ pub use catalog_shell::*; mod cached_chunk_reader; pub use cached_chunk_reader::*; + +pub struct BackupLockGuard(std::fs::File); + +/// Open or create a lock file owned by user "backup" and lock it. +/// +/// Owner/Group of the file is set to backup/backup. +/// File mode is 0660. +/// Default timeout is 10 seconds. +/// +/// Note: This method needs to be called by user "root" or "backup". +pub fn open_backup_lockfile<P: AsRef<std::path::Path>>( + path: P, + timeout: Option<std::time::Duration>, + exclusive: bool, +) -> Result<BackupLockGuard, Error> { + let user = backup_user()?; + let options = proxmox::tools::fs::CreateOptions::new() + .perm(nix::sys::stat::Mode::from_bits_truncate(0o660)) + .owner(user.uid) + .group(user.gid); + + let timeout = timeout.unwrap_or(std::time::Duration::new(10, 0)); + + let file = proxmox::tools::fs::open_file_locked(&path, timeout, exclusive, options)?; + Ok(BackupLockGuard(file)) +} diff --git a/src/config/acme/plugin.rs b/src/config/acme/plugin.rs index 960aec3a..fde800e2 100644 --- a/src/config/acme/plugin.rs +++ b/src/config/acme/plugin.rs @@ -12,6 +12,7 @@ use proxmox::api::{ use proxmox::tools::{fs::replace_file, fs::CreateOptions}; use crate::api2::types::PROXMOX_SAFE_ID_FORMAT; +use crate::backup::{open_backup_lockfile, BackupLockGuard}; pub const PLUGIN_ID_SCHEMA: Schema = StringSchema::new("ACME Challenge Plugin ID.") .format(&PROXMOX_SAFE_ID_FORMAT) @@ -142,11 +143,10 @@ fn init() -> SectionConfig { const ACME_PLUGIN_CFG_FILENAME: &str = pbs_buildcfg::configdir!("/acme/plugins.cfg"); const ACME_PLUGIN_CFG_LOCKFILE: &str = pbs_buildcfg::configdir!("/acme/.plugins.lck"); -const LOCK_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); -pub fn lock() -> Result<std::fs::File, Error> { +pub fn lock() -> Result<BackupLockGuard, Error> { super::make_acme_dir()?; - proxmox::tools::fs::open_file_locked(ACME_PLUGIN_CFG_LOCKFILE, LOCK_TIMEOUT, true) + open_backup_lockfile(ACME_PLUGIN_CFG_LOCKFILE, None, true) } pub fn config() -> Result<(PluginData, [u8; 32]), Error> { diff --git a/src/config/datastore.rs b/src/config/datastore.rs index d22fa316..9e37073d 100644 --- a/src/config/datastore.rs +++ b/src/config/datastore.rs @@ -14,12 +14,12 @@ use proxmox::api::{ }; use proxmox::tools::fs::{ - open_file_locked, replace_file, CreateOptions, }; use crate::api2::types::*; +use crate::backup::{open_backup_lockfile, BackupLockGuard}; lazy_static! { pub static ref CONFIG: SectionConfig = init(); @@ -138,8 +138,8 @@ pub const DATASTORE_CFG_FILENAME: &str = "/etc/proxmox-backup/datastore.cfg"; pub const DATASTORE_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.datastore.lck"; /// Get exclusive lock -pub fn lock_config() -> Result<std::fs::File, Error> { - open_file_locked(DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true) +pub fn lock_config() -> Result<BackupLockGuard, Error> { + open_backup_lockfile(DATASTORE_CFG_LOCKFILE, None, true) } pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { diff --git a/src/config/domains.rs b/src/config/domains.rs index 775c02f3..9f513a44 100644 --- a/src/config/domains.rs +++ b/src/config/domains.rs @@ -14,12 +14,12 @@ use proxmox::api::{ }; use proxmox::tools::fs::{ - open_file_locked, replace_file, CreateOptions, }; use crate::api2::types::*; +use crate::backup::{open_backup_lockfile, BackupLockGuard}; lazy_static! { pub static ref CONFIG: SectionConfig = init(); @@ -110,8 +110,8 @@ pub const DOMAINS_CFG_FILENAME: &str = "/etc/proxmox-backup/domains.cfg"; pub const DOMAINS_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.domains.lck"; /// Get exclusive lock -pub fn lock_config() -> Result<std::fs::File, Error> { - open_file_locked(DOMAINS_CFG_LOCKFILE, std::time::Duration::new(10, 0), true) +pub fn lock_config() -> Result<BackupLockGuard, Error> { + open_backup_lockfile(DOMAINS_CFG_LOCKFILE, None, true) } pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { diff --git a/src/config/drive.rs b/src/config/drive.rs index 57f6911f..9c20051f 100644 --- a/src/config/drive.rs +++ b/src/config/drive.rs @@ -26,13 +26,13 @@ use proxmox::{ }, }, tools::fs::{ - open_file_locked, replace_file, CreateOptions, }, }; use crate::{ + backup::{open_backup_lockfile, BackupLockGuard}, api2::types::{ DRIVE_NAME_SCHEMA, VirtualTapeDrive, @@ -79,8 +79,8 @@ pub const DRIVE_CFG_FILENAME: &str = "/etc/proxmox-backup/tape.cfg"; pub const DRIVE_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.tape.lck"; /// Get exclusive lock -pub fn lock() -> Result<std::fs::File, Error> { - open_file_locked(DRIVE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true) +pub fn lock() -> Result<BackupLockGuard, Error> { + open_backup_lockfile(DRIVE_CFG_LOCKFILE, None, true) } /// Read and parse the configuration file diff --git a/src/config/media_pool.rs b/src/config/media_pool.rs index 5ba1a6b2..e50992d8 100644 --- a/src/config/media_pool.rs +++ b/src/config/media_pool.rs @@ -21,13 +21,13 @@ use proxmox::{ } }, tools::fs::{ - open_file_locked, replace_file, CreateOptions, }, }; use crate::{ + backup::{open_backup_lockfile, BackupLockGuard}, api2::types::{ MEDIA_POOL_NAME_SCHEMA, MediaPoolConfig, @@ -59,8 +59,8 @@ pub const MEDIA_POOL_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.media-pool.lck"; /// Get exclusive lock -pub fn lock() -> Result<std::fs::File, Error> { - open_file_locked(MEDIA_POOL_CFG_LOCKFILE, std::time::Duration::new(10, 0), true) +pub fn lock() -> Result<BackupLockGuard, Error> { + open_backup_lockfile(MEDIA_POOL_CFG_LOCKFILE, None, true) } /// Read and parse the configuration file diff --git a/src/config/node.rs b/src/config/node.rs index 27e32cd1..dc3eeeb0 100644 --- a/src/config/node.rs +++ b/src/config/node.rs @@ -1,6 +1,4 @@ use std::collections::HashSet; -use std::fs::File; -use std::time::Duration; use anyhow::{bail, Error}; use nix::sys::stat::Mode; @@ -14,6 +12,7 @@ use proxmox_http::ProxyConfig; use pbs_buildcfg::configdir; +use crate::backup::{open_backup_lockfile, BackupLockGuard}; use crate::acme::AcmeClient; use crate::api2::types::{ AcmeAccountName, AcmeDomain, ACME_DOMAIN_PROPERTY_SCHEMA, HTTP_PROXY_SCHEMA, @@ -21,10 +20,9 @@ use crate::api2::types::{ const CONF_FILE: &str = configdir!("/node.cfg"); const LOCK_FILE: &str = configdir!("/.node.lck"); -const LOCK_TIMEOUT: Duration = Duration::from_secs(10); -pub fn lock() -> Result<File, Error> { - proxmox::tools::fs::open_file_locked(LOCK_FILE, LOCK_TIMEOUT, true) +pub fn lock() -> Result<BackupLockGuard, Error> { + open_backup_lockfile(LOCK_FILE, None, true) } /// Read the Node Config. diff --git a/src/config/tape_encryption_keys.rs b/src/config/tape_encryption_keys.rs index e3aab1d8..5ee0ac1f 100644 --- a/src/config/tape_encryption_keys.rs +++ b/src/config/tape_encryption_keys.rs @@ -18,12 +18,12 @@ use serde::{Deserialize, Serialize}; use proxmox::tools::fs::{ file_read_optional_string, replace_file, - open_file_locked, CreateOptions, }; use crate::{ backup::{ + open_backup_lockfile, Fingerprint, KeyConfig, }, @@ -187,11 +187,7 @@ pub fn save_key_configs(map: HashMap<Fingerprint, KeyConfig>) -> Result<(), Erro /// 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> { - let _lock = open_file_locked( - TAPE_KEYS_LOCKFILE, - std::time::Duration::new(10, 0), - true, - )?; + let _lock = open_backup_lockfile(TAPE_KEYS_LOCKFILE, None, true)?; let (mut key_map, _) = load_keys()?; let (mut config_map, _) = load_key_configs()?; diff --git a/src/config/tfa.rs b/src/config/tfa.rs index 341307db..efba0c13 100644 --- a/src/config/tfa.rs +++ b/src/config/tfa.rs @@ -4,7 +4,6 @@ use std::io::{self, Read, Seek, SeekFrom}; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::AsRawFd; use std::path::PathBuf; -use std::time::Duration; use anyhow::{bail, format_err, Error}; use nix::sys::stat::Mode; @@ -29,25 +28,25 @@ use proxmox::tools::AsHex; use pbs_buildcfg::configdir; use crate::api2::types::Userid; +use crate::backup::{open_backup_lockfile, BackupLockGuard}; /// Mapping of userid to TFA entry. pub type TfaUsers = HashMap<Userid, TfaUserData>; const CONF_FILE: &str = configdir!("/tfa.json"); const LOCK_FILE: &str = configdir!("/tfa.json.lock"); -const LOCK_TIMEOUT: Duration = Duration::from_secs(5); const CHALLENGE_DATA_PATH: &str = pbs_buildcfg::rundir!("/tfa/challenges"); /// U2F registration challenges time out after 2 minutes. const CHALLENGE_TIMEOUT: i64 = 2 * 60; -pub fn read_lock() -> Result<File, Error> { - proxmox::tools::fs::open_file_locked(LOCK_FILE, LOCK_TIMEOUT, false) +pub fn read_lock() -> Result<BackupLockGuard, Error> { + open_backup_lockfile(LOCK_FILE, None, false) } -pub fn write_lock() -> Result<File, Error> { - proxmox::tools::fs::open_file_locked(LOCK_FILE, LOCK_TIMEOUT, true) +pub fn write_lock() -> Result<BackupLockGuard, Error> { + open_backup_lockfile(LOCK_FILE, None, true) } /// Read the TFA entries. diff --git a/src/config/token_shadow.rs b/src/config/token_shadow.rs index 9f8bb2e0..a210ffb2 100644 --- a/src/config/token_shadow.rs +++ b/src/config/token_shadow.rs @@ -1,18 +1,17 @@ use std::collections::HashMap; -use std::time::Duration; use anyhow::{bail, format_err, Error}; use serde::{Serialize, Deserialize}; use serde_json::{from_value, Value}; -use proxmox::tools::fs::{open_file_locked, CreateOptions}; +use proxmox::tools::fs::CreateOptions; use crate::api2::types::Authid; use crate::auth; +use crate::backup::open_backup_lockfile; const LOCK_FILE: &str = pbs_buildcfg::configdir!("/token.shadow.lock"); const CONF_FILE: &str = pbs_buildcfg::configdir!("/token.shadow"); -const LOCK_TIMEOUT: Duration = Duration::from_secs(5); #[derive(Serialize, Deserialize)] #[serde(rename_all="kebab-case")] @@ -65,7 +64,7 @@ pub fn set_secret(tokenid: &Authid, secret: &str) -> Result<(), Error> { bail!("not an API token ID"); } - let _guard = open_file_locked(LOCK_FILE, LOCK_TIMEOUT, true)?; + let _guard = open_backup_lockfile(LOCK_FILE, None, true)?; let mut data = read_file()?; let hashed_secret = auth::encrypt_pw(secret)?; @@ -81,7 +80,7 @@ pub fn delete_secret(tokenid: &Authid) -> Result<(), Error> { bail!("not an API token ID"); } - let _guard = open_file_locked(LOCK_FILE, LOCK_TIMEOUT, true)?; + let _guard = open_backup_lockfile(LOCK_FILE, None, true)?; let mut data = read_file()?; data.remove(tokenid); diff --git a/src/server/jobstate.rs b/src/server/jobstate.rs index 96dd21aa..4b15fabc 100644 --- a/src/server/jobstate.rs +++ b/src/server/jobstate.rs @@ -37,18 +37,17 @@ //! # } //! //! ``` -use std::fs::File; use std::path::{Path, PathBuf}; -use std::time::Duration; use anyhow::{bail, format_err, Error}; use proxmox::tools::fs::{ - create_path, file_read_optional_string, open_file_locked, replace_file, CreateOptions, + create_path, file_read_optional_string, replace_file, CreateOptions, }; use serde::{Deserialize, Serialize}; use crate::{ - tools::systemd::time::{ + backup::{open_backup_lockfile, BackupLockGuard}, + tools::systemd::time::{ parse_calendar_event, compute_next_event, }, @@ -83,7 +82,7 @@ pub struct Job { jobname: String, /// The State of the job pub state: JobState, - _lock: File, + _lock: BackupLockGuard, } const JOB_STATE_BASEDIR: &str = "/var/lib/proxmox-backup/jobstates"; @@ -107,16 +106,13 @@ fn get_path(jobtype: &str, jobname: &str) -> PathBuf { path } -fn get_lock<P>(path: P) -> Result<File, Error> +fn get_lock<P>(path: P) -> Result<BackupLockGuard, Error> where P: AsRef<Path>, { let mut path = path.as_ref().to_path_buf(); path.set_extension("lck"); - let lock = open_file_locked(&path, Duration::new(10, 0), true)?; - let backup_user = crate::backup::backup_user()?; - nix::unistd::chown(&path, Some(backup_user.uid), Some(backup_user.gid))?; - Ok(lock) + open_backup_lockfile(&path, None, true) } /// Removes the statefile of a job, this is useful if we delete a job diff --git a/src/server/worker_task.rs b/src/server/worker_task.rs index 13578446..8507842b 100644 --- a/src/server/worker_task.rs +++ b/src/server/worker_task.rs @@ -14,7 +14,7 @@ use tokio::sync::oneshot; use proxmox::sys::linux::procfs; use proxmox::try_block; -use proxmox::tools::fs::{create_path, open_file_locked, replace_file, CreateOptions}; +use proxmox::tools::fs::{create_path, replace_file, CreateOptions}; use super::{UPID, UPIDExt}; @@ -24,6 +24,7 @@ use crate::server; use crate::tools::logrotate::{LogRotate, LogRotateFiles}; use crate::tools::{FileLogger, FileLogOptions}; use crate::api2::types::{Authid, TaskStateType}; +use crate::backup::{open_backup_lockfile, BackupLockGuard}; macro_rules! taskdir { ($subdir:expr) => (concat!(pbs_buildcfg::PROXMOX_BACKUP_LOG_DIR_M!(), "/tasks", $subdir)) @@ -313,13 +314,8 @@ pub struct TaskListInfo { pub state: Option<TaskState>, // endtime, status } -fn lock_task_list_files(exclusive: bool) -> Result<std::fs::File, Error> { - let backup_user = crate::backup::backup_user()?; - - let lock = open_file_locked(PROXMOX_BACKUP_TASK_LOCK_FN, std::time::Duration::new(10, 0), exclusive)?; - nix::unistd::chown(PROXMOX_BACKUP_TASK_LOCK_FN, Some(backup_user.uid), Some(backup_user.gid))?; - - Ok(lock) +fn lock_task_list_files(exclusive: bool) -> Result<BackupLockGuard, Error> { + open_backup_lockfile(PROXMOX_BACKUP_TASK_LOCK_FN, None, exclusive) } /// checks if the Task Archive is bigger that 'size_threshold' bytes, and @@ -481,7 +477,7 @@ pub struct TaskListInfoIterator { list: VecDeque<TaskListInfo>, end: bool, archive: Option<LogRotateFiles>, - lock: Option<File>, + lock: Option<BackupLockGuard>, } impl TaskListInfoIterator { diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs index fb4b6f47..a6cbed84 100644 --- a/src/tape/drive/mod.rs +++ b/src/tape/drive/mod.rs @@ -5,19 +5,21 @@ mod virtual_tape; mod lto; pub use lto::*; -use std::os::unix::io::AsRawFd; use std::path::PathBuf; use anyhow::{bail, format_err, Error}; use ::serde::{Deserialize}; use serde_json::Value; +use nix::fcntl::OFlag; +use nix::sys::stat::Mode; use proxmox::{ tools::{ Uuid, io::ReadExt, fs::{ - fchown, + lock_file, + atomic_open_or_create_file, file_read_optional_string, replace_file, CreateOptions, @@ -604,20 +606,34 @@ fn tape_device_path( pub struct DeviceLockGuard(std::fs::File); -// Acquires an exclusive lock on `device_path` -// // Uses systemd escape_unit to compute a file name from `device_path`, the try // to lock `/var/lock/<name>`. -fn lock_device_path(device_path: &str) -> Result<DeviceLockGuard, TapeLockError> { - +fn open_device_lock(device_path: &str) -> Result<std::fs::File, Error> { let lock_name = crate::tools::systemd::escape_unit(device_path, true); let mut path = std::path::PathBuf::from(crate::tape::DRIVE_LOCK_DIR); path.push(lock_name); + let user = crate::backup::backup_user()?; + let options = CreateOptions::new() + .perm(Mode::from_bits_truncate(0o660)) + .owner(user.uid) + .group(user.gid); + + atomic_open_or_create_file( + path, + OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_APPEND, + &[], + options, + ) +} + +// Acquires an exclusive lock on `device_path` +// +fn lock_device_path(device_path: &str) -> Result<DeviceLockGuard, TapeLockError> { + let mut file = open_device_lock(device_path)?; let timeout = std::time::Duration::new(10, 0); - let mut file = std::fs::OpenOptions::new().create(true).append(true).open(path)?; - if let Err(err) = proxmox::tools::fs::lock_file(&mut file, true, Some(timeout)) { + if let Err(err) = lock_file(&mut file, true, Some(timeout)) { if err.kind() == std::io::ErrorKind::Interrupted { return Err(TapeLockError::TimeOut); } else { @@ -625,9 +641,6 @@ fn lock_device_path(device_path: &str) -> Result<DeviceLockGuard, TapeLockError> } } - let backup_user = crate::backup::backup_user()?; - fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; - Ok(DeviceLockGuard(file)) } @@ -635,14 +648,10 @@ fn lock_device_path(device_path: &str) -> Result<DeviceLockGuard, TapeLockError> // non-blocking, and returning if the file is locked or not fn test_device_path_lock(device_path: &str) -> Result<bool, Error> { - let lock_name = crate::tools::systemd::escape_unit(device_path, true); - - let mut path = std::path::PathBuf::from(crate::tape::DRIVE_LOCK_DIR); - path.push(lock_name); + let mut file = open_device_lock(device_path)?; let timeout = std::time::Duration::new(0, 0); - let mut file = std::fs::OpenOptions::new().create(true).append(true).open(path)?; - match proxmox::tools::fs::lock_file(&mut file, true, Some(timeout)) { + match lock_file(&mut file, true, Some(timeout)) { // file was not locked, continue Ok(()) => {}, // file was locked, return true @@ -650,8 +659,5 @@ fn test_device_path_lock(device_path: &str) -> Result<bool, Error> { Err(err) => bail!("{}", err), } - let backup_user = crate::backup::backup_user()?; - fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; - Ok(false) } diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs index 3c5f9502..f48afa79 100644 --- a/src/tape/drive/virtual_tape.rs +++ b/src/tape/drive/virtual_tape.rs @@ -49,8 +49,9 @@ impl VirtualTapeDrive { let mut lock_path = std::path::PathBuf::from(&self.path); lock_path.push(".drive.lck"); + let options = CreateOptions::new(); let timeout = std::time::Duration::new(10, 0); - let lock = proxmox::tools::fs::open_file_locked(&lock_path, timeout, true)?; + let lock = proxmox::tools::fs::open_file_locked(&lock_path, timeout, true, options)?; Ok(VirtualTapeHandle { _lock: lock, diff --git a/src/tape/inventory.rs b/src/tape/inventory.rs index 4bb6d4f8..e97f07f3 100644 --- a/src/tape/inventory.rs +++ b/src/tape/inventory.rs @@ -24,8 +24,6 @@ use std::collections::{HashMap, BTreeMap}; use std::path::{Path, PathBuf}; -use std::os::unix::io::AsRawFd; -use std::fs::File; use std::time::Duration; use anyhow::{bail, Error}; @@ -35,9 +33,7 @@ use serde_json::json; use proxmox::tools::{ Uuid, fs::{ - open_file_locked, replace_file, - fchown, file_get_json, CreateOptions, }, @@ -51,6 +47,7 @@ use crate::{ MediaStatus, MediaLocation, }, + backup::{open_backup_lockfile, BackupLockGuard}, tape::{ TAPE_STATUS_DIR, MediaSet, @@ -149,17 +146,8 @@ impl Inventory { } /// Lock the database - fn lock(&self) -> Result<std::fs::File, Error> { - let file = open_file_locked(&self.lockfile_path, std::time::Duration::new(10, 0), true)?; - if cfg!(test) { - // We cannot use chown inside test environment (no permissions) - return Ok(file); - } - - let backup_user = crate::backup::backup_user()?; - fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; - - Ok(file) + fn lock(&self) -> Result<BackupLockGuard, Error> { + open_backup_lockfile(&self.lockfile_path, None, true) } fn load_media_db(path: &Path) -> Result<BTreeMap<Uuid, MediaStateEntry>, Error> { @@ -756,27 +744,16 @@ impl Inventory { } /// Lock a media pool -pub fn lock_media_pool(base_path: &Path, name: &str) -> Result<File, Error> { +pub fn lock_media_pool(base_path: &Path, name: &str) -> Result<BackupLockGuard, Error> { let mut path = base_path.to_owned(); path.push(format!(".pool-{}", name)); path.set_extension("lck"); - let timeout = std::time::Duration::new(10, 0); - let lock = proxmox::tools::fs::open_file_locked(&path, timeout, true)?; - - if cfg!(test) { - // We cannot use chown inside test environment (no permissions) - return Ok(lock); - } - - let backup_user = crate::backup::backup_user()?; - fchown(lock.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; - - Ok(lock) + open_backup_lockfile(&path, None, true) } /// Lock for media not assigned to any pool -pub fn lock_unassigned_media_pool(base_path: &Path) -> Result<File, Error> { +pub fn lock_unassigned_media_pool(base_path: &Path) -> Result<BackupLockGuard, Error> { // lock artificial "__UNASSIGNED__" pool to avoid races lock_media_pool(base_path, "__UNASSIGNED__") } @@ -788,22 +765,12 @@ pub fn lock_media_set( base_path: &Path, media_set_uuid: &Uuid, timeout: Option<Duration>, -) -> Result<File, Error> { +) -> Result<BackupLockGuard, Error> { let mut path = base_path.to_owned(); path.push(format!(".media-set-{}", media_set_uuid)); path.set_extension("lck"); - let timeout = timeout.unwrap_or(Duration::new(10, 0)); - let file = open_file_locked(&path, timeout, true)?; - if cfg!(test) { - // We cannot use chown inside test environment (no permissions) - return Ok(file); - } - - let backup_user = crate::backup::backup_user()?; - fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; - - Ok(file) + open_backup_lockfile(&path, timeout, true) } // shell completion helper diff --git a/src/tape/media_pool.rs b/src/tape/media_pool.rs index dd471551..7bb7fbdb 100644 --- a/src/tape/media_pool.rs +++ b/src/tape/media_pool.rs @@ -8,7 +8,6 @@ //! use std::path::{PathBuf, Path}; -use std::fs::File; use anyhow::{bail, Error}; use ::serde::{Deserialize, Serialize}; @@ -16,7 +15,7 @@ use ::serde::{Deserialize, Serialize}; use proxmox::tools::Uuid; use crate::{ - backup::Fingerprint, + backup::{Fingerprint, BackupLockGuard}, api2::types::{ MediaStatus, MediaLocation, @@ -61,7 +60,7 @@ pub struct MediaPool { inventory: Inventory, current_media_set: MediaSet, - current_media_set_lock: Option<File>, + current_media_set_lock: Option<BackupLockGuard>, } impl MediaPool { diff --git a/src/tools/memcom.rs b/src/tools/memcom.rs index 9921f5c9..87b73561 100644 --- a/src/tools/memcom.rs +++ b/src/tools/memcom.rs @@ -1,21 +1,17 @@ //! Memory based communication channel between proxy & daemon for things such as cache //! invalidation. -use std::ffi::CString; -use std::io; use std::os::unix::io::AsRawFd; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; -use anyhow::{bail, format_err, Error}; -use nix::errno::Errno; +use anyhow::Error; use nix::fcntl::OFlag; use nix::sys::mman::{MapFlags, ProtFlags}; use nix::sys::stat::Mode; use once_cell::sync::OnceCell; -use proxmox::sys::error::SysError; -use proxmox::tools::fd::Fd; +use proxmox::tools::fs::CreateOptions; use proxmox::tools::mmap::Mmap; /// In-memory communication channel. @@ -32,6 +28,7 @@ struct Head { static INSTANCE: OnceCell<Arc<Memcom>> = OnceCell::new(); const MEMCOM_FILE_PATH: &str = pbs_buildcfg::rundir!("/proxmox-backup-memcom"); +const EMPTY_PAGE: [u8; 4096] = [0u8; 4096]; impl Memcom { /// Open the memory based communication channel singleton. @@ -41,15 +38,20 @@ impl Memcom { // Actual work of `new`: fn open() -> Result<Arc<Self>, Error> { - let fd = match open_existing() { - Ok(fd) => fd, - Err(err) if err.not_found() => create_new()?, - Err(err) => bail!("failed to open {} - {}", MEMCOM_FILE_PATH, err), - }; + let user = crate::backup::backup_user()?; + let options = CreateOptions::new() + .perm(Mode::from_bits_truncate(0o660)) + .owner(user.uid) + .group(user.gid); + + let file = proxmox::tools::fs::atomic_open_or_create_file( + MEMCOM_FILE_PATH, + OFlag::O_RDWR | OFlag::O_CLOEXEC, + &EMPTY_PAGE, options)?; let mmap = unsafe { Mmap::<u8>::map_fd( - fd.as_raw_fd(), + file.as_raw_fd(), 0, 4096, ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, @@ -77,87 +79,3 @@ impl Memcom { .fetch_add(1, Ordering::AcqRel); } } - -/// The fast path opens an existing file. -fn open_existing() -> Result<Fd, nix::Error> { - Fd::open( - MEMCOM_FILE_PATH, - OFlag::O_RDWR | OFlag::O_CLOEXEC, - Mode::empty(), - ) -} - -/// Since we need to initialize the file, we also need a solid slow path where we create the file. -/// In order to make sure the next user's `open()` vs `mmap()` race against our `truncate()` call, -/// we create it in a temporary location and rotate it in place. -fn create_new() -> Result<Fd, Error> { - // create a temporary file: - let temp_file_name = format!("{}.{}", MEMCOM_FILE_PATH, unsafe { libc::getpid() }); - let fd = Fd::open( - temp_file_name.as_str(), - OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_RDWR | OFlag::O_CLOEXEC, - Mode::from_bits_truncate(0o660), - ) - .map_err(|err| { - format_err!( - "failed to create new in-memory communication file at {} - {}", - temp_file_name, - err - ) - })?; - - // let it be a page in size, it'll be initialized to zero by the kernel - nix::unistd::ftruncate(fd.as_raw_fd(), 4096) - .map_err(|err| format_err!("failed to set size of {} - {}", temp_file_name, err))?; - - // if this is the pbs-daemon (running as root) rather than the proxy (running as backup user), - // make sure the backup user can access the file: - if let Ok(backup_user) = crate::backup::backup_user() { - match nix::unistd::fchown(fd.as_raw_fd(), None, Some(backup_user.gid)) { - Ok(()) => (), - Err(err) if err.is_errno(Errno::EPERM) => { - // we're not the daemon (root), so the file is already owned by the backup user - } - Err(err) => bail!( - "failed to set group to 'backup' for {} - {}", - temp_file_name, - err - ), - } - } - - // rotate the file into place, but use `RENAME_NOREPLACE`, so in case 2 processes race against - // the initialization, the first one wins! - // TODO: nicer `renameat2()` wrapper in `proxmox::sys`? - let c_file_name = CString::new(temp_file_name.as_bytes()).unwrap(); - let new_path = CString::new(MEMCOM_FILE_PATH).unwrap(); - let rc = unsafe { - libc::renameat2( - -1, - c_file_name.as_ptr(), - -1, - new_path.as_ptr(), - libc::RENAME_NOREPLACE, - ) - }; - if rc == 0 { - return Ok(fd); - } - let err = io::Error::last_os_error(); - - // if another process has already raced ahead and created the file, let's just open theirs - // instead: - if err.kind() == io::ErrorKind::AlreadyExists { - // someone beat us to it: - drop(fd); - return open_existing().map_err(Error::from); - } - - // for any other errors, just bail out - bail!( - "failed to move file at {} into place at {} - {}", - temp_file_name, - MEMCOM_FILE_PATH, - err - ); -} -- 2.30.2 ^ permalink raw reply [flat|nested] 8+ messages in thread
[parent not found: <<20210716082834.2354163-2-dietmar@proxmox.com>]
* Re: [pbs-devel] [PATCH proxmox-backup 1/2] use new atomic_open_or_create_file [not found] ` <<20210716082834.2354163-2-dietmar@proxmox.com> @ 2021-07-19 10:45 ` Fabian Grünbichler 0 siblings, 0 replies; 8+ messages in thread From: Fabian Grünbichler @ 2021-07-19 10:45 UTC (permalink / raw) To: Proxmox Backup Server development discussion this breaks tests and thus builds as unprivileged user: locking -> creating temp file -> fchown -> EPERM! it (or the next patch?) is missing one no-longer-needed import: warning: unused import: `nix::sys::stat::Mode` --> src/config/node.rs:4:5 | 4 | use nix::sys::stat::Mode; | ^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default and one call-site: diff --git a/src/api2/access/openid.rs b/src/api2/access/openid.rs index 783864cc..efcc0dbb 100644 --- a/src/api2/access/openid.rs +++ b/src/api2/access/openid.rs @@ -9,7 +9,6 @@ use proxmox::api::router::{Router, SubdirMap}; use proxmox::api::{api, Permission, RpcEnvironment}; use proxmox::{list_subdirs_api_method}; use proxmox::{identity, sortable}; -use proxmox::tools::fs::open_file_locked; use proxmox_openid::{OpenIdAuthenticator, OpenIdConfig}; @@ -19,6 +18,7 @@ use pbs_tools::ticket::Ticket; use crate::server::ticket::ApiTicket; +use crate::backup::open_backup_lockfile; use crate::config::domains::{OpenIdUserAttribute, OpenIdRealmConfig}; use crate::config::cached_user_info::CachedUserInfo; @@ -117,7 +117,7 @@ pub fn openid_login( if !user_info.is_active_user_id(&user_id) { if config.autocreate.unwrap_or(false) { use crate::config::user; - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; let user = user::User { userid: user_id.clone(), comment: None, On July 16, 2021 10:28 am, Dietmar Maurer wrote: > Factor out open_backup_lockfile() method to acquire locks owned by > user backup with permission 0660. > --- > src/api2/access/acl.rs | 4 +- > src/api2/access/user.rs | 14 +-- > src/api2/config/datastore.rs | 2 +- > src/api2/config/remote.rs | 8 +- > src/api2/config/sync.rs | 8 +- > src/api2/config/tape_backup_job.rs | 9 +- > src/api2/config/tape_encryption_keys.rs | 15 +--- > src/api2/config/verify.rs | 9 +- > src/api2/node/disks/directory.rs | 4 +- > src/api2/node/network.rs | 9 +- > src/backup/datastore.rs | 9 +- > src/backup/mod.rs | 26 ++++++ > src/config/acme/plugin.rs | 6 +- > src/config/datastore.rs | 6 +- > src/config/domains.rs | 6 +- > src/config/drive.rs | 6 +- > src/config/media_pool.rs | 6 +- > src/config/node.rs | 8 +- > src/config/tape_encryption_keys.rs | 8 +- > src/config/tfa.rs | 11 ++- > src/config/token_shadow.rs | 9 +- > src/server/jobstate.rs | 16 ++-- > src/server/worker_task.rs | 14 ++- > src/tape/drive/mod.rs | 46 +++++----- > src/tape/drive/virtual_tape.rs | 3 +- > src/tape/inventory.rs | 49 ++--------- > src/tape/media_pool.rs | 5 +- > src/tools/memcom.rs | 110 +++--------------------- > 28 files changed, 158 insertions(+), 268 deletions(-) > > diff --git a/src/api2/access/acl.rs b/src/api2/access/acl.rs > index a78aabcd..88a2667c 100644 > --- a/src/api2/access/acl.rs > +++ b/src/api2/access/acl.rs > @@ -3,12 +3,12 @@ > use anyhow::{bail, Error}; > > use proxmox::api::{api, Router, RpcEnvironment, Permission}; > -use proxmox::tools::fs::open_file_locked; > > use crate::api2::types::*; > use crate::config::acl; > use crate::config::acl::{Role, PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY}; > use crate::config::cached_user_info::CachedUserInfo; > +use crate::backup::open_backup_lockfile; > > fn extract_acl_node_data( > node: &acl::AclTreeNode, > @@ -200,7 +200,7 @@ pub fn update_acl( > }; > } > > - let _lock = open_file_locked(acl::ACL_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(acl::ACL_CFG_LOCKFILE, None, true)?; > > let (mut tree, expected_digest) = acl::config()?; > > diff --git a/src/api2/access/user.rs b/src/api2/access/user.rs > index e080d57a..23c999b4 100644 > --- a/src/api2/access/user.rs > +++ b/src/api2/access/user.rs > @@ -8,13 +8,13 @@ use std::collections::HashMap; > use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; > use proxmox::api::router::SubdirMap; > use proxmox::api::schema::{Schema, StringSchema}; > -use proxmox::tools::fs::open_file_locked; > > use crate::api2::types::*; > use crate::config::user; > use crate::config::token_shadow; > use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY}; > use crate::config::cached_user_info::CachedUserInfo; > +use crate::backup::open_backup_lockfile; > > pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.") > .format(&PASSWORD_FORMAT) > @@ -226,7 +226,7 @@ pub fn create_user( > rpcenv: &mut dyn RpcEnvironment > ) -> Result<(), Error> { > > - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; > > let user: user::User = serde_json::from_value(param)?; > > @@ -368,7 +368,7 @@ pub fn update_user( > rpcenv: &mut dyn RpcEnvironment, > ) -> Result<(), Error> { > > - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; > > let (mut config, expected_digest) = user::config()?; > > @@ -461,7 +461,7 @@ pub fn update_user( > pub fn delete_user(userid: Userid, digest: Option<String>) -> Result<(), Error> { > > let _tfa_lock = crate::config::tfa::write_lock()?; > - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; > > let (mut config, expected_digest) = user::config()?; > > @@ -597,7 +597,7 @@ pub fn generate_token( > digest: Option<String>, > ) -> Result<Value, Error> { > > - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; > > let (mut config, expected_digest) = user::config()?; > > @@ -678,7 +678,7 @@ pub fn update_token( > digest: Option<String>, > ) -> Result<(), Error> { > > - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; > > let (mut config, expected_digest) = user::config()?; > > @@ -746,7 +746,7 @@ pub fn delete_token( > digest: Option<String>, > ) -> Result<(), Error> { > > - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; > > let (mut config, expected_digest) = user::config()?; > > diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs > index 19b822d4..a55e3bdc 100644 > --- a/src/api2/config/datastore.rs > +++ b/src/api2/config/datastore.rs > @@ -60,7 +60,7 @@ pub fn list_datastores( > } > > pub(crate) fn do_create_datastore( > - _lock: std::fs::File, > + _lock: BackupLockGuard, > mut config: SectionConfigData, > datastore: DataStoreConfig, > worker: Option<&dyn TaskState>, > diff --git a/src/api2/config/remote.rs b/src/api2/config/remote.rs > index 446a2604..33c99bff 100644 > --- a/src/api2/config/remote.rs > +++ b/src/api2/config/remote.rs > @@ -4,13 +4,13 @@ use ::serde::{Deserialize, Serialize}; > > use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; > use proxmox::http_err; > -use proxmox::tools::fs::open_file_locked; > > use crate::api2::types::*; > use crate::client::{HttpClient, HttpClientOptions}; > use crate::config::cached_user_info::CachedUserInfo; > use crate::config::remote; > use crate::config::acl::{PRIV_REMOTE_AUDIT, PRIV_REMOTE_MODIFY}; > +use crate::backup::open_backup_lockfile; > > #[api( > input: { > @@ -94,7 +94,7 @@ pub fn list_remotes( > /// Create new remote. > pub fn create_remote(password: String, param: Value) -> Result<(), Error> { > > - let _lock = open_file_locked(remote::REMOTE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(remote::REMOTE_CFG_LOCKFILE, None, true)?; > > let mut data = param; > data["password"] = Value::from(base64::encode(password.as_bytes())); > @@ -216,7 +216,7 @@ pub fn update_remote( > digest: Option<String>, > ) -> Result<(), Error> { > > - let _lock = open_file_locked(remote::REMOTE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(remote::REMOTE_CFG_LOCKFILE, None, true)?; > > let (mut config, expected_digest) = remote::config()?; > > @@ -290,7 +290,7 @@ pub fn delete_remote(name: String, digest: Option<String>) -> Result<(), Error> > } > } > > - let _lock = open_file_locked(remote::REMOTE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(remote::REMOTE_CFG_LOCKFILE, None, true)?; > > let (mut config, expected_digest) = remote::config()?; > > diff --git a/src/api2/config/sync.rs b/src/api2/config/sync.rs > index e784029a..bc7b9f24 100644 > --- a/src/api2/config/sync.rs > +++ b/src/api2/config/sync.rs > @@ -3,7 +3,6 @@ use serde_json::Value; > use ::serde::{Deserialize, Serialize}; > > use proxmox::api::{api, Permission, Router, RpcEnvironment}; > -use proxmox::tools::fs::open_file_locked; > > use crate::api2::types::*; > > @@ -18,6 +17,7 @@ use crate::config::acl::{ > > use crate::config::cached_user_info::CachedUserInfo; > use crate::config::sync::{self, SyncJobConfig}; > +use crate::backup::open_backup_lockfile; > > pub fn check_sync_job_read_access( > user_info: &CachedUserInfo, > @@ -152,7 +152,7 @@ pub fn create_sync_job( > let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; > let user_info = CachedUserInfo::new()?; > > - let _lock = open_file_locked(sync::SYNC_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(sync::SYNC_CFG_LOCKFILE, None, true)?; > > let sync_job: sync::SyncJobConfig = serde_json::from_value(param)?; > if !check_sync_job_modify_access(&user_info, &auth_id, &sync_job) { > @@ -296,7 +296,7 @@ pub fn update_sync_job( > let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; > let user_info = CachedUserInfo::new()?; > > - let _lock = open_file_locked(sync::SYNC_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(sync::SYNC_CFG_LOCKFILE, None, true)?; > > // pass/compare digest > let (mut config, expected_digest) = sync::config()?; > @@ -379,7 +379,7 @@ pub fn delete_sync_job( > let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; > let user_info = CachedUserInfo::new()?; > > - let _lock = open_file_locked(sync::SYNC_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(sync::SYNC_CFG_LOCKFILE, None, true)?; > > let (mut config, expected_digest) = sync::config()?; > > diff --git a/src/api2/config/tape_backup_job.rs b/src/api2/config/tape_backup_job.rs > index 22afeb6d..02fa6f7d 100644 > --- a/src/api2/config/tape_backup_job.rs > +++ b/src/api2/config/tape_backup_job.rs > @@ -3,7 +3,6 @@ use serde_json::Value; > use ::serde::{Deserialize, Serialize}; > > use proxmox::api::{api, Router, RpcEnvironment, Permission}; > -use proxmox::tools::fs::open_file_locked; > > use crate::{ > api2::types::{ > @@ -17,6 +16,7 @@ use crate::{ > MEDIA_POOL_NAME_SCHEMA, > SYNC_SCHEDULE_SCHEMA, > }, > + backup::open_backup_lockfile, > config::{ > self, > cached_user_info::CachedUserInfo, > @@ -89,8 +89,7 @@ pub fn create_tape_backup_job( > job: TapeBackupJobConfig, > _rpcenv: &mut dyn RpcEnvironment, > ) -> Result<(), Error> { > - > - let _lock = open_file_locked(TAPE_JOB_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(TAPE_JOB_CFG_LOCKFILE, None, true)?; > > let (mut config, _digest) = config::tape_job::config()?; > > @@ -233,7 +232,7 @@ pub fn update_tape_backup_job( > delete: Option<Vec<DeletableProperty>>, > digest: Option<String>, > ) -> Result<(), Error> { > - let _lock = open_file_locked(TAPE_JOB_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(TAPE_JOB_CFG_LOCKFILE, None, true)?; > > let (mut config, expected_digest) = config::tape_job::config()?; > > @@ -312,7 +311,7 @@ pub fn delete_tape_backup_job( > digest: Option<String>, > _rpcenv: &mut dyn RpcEnvironment, > ) -> Result<(), Error> { > - let _lock = open_file_locked(TAPE_JOB_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(TAPE_JOB_CFG_LOCKFILE, None, true)?; > > let (mut config, expected_digest) = config::tape_job::config()?; > > diff --git a/src/api2/config/tape_encryption_keys.rs b/src/api2/config/tape_encryption_keys.rs > index e1b42fb8..6fbfaded 100644 > --- a/src/api2/config/tape_encryption_keys.rs > +++ b/src/api2/config/tape_encryption_keys.rs > @@ -9,7 +9,6 @@ use proxmox::{ > RpcEnvironment, > Permission, > }, > - tools::fs::open_file_locked, > }; > > use pbs_datastore::{KeyInfo, Kdf}; > @@ -35,6 +34,7 @@ use crate::{ > PASSWORD_HINT_SCHEMA, > }, > backup::{ > + open_backup_lockfile, > KeyConfig, > Fingerprint, > }, > @@ -122,11 +122,7 @@ pub fn change_passphrase( > bail!("Please specify a key derivation function (none is not allowed here)."); > } > > - let _lock = open_file_locked( > - TAPE_KEYS_LOCKFILE, > - std::time::Duration::new(10, 0), > - true, > - )?; > + let _lock = open_backup_lockfile(TAPE_KEYS_LOCKFILE, None, true)?; > > let (mut config_map, expected_digest) = load_key_configs()?; > > @@ -261,12 +257,7 @@ pub fn delete_key( > digest: Option<String>, > _rpcenv: &mut dyn RpcEnvironment, > ) -> Result<(), Error> { > - > - let _lock = open_file_locked( > - TAPE_KEYS_LOCKFILE, > - std::time::Duration::new(10, 0), > - true, > - )?; > + let _lock = open_backup_lockfile(TAPE_KEYS_LOCKFILE, None, true)?; > > let (mut config_map, expected_digest) = load_key_configs()?; > let (mut key_map, _) = load_keys()?; > diff --git a/src/api2/config/verify.rs b/src/api2/config/verify.rs > index 477cda89..1a613327 100644 > --- a/src/api2/config/verify.rs > +++ b/src/api2/config/verify.rs > @@ -3,7 +3,6 @@ use serde_json::Value; > use ::serde::{Deserialize, Serialize}; > > use proxmox::api::{api, Permission, Router, RpcEnvironment}; > -use proxmox::tools::fs::open_file_locked; > > use crate::api2::types::*; > > @@ -13,8 +12,8 @@ use crate::config::acl::{ > }; > > use crate::config::cached_user_info::CachedUserInfo; > - > use crate::config::verify::{self, VerificationJobConfig}; > +use crate::backup::open_backup_lockfile; > > #[api( > input: { > @@ -102,7 +101,7 @@ pub fn create_verification_job( > > user_info.check_privs(&auth_id, &["datastore", &verification_job.store], PRIV_DATASTORE_VERIFY, false)?; > > - let _lock = open_file_locked(verify::VERIFICATION_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(verify::VERIFICATION_CFG_LOCKFILE, None, true)?; > > let (mut config, _digest) = verify::config()?; > > @@ -230,7 +229,7 @@ pub fn update_verification_job( > let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; > let user_info = CachedUserInfo::new()?; > > - let _lock = open_file_locked(verify::VERIFICATION_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(verify::VERIFICATION_CFG_LOCKFILE, None, true)?; > > // pass/compare digest > let (mut config, expected_digest) = verify::config()?; > @@ -315,7 +314,7 @@ pub fn delete_verification_job( > let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; > let user_info = CachedUserInfo::new()?; > > - let _lock = open_file_locked(verify::VERIFICATION_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(verify::VERIFICATION_CFG_LOCKFILE, None, true)?; > > let (mut config, expected_digest) = verify::config()?; > > diff --git a/src/api2/node/disks/directory.rs b/src/api2/node/disks/directory.rs > index ae8a3974..75e0ea02 100644 > --- a/src/api2/node/disks/directory.rs > +++ b/src/api2/node/disks/directory.rs > @@ -5,7 +5,6 @@ use ::serde::{Deserialize, Serialize}; > use proxmox::api::{api, Permission, RpcEnvironment, RpcEnvironmentType}; > use proxmox::api::section_config::SectionConfigData; > use proxmox::api::router::Router; > -use proxmox::tools::fs::open_file_locked; > > use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; > use crate::tools::disks::{ > @@ -18,6 +17,7 @@ use crate::server::WorkerTask; > > use crate::api2::types::*; > use crate::config::datastore::{self, DataStoreConfig}; > +use crate::backup::open_backup_lockfile; > > #[api( > properties: { > @@ -180,7 +180,7 @@ pub fn create_datastore_disk( > systemd::start_unit(&mount_unit_name)?; > > if add_datastore { > - let lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let lock = open_backup_lockfile(datastore::DATASTORE_CFG_LOCKFILE, None, true)?; > let datastore: DataStoreConfig = > serde_json::from_value(json!({ "name": name, "path": mount_point }))?; > > diff --git a/src/api2/node/network.rs b/src/api2/node/network.rs > index 0dc321b8..ecf112a4 100644 > --- a/src/api2/node/network.rs > +++ b/src/api2/node/network.rs > @@ -4,12 +4,12 @@ use ::serde::{Deserialize, Serialize}; > > use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; > use proxmox::api::schema::parse_property_string; > -use proxmox::tools::fs::open_file_locked; > > use crate::config::network::{self, NetworkConfig}; > use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; > use crate::api2::types::*; > use crate::server::{WorkerTask}; > +use crate::backup::open_backup_lockfile; > > fn split_interface_list(list: &str) -> Result<Vec<String>, Error> { > let value = parse_property_string(&list, &NETWORK_INTERFACE_ARRAY_SCHEMA)?; > @@ -238,7 +238,7 @@ pub fn create_interface( > let interface_type = crate::tools::required_string_param(¶m, "type")?; > let interface_type: NetworkInterfaceType = serde_json::from_value(interface_type.into())?; > > - let _lock = open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(network::NETWORK_LOCKFILE, None, true)?; > > let (mut config, _digest) = network::config()?; > > @@ -502,7 +502,7 @@ pub fn update_interface( > param: Value, > ) -> Result<(), Error> { > > - let _lock = open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(network::NETWORK_LOCKFILE, None, true)?; > > let (mut config, expected_digest) = network::config()?; > > @@ -642,8 +642,7 @@ pub fn update_interface( > )] > /// Remove network interface configuration. > pub fn delete_interface(iface: String, digest: Option<String>) -> Result<(), Error> { > - > - let _lock = open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0), true)?; > + let _lock = open_backup_lockfile(network::NETWORK_LOCKFILE, None, true)?; > > let (mut config, expected_digest) = network::config()?; > > diff --git a/src/backup/datastore.rs b/src/backup/datastore.rs > index d47c412b..5695adcc 100644 > --- a/src/backup/datastore.rs > +++ b/src/backup/datastore.rs > @@ -5,12 +5,11 @@ use std::sync::{Arc, Mutex}; > use std::convert::TryFrom; > use std::str::FromStr; > use std::time::Duration; > -use std::fs::File; > > use anyhow::{bail, format_err, Error}; > use lazy_static::lazy_static; > > -use proxmox::tools::fs::{replace_file, file_read_optional_string, CreateOptions, open_file_locked}; > +use proxmox::tools::fs::{replace_file, file_read_optional_string, CreateOptions}; > > use pbs_api_types::upid::UPID; > use pbs_api_types::{Authid, GarbageCollectionStatus}; > @@ -32,6 +31,8 @@ use pbs_tools::fs::{lock_dir_noblock, DirLockGuard}; > > use crate::config::datastore::{self, DataStoreConfig}; > use crate::tools; > +use crate::backup::{open_backup_lockfile, BackupLockGuard}; > + > > lazy_static! { > static ref DATASTORE_MAP: Mutex<HashMap<String, Arc<DataStore>>> = Mutex::new(HashMap::new()); > @@ -777,12 +778,12 @@ impl DataStore { > fn lock_manifest( > &self, > backup_dir: &BackupDir, > - ) -> Result<File, Error> { > + ) -> Result<BackupLockGuard, Error> { > let path = self.manifest_lock_path(backup_dir)?; > > // update_manifest should never take a long time, so if someone else has > // the lock we can simply block a bit and should get it soon > - open_file_locked(&path, Duration::from_secs(5), true) > + open_backup_lockfile(&path, Some(Duration::from_secs(5)), true) > .map_err(|err| { > format_err!( > "unable to acquire manifest lock {:?} - {}", &path, err > diff --git a/src/backup/mod.rs b/src/backup/mod.rs > index 4161b402..1db61cf5 100644 > --- a/src/backup/mod.rs > +++ b/src/backup/mod.rs > @@ -108,3 +108,29 @@ pub use catalog_shell::*; > > mod cached_chunk_reader; > pub use cached_chunk_reader::*; > + > +pub struct BackupLockGuard(std::fs::File); > + > +/// Open or create a lock file owned by user "backup" and lock it. > +/// > +/// Owner/Group of the file is set to backup/backup. > +/// File mode is 0660. > +/// Default timeout is 10 seconds. > +/// > +/// Note: This method needs to be called by user "root" or "backup". > +pub fn open_backup_lockfile<P: AsRef<std::path::Path>>( > + path: P, > + timeout: Option<std::time::Duration>, > + exclusive: bool, > +) -> Result<BackupLockGuard, Error> { > + let user = backup_user()?; > + let options = proxmox::tools::fs::CreateOptions::new() > + .perm(nix::sys::stat::Mode::from_bits_truncate(0o660)) > + .owner(user.uid) > + .group(user.gid); > + > + let timeout = timeout.unwrap_or(std::time::Duration::new(10, 0)); > + > + let file = proxmox::tools::fs::open_file_locked(&path, timeout, exclusive, options)?; > + Ok(BackupLockGuard(file)) > +} > diff --git a/src/config/acme/plugin.rs b/src/config/acme/plugin.rs > index 960aec3a..fde800e2 100644 > --- a/src/config/acme/plugin.rs > +++ b/src/config/acme/plugin.rs > @@ -12,6 +12,7 @@ use proxmox::api::{ > use proxmox::tools::{fs::replace_file, fs::CreateOptions}; > > use crate::api2::types::PROXMOX_SAFE_ID_FORMAT; > +use crate::backup::{open_backup_lockfile, BackupLockGuard}; > > pub const PLUGIN_ID_SCHEMA: Schema = StringSchema::new("ACME Challenge Plugin ID.") > .format(&PROXMOX_SAFE_ID_FORMAT) > @@ -142,11 +143,10 @@ fn init() -> SectionConfig { > > const ACME_PLUGIN_CFG_FILENAME: &str = pbs_buildcfg::configdir!("/acme/plugins.cfg"); > const ACME_PLUGIN_CFG_LOCKFILE: &str = pbs_buildcfg::configdir!("/acme/.plugins.lck"); > -const LOCK_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); > > -pub fn lock() -> Result<std::fs::File, Error> { > +pub fn lock() -> Result<BackupLockGuard, Error> { > super::make_acme_dir()?; > - proxmox::tools::fs::open_file_locked(ACME_PLUGIN_CFG_LOCKFILE, LOCK_TIMEOUT, true) > + open_backup_lockfile(ACME_PLUGIN_CFG_LOCKFILE, None, true) > } > > pub fn config() -> Result<(PluginData, [u8; 32]), Error> { > diff --git a/src/config/datastore.rs b/src/config/datastore.rs > index d22fa316..9e37073d 100644 > --- a/src/config/datastore.rs > +++ b/src/config/datastore.rs > @@ -14,12 +14,12 @@ use proxmox::api::{ > }; > > use proxmox::tools::fs::{ > - open_file_locked, > replace_file, > CreateOptions, > }; > > use crate::api2::types::*; > +use crate::backup::{open_backup_lockfile, BackupLockGuard}; > > lazy_static! { > pub static ref CONFIG: SectionConfig = init(); > @@ -138,8 +138,8 @@ pub const DATASTORE_CFG_FILENAME: &str = "/etc/proxmox-backup/datastore.cfg"; > pub const DATASTORE_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.datastore.lck"; > > /// Get exclusive lock > -pub fn lock_config() -> Result<std::fs::File, Error> { > - open_file_locked(DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true) > +pub fn lock_config() -> Result<BackupLockGuard, Error> { > + open_backup_lockfile(DATASTORE_CFG_LOCKFILE, None, true) > } > > pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { > diff --git a/src/config/domains.rs b/src/config/domains.rs > index 775c02f3..9f513a44 100644 > --- a/src/config/domains.rs > +++ b/src/config/domains.rs > @@ -14,12 +14,12 @@ use proxmox::api::{ > }; > > use proxmox::tools::fs::{ > - open_file_locked, > replace_file, > CreateOptions, > }; > > use crate::api2::types::*; > +use crate::backup::{open_backup_lockfile, BackupLockGuard}; > > lazy_static! { > pub static ref CONFIG: SectionConfig = init(); > @@ -110,8 +110,8 @@ pub const DOMAINS_CFG_FILENAME: &str = "/etc/proxmox-backup/domains.cfg"; > pub const DOMAINS_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.domains.lck"; > > /// Get exclusive lock > -pub fn lock_config() -> Result<std::fs::File, Error> { > - open_file_locked(DOMAINS_CFG_LOCKFILE, std::time::Duration::new(10, 0), true) > +pub fn lock_config() -> Result<BackupLockGuard, Error> { > + open_backup_lockfile(DOMAINS_CFG_LOCKFILE, None, true) > } > > pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { > diff --git a/src/config/drive.rs b/src/config/drive.rs > index 57f6911f..9c20051f 100644 > --- a/src/config/drive.rs > +++ b/src/config/drive.rs > @@ -26,13 +26,13 @@ use proxmox::{ > }, > }, > tools::fs::{ > - open_file_locked, > replace_file, > CreateOptions, > }, > }; > > use crate::{ > + backup::{open_backup_lockfile, BackupLockGuard}, > api2::types::{ > DRIVE_NAME_SCHEMA, > VirtualTapeDrive, > @@ -79,8 +79,8 @@ pub const DRIVE_CFG_FILENAME: &str = "/etc/proxmox-backup/tape.cfg"; > pub const DRIVE_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.tape.lck"; > > /// Get exclusive lock > -pub fn lock() -> Result<std::fs::File, Error> { > - open_file_locked(DRIVE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true) > +pub fn lock() -> Result<BackupLockGuard, Error> { > + open_backup_lockfile(DRIVE_CFG_LOCKFILE, None, true) > } > > /// Read and parse the configuration file > diff --git a/src/config/media_pool.rs b/src/config/media_pool.rs > index 5ba1a6b2..e50992d8 100644 > --- a/src/config/media_pool.rs > +++ b/src/config/media_pool.rs > @@ -21,13 +21,13 @@ use proxmox::{ > } > }, > tools::fs::{ > - open_file_locked, > replace_file, > CreateOptions, > }, > }; > > use crate::{ > + backup::{open_backup_lockfile, BackupLockGuard}, > api2::types::{ > MEDIA_POOL_NAME_SCHEMA, > MediaPoolConfig, > @@ -59,8 +59,8 @@ pub const MEDIA_POOL_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.media-pool.lck"; > > > /// Get exclusive lock > -pub fn lock() -> Result<std::fs::File, Error> { > - open_file_locked(MEDIA_POOL_CFG_LOCKFILE, std::time::Duration::new(10, 0), true) > +pub fn lock() -> Result<BackupLockGuard, Error> { > + open_backup_lockfile(MEDIA_POOL_CFG_LOCKFILE, None, true) > } > > /// Read and parse the configuration file > diff --git a/src/config/node.rs b/src/config/node.rs > index 27e32cd1..dc3eeeb0 100644 > --- a/src/config/node.rs > +++ b/src/config/node.rs > @@ -1,6 +1,4 @@ > use std::collections::HashSet; > -use std::fs::File; > -use std::time::Duration; > > use anyhow::{bail, Error}; > use nix::sys::stat::Mode; > @@ -14,6 +12,7 @@ use proxmox_http::ProxyConfig; > > use pbs_buildcfg::configdir; > > +use crate::backup::{open_backup_lockfile, BackupLockGuard}; > use crate::acme::AcmeClient; > use crate::api2::types::{ > AcmeAccountName, AcmeDomain, ACME_DOMAIN_PROPERTY_SCHEMA, HTTP_PROXY_SCHEMA, > @@ -21,10 +20,9 @@ use crate::api2::types::{ > > const CONF_FILE: &str = configdir!("/node.cfg"); > const LOCK_FILE: &str = configdir!("/.node.lck"); > -const LOCK_TIMEOUT: Duration = Duration::from_secs(10); > > -pub fn lock() -> Result<File, Error> { > - proxmox::tools::fs::open_file_locked(LOCK_FILE, LOCK_TIMEOUT, true) > +pub fn lock() -> Result<BackupLockGuard, Error> { > + open_backup_lockfile(LOCK_FILE, None, true) > } > > /// Read the Node Config. > diff --git a/src/config/tape_encryption_keys.rs b/src/config/tape_encryption_keys.rs > index e3aab1d8..5ee0ac1f 100644 > --- a/src/config/tape_encryption_keys.rs > +++ b/src/config/tape_encryption_keys.rs > @@ -18,12 +18,12 @@ use serde::{Deserialize, Serialize}; > use proxmox::tools::fs::{ > file_read_optional_string, > replace_file, > - open_file_locked, > CreateOptions, > }; > > use crate::{ > backup::{ > + open_backup_lockfile, > Fingerprint, > KeyConfig, > }, > @@ -187,11 +187,7 @@ pub fn save_key_configs(map: HashMap<Fingerprint, KeyConfig>) -> Result<(), Erro > /// 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> { > > - let _lock = open_file_locked( > - TAPE_KEYS_LOCKFILE, > - std::time::Duration::new(10, 0), > - true, > - )?; > + let _lock = open_backup_lockfile(TAPE_KEYS_LOCKFILE, None, true)?; > > let (mut key_map, _) = load_keys()?; > let (mut config_map, _) = load_key_configs()?; > diff --git a/src/config/tfa.rs b/src/config/tfa.rs > index 341307db..efba0c13 100644 > --- a/src/config/tfa.rs > +++ b/src/config/tfa.rs > @@ -4,7 +4,6 @@ use std::io::{self, Read, Seek, SeekFrom}; > use std::os::unix::fs::OpenOptionsExt; > use std::os::unix::io::AsRawFd; > use std::path::PathBuf; > -use std::time::Duration; > > use anyhow::{bail, format_err, Error}; > use nix::sys::stat::Mode; > @@ -29,25 +28,25 @@ use proxmox::tools::AsHex; > use pbs_buildcfg::configdir; > > use crate::api2::types::Userid; > +use crate::backup::{open_backup_lockfile, BackupLockGuard}; > > /// Mapping of userid to TFA entry. > pub type TfaUsers = HashMap<Userid, TfaUserData>; > > const CONF_FILE: &str = configdir!("/tfa.json"); > const LOCK_FILE: &str = configdir!("/tfa.json.lock"); > -const LOCK_TIMEOUT: Duration = Duration::from_secs(5); > > const CHALLENGE_DATA_PATH: &str = pbs_buildcfg::rundir!("/tfa/challenges"); > > /// U2F registration challenges time out after 2 minutes. > const CHALLENGE_TIMEOUT: i64 = 2 * 60; > > -pub fn read_lock() -> Result<File, Error> { > - proxmox::tools::fs::open_file_locked(LOCK_FILE, LOCK_TIMEOUT, false) > +pub fn read_lock() -> Result<BackupLockGuard, Error> { > + open_backup_lockfile(LOCK_FILE, None, false) > } > > -pub fn write_lock() -> Result<File, Error> { > - proxmox::tools::fs::open_file_locked(LOCK_FILE, LOCK_TIMEOUT, true) > +pub fn write_lock() -> Result<BackupLockGuard, Error> { > + open_backup_lockfile(LOCK_FILE, None, true) > } > > /// Read the TFA entries. > diff --git a/src/config/token_shadow.rs b/src/config/token_shadow.rs > index 9f8bb2e0..a210ffb2 100644 > --- a/src/config/token_shadow.rs > +++ b/src/config/token_shadow.rs > @@ -1,18 +1,17 @@ > use std::collections::HashMap; > -use std::time::Duration; > > use anyhow::{bail, format_err, Error}; > use serde::{Serialize, Deserialize}; > use serde_json::{from_value, Value}; > > -use proxmox::tools::fs::{open_file_locked, CreateOptions}; > +use proxmox::tools::fs::CreateOptions; > > use crate::api2::types::Authid; > use crate::auth; > +use crate::backup::open_backup_lockfile; > > const LOCK_FILE: &str = pbs_buildcfg::configdir!("/token.shadow.lock"); > const CONF_FILE: &str = pbs_buildcfg::configdir!("/token.shadow"); > -const LOCK_TIMEOUT: Duration = Duration::from_secs(5); > > #[derive(Serialize, Deserialize)] > #[serde(rename_all="kebab-case")] > @@ -65,7 +64,7 @@ pub fn set_secret(tokenid: &Authid, secret: &str) -> Result<(), Error> { > bail!("not an API token ID"); > } > > - let _guard = open_file_locked(LOCK_FILE, LOCK_TIMEOUT, true)?; > + let _guard = open_backup_lockfile(LOCK_FILE, None, true)?; > > let mut data = read_file()?; > let hashed_secret = auth::encrypt_pw(secret)?; > @@ -81,7 +80,7 @@ pub fn delete_secret(tokenid: &Authid) -> Result<(), Error> { > bail!("not an API token ID"); > } > > - let _guard = open_file_locked(LOCK_FILE, LOCK_TIMEOUT, true)?; > + let _guard = open_backup_lockfile(LOCK_FILE, None, true)?; > > let mut data = read_file()?; > data.remove(tokenid); > diff --git a/src/server/jobstate.rs b/src/server/jobstate.rs > index 96dd21aa..4b15fabc 100644 > --- a/src/server/jobstate.rs > +++ b/src/server/jobstate.rs > @@ -37,18 +37,17 @@ > //! # } > //! > //! ``` > -use std::fs::File; > use std::path::{Path, PathBuf}; > -use std::time::Duration; > > use anyhow::{bail, format_err, Error}; > use proxmox::tools::fs::{ > - create_path, file_read_optional_string, open_file_locked, replace_file, CreateOptions, > + create_path, file_read_optional_string, replace_file, CreateOptions, > }; > use serde::{Deserialize, Serialize}; > > use crate::{ > - tools::systemd::time::{ > + backup::{open_backup_lockfile, BackupLockGuard}, > + tools::systemd::time::{ > parse_calendar_event, > compute_next_event, > }, > @@ -83,7 +82,7 @@ pub struct Job { > jobname: String, > /// The State of the job > pub state: JobState, > - _lock: File, > + _lock: BackupLockGuard, > } > > const JOB_STATE_BASEDIR: &str = "/var/lib/proxmox-backup/jobstates"; > @@ -107,16 +106,13 @@ fn get_path(jobtype: &str, jobname: &str) -> PathBuf { > path > } > > -fn get_lock<P>(path: P) -> Result<File, Error> > +fn get_lock<P>(path: P) -> Result<BackupLockGuard, Error> > where > P: AsRef<Path>, > { > let mut path = path.as_ref().to_path_buf(); > path.set_extension("lck"); > - let lock = open_file_locked(&path, Duration::new(10, 0), true)?; > - let backup_user = crate::backup::backup_user()?; > - nix::unistd::chown(&path, Some(backup_user.uid), Some(backup_user.gid))?; > - Ok(lock) > + open_backup_lockfile(&path, None, true) > } > > /// Removes the statefile of a job, this is useful if we delete a job > diff --git a/src/server/worker_task.rs b/src/server/worker_task.rs > index 13578446..8507842b 100644 > --- a/src/server/worker_task.rs > +++ b/src/server/worker_task.rs > @@ -14,7 +14,7 @@ use tokio::sync::oneshot; > > use proxmox::sys::linux::procfs; > use proxmox::try_block; > -use proxmox::tools::fs::{create_path, open_file_locked, replace_file, CreateOptions}; > +use proxmox::tools::fs::{create_path, replace_file, CreateOptions}; > > use super::{UPID, UPIDExt}; > > @@ -24,6 +24,7 @@ use crate::server; > use crate::tools::logrotate::{LogRotate, LogRotateFiles}; > use crate::tools::{FileLogger, FileLogOptions}; > use crate::api2::types::{Authid, TaskStateType}; > +use crate::backup::{open_backup_lockfile, BackupLockGuard}; > > macro_rules! taskdir { > ($subdir:expr) => (concat!(pbs_buildcfg::PROXMOX_BACKUP_LOG_DIR_M!(), "/tasks", $subdir)) > @@ -313,13 +314,8 @@ pub struct TaskListInfo { > pub state: Option<TaskState>, // endtime, status > } > > -fn lock_task_list_files(exclusive: bool) -> Result<std::fs::File, Error> { > - let backup_user = crate::backup::backup_user()?; > - > - let lock = open_file_locked(PROXMOX_BACKUP_TASK_LOCK_FN, std::time::Duration::new(10, 0), exclusive)?; > - nix::unistd::chown(PROXMOX_BACKUP_TASK_LOCK_FN, Some(backup_user.uid), Some(backup_user.gid))?; > - > - Ok(lock) > +fn lock_task_list_files(exclusive: bool) -> Result<BackupLockGuard, Error> { > + open_backup_lockfile(PROXMOX_BACKUP_TASK_LOCK_FN, None, exclusive) > } > > /// checks if the Task Archive is bigger that 'size_threshold' bytes, and > @@ -481,7 +477,7 @@ pub struct TaskListInfoIterator { > list: VecDeque<TaskListInfo>, > end: bool, > archive: Option<LogRotateFiles>, > - lock: Option<File>, > + lock: Option<BackupLockGuard>, > } > > impl TaskListInfoIterator { > diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs > index fb4b6f47..a6cbed84 100644 > --- a/src/tape/drive/mod.rs > +++ b/src/tape/drive/mod.rs > @@ -5,19 +5,21 @@ mod virtual_tape; > mod lto; > pub use lto::*; > > -use std::os::unix::io::AsRawFd; > use std::path::PathBuf; > > use anyhow::{bail, format_err, Error}; > use ::serde::{Deserialize}; > use serde_json::Value; > +use nix::fcntl::OFlag; > +use nix::sys::stat::Mode; > > use proxmox::{ > tools::{ > Uuid, > io::ReadExt, > fs::{ > - fchown, > + lock_file, > + atomic_open_or_create_file, > file_read_optional_string, > replace_file, > CreateOptions, > @@ -604,20 +606,34 @@ fn tape_device_path( > > pub struct DeviceLockGuard(std::fs::File); > > -// Acquires an exclusive lock on `device_path` > -// > // Uses systemd escape_unit to compute a file name from `device_path`, the try > // to lock `/var/lock/<name>`. > -fn lock_device_path(device_path: &str) -> Result<DeviceLockGuard, TapeLockError> { > - > +fn open_device_lock(device_path: &str) -> Result<std::fs::File, Error> { > let lock_name = crate::tools::systemd::escape_unit(device_path, true); > > let mut path = std::path::PathBuf::from(crate::tape::DRIVE_LOCK_DIR); > path.push(lock_name); > > + let user = crate::backup::backup_user()?; > + let options = CreateOptions::new() > + .perm(Mode::from_bits_truncate(0o660)) > + .owner(user.uid) > + .group(user.gid); > + > + atomic_open_or_create_file( > + path, > + OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_APPEND, > + &[], > + options, > + ) > +} > + > +// Acquires an exclusive lock on `device_path` > +// > +fn lock_device_path(device_path: &str) -> Result<DeviceLockGuard, TapeLockError> { > + let mut file = open_device_lock(device_path)?; > let timeout = std::time::Duration::new(10, 0); > - let mut file = std::fs::OpenOptions::new().create(true).append(true).open(path)?; > - if let Err(err) = proxmox::tools::fs::lock_file(&mut file, true, Some(timeout)) { > + if let Err(err) = lock_file(&mut file, true, Some(timeout)) { > if err.kind() == std::io::ErrorKind::Interrupted { > return Err(TapeLockError::TimeOut); > } else { > @@ -625,9 +641,6 @@ fn lock_device_path(device_path: &str) -> Result<DeviceLockGuard, TapeLockError> > } > } > > - let backup_user = crate::backup::backup_user()?; > - fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; > - > Ok(DeviceLockGuard(file)) > } > > @@ -635,14 +648,10 @@ fn lock_device_path(device_path: &str) -> Result<DeviceLockGuard, TapeLockError> > // non-blocking, and returning if the file is locked or not > fn test_device_path_lock(device_path: &str) -> Result<bool, Error> { > > - let lock_name = crate::tools::systemd::escape_unit(device_path, true); > - > - let mut path = std::path::PathBuf::from(crate::tape::DRIVE_LOCK_DIR); > - path.push(lock_name); > + let mut file = open_device_lock(device_path)?; > > let timeout = std::time::Duration::new(0, 0); > - let mut file = std::fs::OpenOptions::new().create(true).append(true).open(path)?; > - match proxmox::tools::fs::lock_file(&mut file, true, Some(timeout)) { > + match lock_file(&mut file, true, Some(timeout)) { > // file was not locked, continue > Ok(()) => {}, > // file was locked, return true > @@ -650,8 +659,5 @@ fn test_device_path_lock(device_path: &str) -> Result<bool, Error> { > Err(err) => bail!("{}", err), > } > > - let backup_user = crate::backup::backup_user()?; > - fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; > - > Ok(false) > } > diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs > index 3c5f9502..f48afa79 100644 > --- a/src/tape/drive/virtual_tape.rs > +++ b/src/tape/drive/virtual_tape.rs > @@ -49,8 +49,9 @@ impl VirtualTapeDrive { > let mut lock_path = std::path::PathBuf::from(&self.path); > lock_path.push(".drive.lck"); > > + let options = CreateOptions::new(); > let timeout = std::time::Duration::new(10, 0); > - let lock = proxmox::tools::fs::open_file_locked(&lock_path, timeout, true)?; > + let lock = proxmox::tools::fs::open_file_locked(&lock_path, timeout, true, options)?; > > Ok(VirtualTapeHandle { > _lock: lock, > diff --git a/src/tape/inventory.rs b/src/tape/inventory.rs > index 4bb6d4f8..e97f07f3 100644 > --- a/src/tape/inventory.rs > +++ b/src/tape/inventory.rs > @@ -24,8 +24,6 @@ > > use std::collections::{HashMap, BTreeMap}; > use std::path::{Path, PathBuf}; > -use std::os::unix::io::AsRawFd; > -use std::fs::File; > use std::time::Duration; > > use anyhow::{bail, Error}; > @@ -35,9 +33,7 @@ use serde_json::json; > use proxmox::tools::{ > Uuid, > fs::{ > - open_file_locked, > replace_file, > - fchown, > file_get_json, > CreateOptions, > }, > @@ -51,6 +47,7 @@ use crate::{ > MediaStatus, > MediaLocation, > }, > + backup::{open_backup_lockfile, BackupLockGuard}, > tape::{ > TAPE_STATUS_DIR, > MediaSet, > @@ -149,17 +146,8 @@ impl Inventory { > } > > /// Lock the database > - fn lock(&self) -> Result<std::fs::File, Error> { > - let file = open_file_locked(&self.lockfile_path, std::time::Duration::new(10, 0), true)?; > - if cfg!(test) { > - // We cannot use chown inside test environment (no permissions) > - return Ok(file); > - } > - > - let backup_user = crate::backup::backup_user()?; > - fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; > - > - Ok(file) > + fn lock(&self) -> Result<BackupLockGuard, Error> { > + open_backup_lockfile(&self.lockfile_path, None, true) > } > > fn load_media_db(path: &Path) -> Result<BTreeMap<Uuid, MediaStateEntry>, Error> { > @@ -756,27 +744,16 @@ impl Inventory { > } > > /// Lock a media pool > -pub fn lock_media_pool(base_path: &Path, name: &str) -> Result<File, Error> { > +pub fn lock_media_pool(base_path: &Path, name: &str) -> Result<BackupLockGuard, Error> { > let mut path = base_path.to_owned(); > path.push(format!(".pool-{}", name)); > path.set_extension("lck"); > > - let timeout = std::time::Duration::new(10, 0); > - let lock = proxmox::tools::fs::open_file_locked(&path, timeout, true)?; > - > - if cfg!(test) { > - // We cannot use chown inside test environment (no permissions) > - return Ok(lock); > - } > - > - let backup_user = crate::backup::backup_user()?; > - fchown(lock.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; > - > - Ok(lock) > + open_backup_lockfile(&path, None, true) > } > > /// Lock for media not assigned to any pool > -pub fn lock_unassigned_media_pool(base_path: &Path) -> Result<File, Error> { > +pub fn lock_unassigned_media_pool(base_path: &Path) -> Result<BackupLockGuard, Error> { > // lock artificial "__UNASSIGNED__" pool to avoid races > lock_media_pool(base_path, "__UNASSIGNED__") > } > @@ -788,22 +765,12 @@ pub fn lock_media_set( > base_path: &Path, > media_set_uuid: &Uuid, > timeout: Option<Duration>, > -) -> Result<File, Error> { > +) -> Result<BackupLockGuard, Error> { > let mut path = base_path.to_owned(); > path.push(format!(".media-set-{}", media_set_uuid)); > path.set_extension("lck"); > > - let timeout = timeout.unwrap_or(Duration::new(10, 0)); > - let file = open_file_locked(&path, timeout, true)?; > - if cfg!(test) { > - // We cannot use chown inside test environment (no permissions) > - return Ok(file); > - } > - > - let backup_user = crate::backup::backup_user()?; > - fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; > - > - Ok(file) > + open_backup_lockfile(&path, timeout, true) > } > > // shell completion helper > diff --git a/src/tape/media_pool.rs b/src/tape/media_pool.rs > index dd471551..7bb7fbdb 100644 > --- a/src/tape/media_pool.rs > +++ b/src/tape/media_pool.rs > @@ -8,7 +8,6 @@ > //! > > use std::path::{PathBuf, Path}; > -use std::fs::File; > > use anyhow::{bail, Error}; > use ::serde::{Deserialize, Serialize}; > @@ -16,7 +15,7 @@ use ::serde::{Deserialize, Serialize}; > use proxmox::tools::Uuid; > > use crate::{ > - backup::Fingerprint, > + backup::{Fingerprint, BackupLockGuard}, > api2::types::{ > MediaStatus, > MediaLocation, > @@ -61,7 +60,7 @@ pub struct MediaPool { > inventory: Inventory, > > current_media_set: MediaSet, > - current_media_set_lock: Option<File>, > + current_media_set_lock: Option<BackupLockGuard>, > } > > impl MediaPool { > diff --git a/src/tools/memcom.rs b/src/tools/memcom.rs > index 9921f5c9..87b73561 100644 > --- a/src/tools/memcom.rs > +++ b/src/tools/memcom.rs > @@ -1,21 +1,17 @@ > //! Memory based communication channel between proxy & daemon for things such as cache > //! invalidation. > > -use std::ffi::CString; > -use std::io; > use std::os::unix::io::AsRawFd; > use std::sync::atomic::{AtomicUsize, Ordering}; > use std::sync::Arc; > > -use anyhow::{bail, format_err, Error}; > -use nix::errno::Errno; > +use anyhow::Error; > use nix::fcntl::OFlag; > use nix::sys::mman::{MapFlags, ProtFlags}; > use nix::sys::stat::Mode; > use once_cell::sync::OnceCell; > > -use proxmox::sys::error::SysError; > -use proxmox::tools::fd::Fd; > +use proxmox::tools::fs::CreateOptions; > use proxmox::tools::mmap::Mmap; > > /// In-memory communication channel. > @@ -32,6 +28,7 @@ struct Head { > static INSTANCE: OnceCell<Arc<Memcom>> = OnceCell::new(); > > const MEMCOM_FILE_PATH: &str = pbs_buildcfg::rundir!("/proxmox-backup-memcom"); > +const EMPTY_PAGE: [u8; 4096] = [0u8; 4096]; > > impl Memcom { > /// Open the memory based communication channel singleton. > @@ -41,15 +38,20 @@ impl Memcom { > > // Actual work of `new`: > fn open() -> Result<Arc<Self>, Error> { > - let fd = match open_existing() { > - Ok(fd) => fd, > - Err(err) if err.not_found() => create_new()?, > - Err(err) => bail!("failed to open {} - {}", MEMCOM_FILE_PATH, err), > - }; > + let user = crate::backup::backup_user()?; > + let options = CreateOptions::new() > + .perm(Mode::from_bits_truncate(0o660)) > + .owner(user.uid) > + .group(user.gid); > + > + let file = proxmox::tools::fs::atomic_open_or_create_file( > + MEMCOM_FILE_PATH, > + OFlag::O_RDWR | OFlag::O_CLOEXEC, > + &EMPTY_PAGE, options)?; > > let mmap = unsafe { > Mmap::<u8>::map_fd( > - fd.as_raw_fd(), > + file.as_raw_fd(), > 0, > 4096, > ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, > @@ -77,87 +79,3 @@ impl Memcom { > .fetch_add(1, Ordering::AcqRel); > } > } > - > -/// The fast path opens an existing file. > -fn open_existing() -> Result<Fd, nix::Error> { > - Fd::open( > - MEMCOM_FILE_PATH, > - OFlag::O_RDWR | OFlag::O_CLOEXEC, > - Mode::empty(), > - ) > -} > - > -/// Since we need to initialize the file, we also need a solid slow path where we create the file. > -/// In order to make sure the next user's `open()` vs `mmap()` race against our `truncate()` call, > -/// we create it in a temporary location and rotate it in place. > -fn create_new() -> Result<Fd, Error> { > - // create a temporary file: > - let temp_file_name = format!("{}.{}", MEMCOM_FILE_PATH, unsafe { libc::getpid() }); > - let fd = Fd::open( > - temp_file_name.as_str(), > - OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_RDWR | OFlag::O_CLOEXEC, > - Mode::from_bits_truncate(0o660), > - ) > - .map_err(|err| { > - format_err!( > - "failed to create new in-memory communication file at {} - {}", > - temp_file_name, > - err > - ) > - })?; > - > - // let it be a page in size, it'll be initialized to zero by the kernel > - nix::unistd::ftruncate(fd.as_raw_fd(), 4096) > - .map_err(|err| format_err!("failed to set size of {} - {}", temp_file_name, err))?; > - > - // if this is the pbs-daemon (running as root) rather than the proxy (running as backup user), > - // make sure the backup user can access the file: > - if let Ok(backup_user) = crate::backup::backup_user() { > - match nix::unistd::fchown(fd.as_raw_fd(), None, Some(backup_user.gid)) { > - Ok(()) => (), > - Err(err) if err.is_errno(Errno::EPERM) => { > - // we're not the daemon (root), so the file is already owned by the backup user > - } > - Err(err) => bail!( > - "failed to set group to 'backup' for {} - {}", > - temp_file_name, > - err > - ), > - } > - } > - > - // rotate the file into place, but use `RENAME_NOREPLACE`, so in case 2 processes race against > - // the initialization, the first one wins! > - // TODO: nicer `renameat2()` wrapper in `proxmox::sys`? > - let c_file_name = CString::new(temp_file_name.as_bytes()).unwrap(); > - let new_path = CString::new(MEMCOM_FILE_PATH).unwrap(); > - let rc = unsafe { > - libc::renameat2( > - -1, > - c_file_name.as_ptr(), > - -1, > - new_path.as_ptr(), > - libc::RENAME_NOREPLACE, > - ) > - }; > - if rc == 0 { > - return Ok(fd); > - } > - let err = io::Error::last_os_error(); > - > - // if another process has already raced ahead and created the file, let's just open theirs > - // instead: > - if err.kind() == io::ErrorKind::AlreadyExists { > - // someone beat us to it: > - drop(fd); > - return open_existing().map_err(Error::from); > - } > - > - // for any other errors, just bail out > - bail!( > - "failed to move file at {} into place at {} - {}", > - temp_file_name, > - MEMCOM_FILE_PATH, > - err > - ); > -} > -- > 2.30.2 > > > _______________________________________________ > pbs-devel mailing list > pbs-devel@lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel > > > ^ permalink raw reply [flat|nested] 8+ messages in thread
* [pbs-devel] [PATCH proxmox-backup 2/2] add helpers to write configuration files 2021-07-16 8:28 [pbs-devel] [PATCH proxmox 1/2] new helper atomic_open_or_create_file() Dietmar Maurer 2021-07-16 8:28 ` [pbs-devel] [PATCH proxmox-backup 1/2] use new atomic_open_or_create_file Dietmar Maurer @ 2021-07-16 8:28 ` Dietmar Maurer 2021-07-16 8:28 ` [pbs-devel] [PATCH proxmox 2/2] open_file_locked: add options parameter (CreateOptions) Dietmar Maurer [not found] ` <<20210716082834.2354163-1-dietmar@proxmox.com> 3 siblings, 0 replies; 8+ messages in thread From: Dietmar Maurer @ 2021-07-16 8:28 UTC (permalink / raw) To: pbs-devel --- src/backup/mod.rs | 41 ++++++++++++++++++++++++++++++ src/config/acl.rs | 14 +--------- src/config/acme/plugin.rs | 16 +----------- src/config/datastore.rs | 19 +------------- src/config/domains.rs | 19 +------------- src/config/drive.rs | 18 +------------ src/config/media_pool.rs | 19 +------------- src/config/mod.rs | 10 ++------ src/config/node.rs | 10 +------- src/config/remote.rs | 16 +----------- src/config/sync.rs | 16 +----------- src/config/tape_encryption_keys.rs | 33 +++--------------------- src/config/tape_job.rs | 16 +----------- src/config/user.rs | 14 +--------- src/config/verify.rs | 17 +------------ 15 files changed, 58 insertions(+), 220 deletions(-) diff --git a/src/backup/mod.rs b/src/backup/mod.rs index 1db61cf5..3337f3a6 100644 --- a/src/backup/mod.rs +++ b/src/backup/mod.rs @@ -134,3 +134,44 @@ pub fn open_backup_lockfile<P: AsRef<std::path::Path>>( let file = proxmox::tools::fs::open_file_locked(&path, timeout, exclusive, options)?; Ok(BackupLockGuard(file)) } + +/// 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> { + let backup_user = backup_user()?; + let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); + // set the correct owner/group/permissions while saving file + // owner(rw) = root, group(r)= backup + let options = proxmox::tools::fs::CreateOptions::new() + .perm(mode) + .owner(nix::unistd::ROOT) + .group(backup_user.gid); + + proxmox::tools::fs::replace_file(path, data, options)?; + + Ok(()) +} + +/// 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> { + 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 + let options = proxmox::tools::fs::CreateOptions::new() + .perm(mode) + .owner(nix::unistd::ROOT) + .group(nix::unistd::Gid::from_raw(0)); + + proxmox::tools::fs::replace_file(path, data, options)?; + + Ok(()) +} diff --git a/src/config/acl.rs b/src/config/acl.rs index b4b3510f..b7badb79 100644 --- a/src/config/acl.rs +++ b/src/config/acl.rs @@ -13,7 +13,6 @@ use serde::de::{value, IntoDeserializer}; use proxmox::api::{api, schema::*}; use proxmox::constnamedbitmap; -use proxmox::tools::{fs::replace_file, fs::CreateOptions}; use crate::api2::types::{Authid, Userid}; @@ -912,18 +911,7 @@ pub fn save_config(acl: &AclTree) -> Result<(), Error> { acl.write_config(&mut raw)?; - let backup_user = crate::backup::backup_user()?; - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= backup - let options = CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(backup_user.gid); - - replace_file(ACL_CFG_FILENAME, &raw, options)?; - - Ok(()) + crate::backup::replace_backup_config(ACL_CFG_FILENAME, &raw) } #[cfg(test)] diff --git a/src/config/acme/plugin.rs b/src/config/acme/plugin.rs index fde800e2..a4322fdd 100644 --- a/src/config/acme/plugin.rs +++ b/src/config/acme/plugin.rs @@ -9,8 +9,6 @@ use proxmox::api::{ section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}, }; -use proxmox::tools::{fs::replace_file, fs::CreateOptions}; - use crate::api2::types::PROXMOX_SAFE_ID_FORMAT; use crate::backup::{open_backup_lockfile, BackupLockGuard}; @@ -168,19 +166,7 @@ pub fn config() -> Result<(PluginData, [u8; 32]), Error> { pub fn save_config(config: &PluginData) -> Result<(), Error> { super::make_acme_dir()?; let raw = CONFIG.write(ACME_PLUGIN_CFG_FILENAME, &config.data)?; - - let backup_user = crate::backup::backup_user()?; - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= backup - let options = CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(backup_user.gid); - - replace_file(ACME_PLUGIN_CFG_FILENAME, raw.as_bytes(), options)?; - - Ok(()) + crate::backup::replace_backup_config(ACME_PLUGIN_CFG_FILENAME, raw.as_bytes()) } pub struct PluginData { diff --git a/src/config/datastore.rs b/src/config/datastore.rs index 9e37073d..46d28feb 100644 --- a/src/config/datastore.rs +++ b/src/config/datastore.rs @@ -13,11 +13,6 @@ use proxmox::api::{ } }; -use proxmox::tools::fs::{ - replace_file, - CreateOptions, -}; - use crate::api2::types::*; use crate::backup::{open_backup_lockfile, BackupLockGuard}; @@ -154,19 +149,7 @@ pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { let raw = CONFIG.write(DATASTORE_CFG_FILENAME, &config)?; - - let backup_user = crate::backup::backup_user()?; - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= backup - let options = CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(backup_user.gid); - - replace_file(DATASTORE_CFG_FILENAME, raw.as_bytes(), options)?; - - Ok(()) + crate::backup::replace_backup_config(DATASTORE_CFG_FILENAME, raw.as_bytes()) } // shell completion helper diff --git a/src/config/domains.rs b/src/config/domains.rs index 9f513a44..0d695777 100644 --- a/src/config/domains.rs +++ b/src/config/domains.rs @@ -13,11 +13,6 @@ use proxmox::api::{ } }; -use proxmox::tools::fs::{ - replace_file, - CreateOptions, -}; - use crate::api2::types::*; use crate::backup::{open_backup_lockfile, BackupLockGuard}; @@ -126,19 +121,7 @@ pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { let raw = CONFIG.write(DOMAINS_CFG_FILENAME, &config)?; - - let backup_user = crate::backup::backup_user()?; - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= backup - let options = CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(backup_user.gid); - - replace_file(DOMAINS_CFG_FILENAME, raw.as_bytes(), options)?; - - Ok(()) + crate::backup::replace_backup_config(DOMAINS_CFG_FILENAME, raw.as_bytes()) } // shell completion helper diff --git a/src/config/drive.rs b/src/config/drive.rs index 9c20051f..f86582ac 100644 --- a/src/config/drive.rs +++ b/src/config/drive.rs @@ -25,10 +25,6 @@ use proxmox::{ SectionConfigPlugin, }, }, - tools::fs::{ - replace_file, - CreateOptions, - }, }; use crate::{ @@ -97,19 +93,7 @@ pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { /// Save the configuration file pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { let raw = CONFIG.write(DRIVE_CFG_FILENAME, &config)?; - - let backup_user = crate::backup::backup_user()?; - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= backup - let options = CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(backup_user.gid); - - replace_file(DRIVE_CFG_FILENAME, raw.as_bytes(), options)?; - - Ok(()) + crate::backup::replace_backup_config(DRIVE_CFG_FILENAME, raw.as_bytes()) } /// Check if the specified drive name exists in the config. diff --git a/src/config/media_pool.rs b/src/config/media_pool.rs index e50992d8..d9828e0f 100644 --- a/src/config/media_pool.rs +++ b/src/config/media_pool.rs @@ -20,10 +20,6 @@ use proxmox::{ SectionConfigPlugin, } }, - tools::fs::{ - replace_file, - CreateOptions, - }, }; use crate::{ @@ -57,7 +53,6 @@ pub const MEDIA_POOL_CFG_FILENAME: &str = "/etc/proxmox-backup/media-pool.cfg"; /// Lock file name (used to prevent concurrent access) pub const MEDIA_POOL_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.media-pool.lck"; - /// Get exclusive lock pub fn lock() -> Result<BackupLockGuard, Error> { open_backup_lockfile(MEDIA_POOL_CFG_LOCKFILE, None, true) @@ -77,19 +72,7 @@ pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { /// Save the configuration file pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { let raw = CONFIG.write(MEDIA_POOL_CFG_FILENAME, &config)?; - - let backup_user = crate::backup::backup_user()?; - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= backup - let options = CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(backup_user.gid); - - replace_file(MEDIA_POOL_CFG_FILENAME, raw.as_bytes(), options)?; - - Ok(()) + crate::backup::replace_backup_config(MEDIA_POOL_CFG_FILENAME, raw.as_bytes()) } // shell completion helper diff --git a/src/config/mod.rs b/src/config/mod.rs index 014d184f..d820ee37 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -10,7 +10,6 @@ use openssl::rsa::{Rsa}; use openssl::x509::{X509Builder}; use openssl::pkey::PKey; -use proxmox::tools::fs::{CreateOptions, replace_file}; use proxmox::try_block; use pbs_buildcfg::{self, configdir}; @@ -194,18 +193,13 @@ pub fn update_self_signed_cert(force: bool) -> Result<(), Error> { } pub(crate) fn set_proxy_certificate(cert_pem: &[u8], key_pem: &[u8]) -> Result<(), Error> { - let backup_user = crate::backup::backup_user()?; - let options = CreateOptions::new() - .perm(Mode::from_bits_truncate(0o0640)) - .owner(nix::unistd::ROOT) - .group(backup_user.gid); let key_path = PathBuf::from(configdir!("/proxy.key")); let cert_path = PathBuf::from(configdir!("/proxy.pem")); create_configdir()?; - replace_file(&key_path, &key_pem, options.clone()) + crate::backup::replace_backup_config(&key_path, key_pem) .map_err(|err| format_err!("error writing certificate private key - {}", err))?; - replace_file(&cert_path, &cert_pem, options) + crate::backup::replace_backup_config(&cert_path, &cert_pem) .map_err(|err| format_err!("error writing certificate file - {}", err))?; Ok(()) diff --git a/src/config/node.rs b/src/config/node.rs index dc3eeeb0..0c1c9938 100644 --- a/src/config/node.rs +++ b/src/config/node.rs @@ -6,7 +6,6 @@ use serde::{Deserialize, Serialize}; use proxmox::api::api; use proxmox::api::schema::{ApiStringFormat, Updater}; -use proxmox::tools::fs::{replace_file, CreateOptions}; use proxmox_http::ProxyConfig; @@ -41,14 +40,7 @@ pub fn save_config(config: &NodeConfig) -> Result<(), Error> { config.validate()?; let raw = crate::tools::config::to_bytes(config, &NodeConfig::API_SCHEMA)?; - - let backup_user = crate::backup::backup_user()?; - let options = CreateOptions::new() - .perm(Mode::from_bits_truncate(0o0640)) - .owner(nix::unistd::ROOT) - .group(backup_user.gid); - - replace_file(CONF_FILE, &raw, options) + crate::backup::replace_backup_config(CONF_FILE, &raw) } #[api( diff --git a/src/config/remote.rs b/src/config/remote.rs index 0ef70677..86fe7b6e 100644 --- a/src/config/remote.rs +++ b/src/config/remote.rs @@ -13,8 +13,6 @@ use proxmox::api::{ } }; -use proxmox::tools::{fs::replace_file, fs::CreateOptions}; - use crate::api2::types::*; lazy_static! { @@ -102,19 +100,7 @@ pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { let raw = CONFIG.write(REMOTE_CFG_FILENAME, &config)?; - - let backup_user = crate::backup::backup_user()?; - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= backup - let options = CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(backup_user.gid); - - replace_file(REMOTE_CFG_FILENAME, raw.as_bytes(), options)?; - - Ok(()) + crate::backup::replace_backup_config(REMOTE_CFG_FILENAME, raw.as_bytes()) } // shell completion helper diff --git a/src/config/sync.rs b/src/config/sync.rs index 2fd3a2c1..5d5b2060 100644 --- a/src/config/sync.rs +++ b/src/config/sync.rs @@ -13,8 +13,6 @@ use proxmox::api::{ } }; -use proxmox::tools::{fs::replace_file, fs::CreateOptions}; - use crate::api2::types::*; lazy_static! { @@ -120,19 +118,7 @@ pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { let raw = CONFIG.write(SYNC_CFG_FILENAME, &config)?; - - let backup_user = crate::backup::backup_user()?; - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= backup - let options = CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(backup_user.gid); - - replace_file(SYNC_CFG_FILENAME, raw.as_bytes(), options)?; - - Ok(()) + crate::backup::replace_backup_config(SYNC_CFG_FILENAME, raw.as_bytes()) } // shell completion helper diff --git a/src/config/tape_encryption_keys.rs b/src/config/tape_encryption_keys.rs index 5ee0ac1f..6d4e91b9 100644 --- a/src/config/tape_encryption_keys.rs +++ b/src/config/tape_encryption_keys.rs @@ -15,11 +15,7 @@ use std::collections::HashMap; use anyhow::{bail, Error}; use serde::{Deserialize, Serialize}; -use proxmox::tools::fs::{ - file_read_optional_string, - replace_file, - CreateOptions, -}; +use proxmox::tools::fs::file_read_optional_string; use crate::{ backup::{ @@ -143,18 +139,7 @@ pub fn save_keys(map: HashMap<Fingerprint, EncryptionKeyInfo>) -> Result<(), Err } let raw = serde_json::to_string_pretty(&list)?; - - 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 - let options = CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(nix::unistd::Gid::from_raw(0)); - - replace_file(TAPE_KEYS_FILENAME, raw.as_bytes(), options)?; - - Ok(()) + crate::backup::replace_secret_config(TAPE_KEYS_FILENAME, raw.as_bytes()) } /// Store tape encryption key configurations (password protected keys) @@ -167,19 +152,7 @@ pub fn save_key_configs(map: HashMap<Fingerprint, KeyConfig>) -> Result<(), Erro } let raw = serde_json::to_string_pretty(&list)?; - - let backup_user = crate::backup::backup_user()?; - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= backup - let options = CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(backup_user.gid); - - replace_file(TAPE_KEY_CONFIG_FILENAME, raw.as_bytes(), options)?; - - Ok(()) + crate::backup::replace_backup_config(TAPE_KEY_CONFIG_FILENAME, raw.as_bytes()) } /// Insert a new key diff --git a/src/config/tape_job.rs b/src/config/tape_job.rs index a5901e86..f09200fc 100644 --- a/src/config/tape_job.rs +++ b/src/config/tape_job.rs @@ -13,8 +13,6 @@ use proxmox::api::{ } }; -use proxmox::tools::{fs::replace_file, fs::CreateOptions}; - use crate::api2::types::{ Userid, JOB_ID_SCHEMA, @@ -159,19 +157,7 @@ pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { let raw = CONFIG.write(TAPE_JOB_CFG_FILENAME, &config)?; - - let backup_user = crate::backup::backup_user()?; - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= backup - let options = CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(backup_user.gid); - - replace_file(TAPE_JOB_CFG_FILENAME, raw.as_bytes(), options)?; - - Ok(()) + crate::backup::replace_backup_config(TAPE_JOB_CFG_FILENAME, raw.as_bytes()) } // shell completion helper diff --git a/src/config/user.rs b/src/config/user.rs index bdec5fc1..8d367ade 100644 --- a/src/config/user.rs +++ b/src/config/user.rs @@ -15,8 +15,6 @@ use proxmox::api::{ } }; -use proxmox::tools::{fs::replace_file, fs::CreateOptions}; - use crate::api2::types::*; use crate::tools::Memcom; @@ -259,17 +257,7 @@ pub fn cached_config() -> Result<Arc<SectionConfigData>, Error> { pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { let raw = CONFIG.write(USER_CFG_FILENAME, &config)?; - - let backup_user = crate::backup::backup_user()?; - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= backup - let options = CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(backup_user.gid); - - replace_file(USER_CFG_FILENAME, raw.as_bytes(), options)?; + crate::backup::replace_backup_config(USER_CFG_FILENAME, raw.as_bytes())?; // increase user cache generation // We use this in CachedUserInfo diff --git a/src/config/verify.rs b/src/config/verify.rs index 549f9801..9001fffc 100644 --- a/src/config/verify.rs +++ b/src/config/verify.rs @@ -13,8 +13,6 @@ use proxmox::api::{ } }; -use proxmox::tools::{fs::replace_file, fs::CreateOptions}; - use crate::api2::types::*; lazy_static! { @@ -118,20 +116,7 @@ pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { let raw = CONFIG.write(VERIFICATION_CFG_FILENAME, &config)?; - - let backup_user = crate::backup::backup_user()?; - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= backup - - let options = CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(backup_user.gid); - - replace_file(VERIFICATION_CFG_FILENAME, raw.as_bytes(), options)?; - - Ok(()) + crate::backup::replace_backup_config(VERIFICATION_CFG_FILENAME, raw.as_bytes()) } // shell completion helper -- 2.30.2 ^ permalink raw reply [flat|nested] 8+ messages in thread
* [pbs-devel] [PATCH proxmox 2/2] open_file_locked: add options parameter (CreateOptions) 2021-07-16 8:28 [pbs-devel] [PATCH proxmox 1/2] new helper atomic_open_or_create_file() Dietmar Maurer 2021-07-16 8:28 ` [pbs-devel] [PATCH proxmox-backup 1/2] use new atomic_open_or_create_file Dietmar Maurer 2021-07-16 8:28 ` [pbs-devel] [PATCH proxmox-backup 2/2] add helpers to write configuration files Dietmar Maurer @ 2021-07-16 8:28 ` Dietmar Maurer [not found] ` <<20210716082834.2354163-4-dietmar@proxmox.com> [not found] ` <<20210716082834.2354163-1-dietmar@proxmox.com> 3 siblings, 1 reply; 8+ messages in thread From: Dietmar Maurer @ 2021-07-16 8:28 UTC (permalink / raw) To: pbs-devel To be able to set file permissions and ownership. --- proxmox/src/tools/fs.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/proxmox/src/tools/fs.rs b/proxmox/src/tools/fs.rs index 2a93b30..5c269a6 100644 --- a/proxmox/src/tools/fs.rs +++ b/proxmox/src/tools/fs.rs @@ -1,7 +1,7 @@ //! File related utilities such as `replace_file`. use std::ffi::CStr; -use std::fs::{File, OpenOptions}; +use std::fs::File; use std::io::{self, BufRead, BufReader, Write}; use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; use std::path::{Path, PathBuf}; @@ -584,12 +584,17 @@ pub fn open_file_locked<P: AsRef<Path>>( path: P, timeout: Duration, exclusive: bool, + options: CreateOptions, ) -> Result<File, Error> { let path = path.as_ref(); - let mut file = match OpenOptions::new().create(true).append(true).open(path) { - Ok(file) => file, - Err(err) => bail!("Unable to open lock {:?} - {}", path, err), - }; + + let mut file = atomic_open_or_create_file( + path, + OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_APPEND, + &[], + options, + )?; + match lock_file(&mut file, exclusive, Some(timeout)) { Ok(_) => Ok(file), Err(err) => bail!("Unable to acquire lock {:?} - {}", path, err), -- 2.30.2 ^ permalink raw reply [flat|nested] 8+ messages in thread
[parent not found: <<20210716082834.2354163-4-dietmar@proxmox.com>]
* Re: [pbs-devel] [PATCH proxmox 2/2] open_file_locked: add options parameter (CreateOptions) [not found] ` <<20210716082834.2354163-4-dietmar@proxmox.com> @ 2021-07-19 10:44 ` Fabian Grünbichler 0 siblings, 0 replies; 8+ messages in thread From: Fabian Grünbichler @ 2021-07-19 10:44 UTC (permalink / raw) To: Proxmox Backup Server development discussion breaking change, should be marked as such both in the patch as well as with a version bump (or alternatively, be implemented as a new function) requires a patch in proxmox-openid-rs to build also see comments on patch #1 in proxmox-backup! On July 16, 2021 10:28 am, Dietmar Maurer wrote: > To be able to set file permissions and ownership. > --- > proxmox/src/tools/fs.rs | 15 ++++++++++----- > 1 file changed, 10 insertions(+), 5 deletions(-) > > diff --git a/proxmox/src/tools/fs.rs b/proxmox/src/tools/fs.rs > index 2a93b30..5c269a6 100644 > --- a/proxmox/src/tools/fs.rs > +++ b/proxmox/src/tools/fs.rs > @@ -1,7 +1,7 @@ > //! File related utilities such as `replace_file`. > > use std::ffi::CStr; > -use std::fs::{File, OpenOptions}; > +use std::fs::File; > use std::io::{self, BufRead, BufReader, Write}; > use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; > use std::path::{Path, PathBuf}; > @@ -584,12 +584,17 @@ pub fn open_file_locked<P: AsRef<Path>>( > path: P, > timeout: Duration, > exclusive: bool, > + options: CreateOptions, > ) -> Result<File, Error> { > let path = path.as_ref(); > - let mut file = match OpenOptions::new().create(true).append(true).open(path) { > - Ok(file) => file, > - Err(err) => bail!("Unable to open lock {:?} - {}", path, err), > - }; > + > + let mut file = atomic_open_or_create_file( > + path, > + OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_APPEND, > + &[], > + options, > + )?; > + > match lock_file(&mut file, exclusive, Some(timeout)) { > Ok(_) => Ok(file), > Err(err) => bail!("Unable to acquire lock {:?} - {}", path, err), > -- > 2.30.2 > > > _______________________________________________ > pbs-devel mailing list > pbs-devel@lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel > > > ^ permalink raw reply [flat|nested] 8+ messages in thread
[parent not found: <<20210716082834.2354163-1-dietmar@proxmox.com>]
* Re: [pbs-devel] [PATCH proxmox 1/2] new helper atomic_open_or_create_file() [not found] ` <<20210716082834.2354163-1-dietmar@proxmox.com> @ 2021-07-19 10:44 ` Fabian Grünbichler 0 siblings, 0 replies; 8+ messages in thread From: Fabian Grünbichler @ 2021-07-19 10:44 UTC (permalink / raw) To: Proxmox Backup Server development discussion one small nit, otherwise this looks okay (and DOES set the permissions). On July 16, 2021 10:28 am, Dietmar Maurer wrote: > --- > proxmox/src/tools/fs.rs | 86 ++++++++++++++++++++++++++++++++++++++++- > 1 file changed, 85 insertions(+), 1 deletion(-) > > diff --git a/proxmox/src/tools/fs.rs b/proxmox/src/tools/fs.rs > index 12e96bd..2a93b30 100644 > --- a/proxmox/src/tools/fs.rs > +++ b/proxmox/src/tools/fs.rs > @@ -12,9 +12,10 @@ use nix::errno::Errno; > use nix::fcntl::OFlag; > use nix::sys::stat; > use nix::unistd::{self, Gid, Uid}; > +use nix::NixPath; > use serde_json::Value; > > -use crate::sys::error::SysResult; > +use crate::sys::error::{SysError, SysResult}; > use crate::sys::timer; > use crate::tools::fd::Fd; > use crate::try_block; > @@ -187,6 +188,89 @@ pub fn replace_file<P: AsRef<Path>>( > Ok(()) > } > > +/// Like open(2), but allows setting initial data, perm, owner and group > +/// > +/// Since we need to initialize the file, we also need a solid slow > +/// path where we create the file. In order to avoid races, we create > +/// it in a temporary location and rotate it in place. > +pub fn atomic_open_or_create_file<P: AsRef<Path>>( > + path: P, > + mut oflag: OFlag, > + initial_data: &[u8], > + options: CreateOptions, > +) -> Result<File, Error> { > + let path = path.as_ref(); > + > + if oflag.contains(OFlag::O_TMPFILE) { > + bail!("open {:?} failed - unsupported OFlag O_TMPFILE", path); > + } > + > + oflag.remove(OFlag::O_CREAT); // we want to handle CREAT ourselfes > + > + // Note: 'mode' is ignored, because oflag does not contain O_CREAT or O_TMPFILE > + match nix::fcntl::open(path, oflag, stat::Mode::empty()) { > + Ok(fd) => return Ok(unsafe { File::from_raw_fd(fd) }), > + Err(err) => { > + if err.not_found() { > + // fall thrue - try to create the file > + } else { > + bail!("open {:?} failed - {}", path, err); > + } > + } > + } > + > + let (mut file, temp_file_name) = make_tmp_file(path, options)?; so after this point we have a temp file that requires cleanup > + > + if !initial_data.is_empty() { > + file.write_all(initial_data).map_err(|err| { > + let _ = nix::unistd::unlink(&temp_file_name); > + format_err!( > + "writing initial data to {:?} failed - {}", > + temp_file_name, > + err, > + ) > + })?; > + } > + > + // rotate the file into place, but use `RENAME_NOREPLACE`, so in case 2 processes race against > + // the initialization, the first one wins! > + let rename_result = temp_file_name.with_nix_path(|c_file_name| { > + path.with_nix_path(|new_path| unsafe { > + let rc = libc::renameat2( > + libc::AT_FDCWD, > + c_file_name.as_ptr(), > + libc::AT_FDCWD, > + new_path.as_ptr(), > + libc::RENAME_NOREPLACE, > + ); > + nix::errno::Errno::result(rc) > + })? > + })?; but here we bubble up the outer Result if it's an error, without any cleanup. > + > + match rename_result { > + Ok(_) => Ok(file), > + Err(err) => { > + // if another process has already raced ahead and created > + // the file, let's just open theirs instead: > + let _ = nix::unistd::unlink(&temp_file_name); > + > + if err.already_exists() { > + match nix::fcntl::open(path, oflag, stat::Mode::empty()) { > + Ok(fd) => Ok(unsafe { File::from_raw_fd(fd) }), > + Err(err) => bail!("open {:?} failed - {}", path, err), > + } > + } else { > + bail!( > + "failed to move file at {:?} into place at {:?} - {}", > + temp_file_name, > + path, > + err > + ); > + } > + } > + } > +} > + > /// Change ownership of an open file handle > pub fn fchown(fd: RawFd, owner: Option<Uid>, group: Option<Gid>) -> Result<(), Error> { > // According to the POSIX specification, -1 is used to indicate that owner and group > -- > 2.30.2 > > > _______________________________________________ > pbs-devel mailing list > pbs-devel@lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel > > > ^ permalink raw reply [flat|nested] 8+ messages in thread
* [pbs-devel] [PATCH proxmox-apt] depend on proxmox 0.12.0, bump version to 0.5.1-1 @ 2021-07-20 11:51 Dietmar Maurer 2021-07-20 11:51 ` [pbs-devel] [PATCH proxmox-backup 1/2] use new atomic_open_or_create_file Dietmar Maurer 0 siblings, 1 reply; 8+ messages in thread From: Dietmar Maurer @ 2021-07-20 11:51 UTC (permalink / raw) To: pbs-devel --- Cargo.toml | 4 ++-- debian/changelog | 6 ++++++ debian/control | 8 ++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a316273..bdce2a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proxmox-apt" -version = "0.5.0" +version = "0.5.1" authors = [ "Fabian Ebner <f.ebner@proxmox.com>", "Proxmox Support Team <support@proxmox.com>", @@ -20,5 +20,5 @@ path = "src/lib.rs" anyhow = "1.0" once_cell = "1.3.1" openssl = "0.10" -proxmox = { version = "0.11.6", features = [ "api-macro" ] } +proxmox = { version = "0.12.0", features = [ "api-macro" ] } serde = { version = "1.0", features = ["derive"] } diff --git a/debian/changelog b/debian/changelog index b8e116d..542ec36 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +rust-proxmox-apt (0.5.1-1) unstable; urgency=medium + + * depend on proxmox 0.12.0 + + -- Proxmox Support Team <support@proxmox.com> Tue, 20 Jul 2021 13:18:02 +0200 + rust-proxmox-apt (0.5.0-1) unstable; urgency=medium * standard repo detection: handle alternative URI for PVE repos diff --git a/debian/control b/debian/control index ced40f2..f63cd3a 100644 --- a/debian/control +++ b/debian/control @@ -9,8 +9,8 @@ Build-Depends: debhelper (>= 12), librust-anyhow-1+default-dev <!nocheck>, librust-once-cell-1+default-dev (>= 1.3.1-~~) <!nocheck>, librust-openssl-0.10+default-dev <!nocheck>, - librust-proxmox-0.11+api-macro-dev (>= 0.11.6-~~) <!nocheck>, - librust-proxmox-0.11+default-dev (>= 0.11.6-~~) <!nocheck>, + librust-proxmox-0.12+api-macro-dev <!nocheck>, + librust-proxmox-0.12+default-dev <!nocheck>, librust-serde-1+default-dev <!nocheck>, librust-serde-1+derive-dev <!nocheck> Maintainer: Proxmox Support Team <support@proxmox.com> @@ -28,8 +28,8 @@ Depends: librust-anyhow-1+default-dev, librust-once-cell-1+default-dev (>= 1.3.1-~~), librust-openssl-0.10+default-dev, - librust-proxmox-0.11+api-macro-dev (>= 0.11.6-~~), - librust-proxmox-0.11+default-dev (>= 0.11.6-~~), + librust-proxmox-0.12+api-macro-dev, + librust-proxmox-0.12+default-dev, librust-serde-1+default-dev, librust-serde-1+derive-dev Provides: -- 2.30.2 ^ permalink raw reply [flat|nested] 8+ messages in thread
* [pbs-devel] [PATCH proxmox-backup 1/2] use new atomic_open_or_create_file 2021-07-20 11:51 [pbs-devel] [PATCH proxmox-apt] depend on proxmox 0.12.0, bump version to 0.5.1-1 Dietmar Maurer @ 2021-07-20 11:51 ` Dietmar Maurer 0 siblings, 0 replies; 8+ messages in thread From: Dietmar Maurer @ 2021-07-20 11:51 UTC (permalink / raw) To: pbs-devel Factor out open_backup_lockfile() method to acquire locks owned by user backup with permission 0660. --- src/api2/access/acl.rs | 4 +- src/api2/access/user.rs | 14 +-- src/api2/config/datastore.rs | 2 +- src/api2/config/remote.rs | 8 +- src/api2/config/sync.rs | 8 +- src/api2/config/tape_backup_job.rs | 9 +- src/api2/config/tape_encryption_keys.rs | 15 +--- src/api2/config/verify.rs | 9 +- src/api2/node/disks/directory.rs | 4 +- src/api2/node/network.rs | 9 +- src/backup/datastore.rs | 9 +- src/backup/mod.rs | 26 ++++++ src/config/acme/plugin.rs | 6 +- src/config/datastore.rs | 6 +- src/config/domains.rs | 6 +- src/config/drive.rs | 6 +- src/config/media_pool.rs | 6 +- src/config/node.rs | 8 +- src/config/tape_encryption_keys.rs | 8 +- src/config/tfa.rs | 11 ++- src/config/token_shadow.rs | 9 +- src/server/jobstate.rs | 16 ++-- src/server/worker_task.rs | 14 ++- src/tape/drive/mod.rs | 46 +++++----- src/tape/drive/virtual_tape.rs | 3 +- src/tape/inventory.rs | 49 ++--------- src/tape/media_pool.rs | 5 +- src/tools/memcom.rs | 110 +++--------------------- 28 files changed, 158 insertions(+), 268 deletions(-) diff --git a/src/api2/access/acl.rs b/src/api2/access/acl.rs index a78aabcd..88a2667c 100644 --- a/src/api2/access/acl.rs +++ b/src/api2/access/acl.rs @@ -3,12 +3,12 @@ use anyhow::{bail, Error}; use proxmox::api::{api, Router, RpcEnvironment, Permission}; -use proxmox::tools::fs::open_file_locked; use crate::api2::types::*; use crate::config::acl; use crate::config::acl::{Role, PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY}; use crate::config::cached_user_info::CachedUserInfo; +use crate::backup::open_backup_lockfile; fn extract_acl_node_data( node: &acl::AclTreeNode, @@ -200,7 +200,7 @@ pub fn update_acl( }; } - let _lock = open_file_locked(acl::ACL_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(acl::ACL_CFG_LOCKFILE, None, true)?; let (mut tree, expected_digest) = acl::config()?; diff --git a/src/api2/access/user.rs b/src/api2/access/user.rs index 70481ffb..c8647b30 100644 --- a/src/api2/access/user.rs +++ b/src/api2/access/user.rs @@ -8,7 +8,6 @@ use std::collections::HashMap; use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; use proxmox::api::router::SubdirMap; use proxmox::api::schema::{Schema, StringSchema}; -use proxmox::tools::fs::open_file_locked; use pbs_api_types::{ PASSWORD_FORMAT, PROXMOX_CONFIG_DIGEST_SCHEMA, SINGLE_LINE_COMMENT_SCHEMA, Authid, @@ -19,6 +18,7 @@ use crate::config::user; use crate::config::token_shadow; use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY}; use crate::config::cached_user_info::CachedUserInfo; +use crate::backup::open_backup_lockfile; pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.") .format(&PASSWORD_FORMAT) @@ -169,7 +169,7 @@ pub fn create_user( rpcenv: &mut dyn RpcEnvironment ) -> Result<(), Error> { - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; let user: user::User = serde_json::from_value(param)?; @@ -311,7 +311,7 @@ pub fn update_user( rpcenv: &mut dyn RpcEnvironment, ) -> Result<(), Error> { - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = user::config()?; @@ -404,7 +404,7 @@ pub fn update_user( pub fn delete_user(userid: Userid, digest: Option<String>) -> Result<(), Error> { let _tfa_lock = crate::config::tfa::write_lock()?; - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = user::config()?; @@ -540,7 +540,7 @@ pub fn generate_token( digest: Option<String>, ) -> Result<Value, Error> { - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = user::config()?; @@ -621,7 +621,7 @@ pub fn update_token( digest: Option<String>, ) -> Result<(), Error> { - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = user::config()?; @@ -689,7 +689,7 @@ pub fn delete_token( digest: Option<String>, ) -> Result<(), Error> { - let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = user::config()?; diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs index 19b822d4..a55e3bdc 100644 --- a/src/api2/config/datastore.rs +++ b/src/api2/config/datastore.rs @@ -60,7 +60,7 @@ pub fn list_datastores( } pub(crate) fn do_create_datastore( - _lock: std::fs::File, + _lock: BackupLockGuard, mut config: SectionConfigData, datastore: DataStoreConfig, worker: Option<&dyn TaskState>, diff --git a/src/api2/config/remote.rs b/src/api2/config/remote.rs index 8eacbc85..24ef8702 100644 --- a/src/api2/config/remote.rs +++ b/src/api2/config/remote.rs @@ -4,7 +4,6 @@ use ::serde::{Deserialize, Serialize}; use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; use proxmox::http_err; -use proxmox::tools::fs::open_file_locked; use pbs_client::{HttpClient, HttpClientOptions}; @@ -12,6 +11,7 @@ use crate::api2::types::*; use crate::config::cached_user_info::CachedUserInfo; use crate::config::remote; use crate::config::acl::{PRIV_REMOTE_AUDIT, PRIV_REMOTE_MODIFY}; +use crate::backup::open_backup_lockfile; #[api( input: { @@ -95,7 +95,7 @@ pub fn list_remotes( /// Create new remote. pub fn create_remote(password: String, param: Value) -> Result<(), Error> { - let _lock = open_file_locked(remote::REMOTE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(remote::REMOTE_CFG_LOCKFILE, None, true)?; let mut data = param; data["password"] = Value::from(base64::encode(password.as_bytes())); @@ -217,7 +217,7 @@ pub fn update_remote( digest: Option<String>, ) -> Result<(), Error> { - let _lock = open_file_locked(remote::REMOTE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(remote::REMOTE_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = remote::config()?; @@ -291,7 +291,7 @@ pub fn delete_remote(name: String, digest: Option<String>) -> Result<(), Error> } } - let _lock = open_file_locked(remote::REMOTE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(remote::REMOTE_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = remote::config()?; diff --git a/src/api2/config/sync.rs b/src/api2/config/sync.rs index e784029a..bc7b9f24 100644 --- a/src/api2/config/sync.rs +++ b/src/api2/config/sync.rs @@ -3,7 +3,6 @@ use serde_json::Value; use ::serde::{Deserialize, Serialize}; use proxmox::api::{api, Permission, Router, RpcEnvironment}; -use proxmox::tools::fs::open_file_locked; use crate::api2::types::*; @@ -18,6 +17,7 @@ use crate::config::acl::{ use crate::config::cached_user_info::CachedUserInfo; use crate::config::sync::{self, SyncJobConfig}; +use crate::backup::open_backup_lockfile; pub fn check_sync_job_read_access( user_info: &CachedUserInfo, @@ -152,7 +152,7 @@ pub fn create_sync_job( let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let user_info = CachedUserInfo::new()?; - let _lock = open_file_locked(sync::SYNC_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(sync::SYNC_CFG_LOCKFILE, None, true)?; let sync_job: sync::SyncJobConfig = serde_json::from_value(param)?; if !check_sync_job_modify_access(&user_info, &auth_id, &sync_job) { @@ -296,7 +296,7 @@ pub fn update_sync_job( let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let user_info = CachedUserInfo::new()?; - let _lock = open_file_locked(sync::SYNC_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(sync::SYNC_CFG_LOCKFILE, None, true)?; // pass/compare digest let (mut config, expected_digest) = sync::config()?; @@ -379,7 +379,7 @@ pub fn delete_sync_job( let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let user_info = CachedUserInfo::new()?; - let _lock = open_file_locked(sync::SYNC_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(sync::SYNC_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = sync::config()?; diff --git a/src/api2/config/tape_backup_job.rs b/src/api2/config/tape_backup_job.rs index 22afeb6d..02fa6f7d 100644 --- a/src/api2/config/tape_backup_job.rs +++ b/src/api2/config/tape_backup_job.rs @@ -3,7 +3,6 @@ use serde_json::Value; use ::serde::{Deserialize, Serialize}; use proxmox::api::{api, Router, RpcEnvironment, Permission}; -use proxmox::tools::fs::open_file_locked; use crate::{ api2::types::{ @@ -17,6 +16,7 @@ use crate::{ MEDIA_POOL_NAME_SCHEMA, SYNC_SCHEDULE_SCHEMA, }, + backup::open_backup_lockfile, config::{ self, cached_user_info::CachedUserInfo, @@ -89,8 +89,7 @@ pub fn create_tape_backup_job( job: TapeBackupJobConfig, _rpcenv: &mut dyn RpcEnvironment, ) -> Result<(), Error> { - - let _lock = open_file_locked(TAPE_JOB_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(TAPE_JOB_CFG_LOCKFILE, None, true)?; let (mut config, _digest) = config::tape_job::config()?; @@ -233,7 +232,7 @@ pub fn update_tape_backup_job( delete: Option<Vec<DeletableProperty>>, digest: Option<String>, ) -> Result<(), Error> { - let _lock = open_file_locked(TAPE_JOB_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(TAPE_JOB_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = config::tape_job::config()?; @@ -312,7 +311,7 @@ pub fn delete_tape_backup_job( digest: Option<String>, _rpcenv: &mut dyn RpcEnvironment, ) -> Result<(), Error> { - let _lock = open_file_locked(TAPE_JOB_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(TAPE_JOB_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = config::tape_job::config()?; diff --git a/src/api2/config/tape_encryption_keys.rs b/src/api2/config/tape_encryption_keys.rs index e1b42fb8..6fbfaded 100644 --- a/src/api2/config/tape_encryption_keys.rs +++ b/src/api2/config/tape_encryption_keys.rs @@ -9,7 +9,6 @@ use proxmox::{ RpcEnvironment, Permission, }, - tools::fs::open_file_locked, }; use pbs_datastore::{KeyInfo, Kdf}; @@ -35,6 +34,7 @@ use crate::{ PASSWORD_HINT_SCHEMA, }, backup::{ + open_backup_lockfile, KeyConfig, Fingerprint, }, @@ -122,11 +122,7 @@ pub fn change_passphrase( bail!("Please specify a key derivation function (none is not allowed here)."); } - let _lock = open_file_locked( - TAPE_KEYS_LOCKFILE, - std::time::Duration::new(10, 0), - true, - )?; + let _lock = open_backup_lockfile(TAPE_KEYS_LOCKFILE, None, true)?; let (mut config_map, expected_digest) = load_key_configs()?; @@ -261,12 +257,7 @@ pub fn delete_key( digest: Option<String>, _rpcenv: &mut dyn RpcEnvironment, ) -> Result<(), Error> { - - let _lock = open_file_locked( - TAPE_KEYS_LOCKFILE, - std::time::Duration::new(10, 0), - true, - )?; + let _lock = open_backup_lockfile(TAPE_KEYS_LOCKFILE, None, true)?; let (mut config_map, expected_digest) = load_key_configs()?; let (mut key_map, _) = load_keys()?; diff --git a/src/api2/config/verify.rs b/src/api2/config/verify.rs index 477cda89..1a613327 100644 --- a/src/api2/config/verify.rs +++ b/src/api2/config/verify.rs @@ -3,7 +3,6 @@ use serde_json::Value; use ::serde::{Deserialize, Serialize}; use proxmox::api::{api, Permission, Router, RpcEnvironment}; -use proxmox::tools::fs::open_file_locked; use crate::api2::types::*; @@ -13,8 +12,8 @@ use crate::config::acl::{ }; use crate::config::cached_user_info::CachedUserInfo; - use crate::config::verify::{self, VerificationJobConfig}; +use crate::backup::open_backup_lockfile; #[api( input: { @@ -102,7 +101,7 @@ pub fn create_verification_job( user_info.check_privs(&auth_id, &["datastore", &verification_job.store], PRIV_DATASTORE_VERIFY, false)?; - let _lock = open_file_locked(verify::VERIFICATION_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(verify::VERIFICATION_CFG_LOCKFILE, None, true)?; let (mut config, _digest) = verify::config()?; @@ -230,7 +229,7 @@ pub fn update_verification_job( let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let user_info = CachedUserInfo::new()?; - let _lock = open_file_locked(verify::VERIFICATION_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(verify::VERIFICATION_CFG_LOCKFILE, None, true)?; // pass/compare digest let (mut config, expected_digest) = verify::config()?; @@ -315,7 +314,7 @@ pub fn delete_verification_job( let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let user_info = CachedUserInfo::new()?; - let _lock = open_file_locked(verify::VERIFICATION_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(verify::VERIFICATION_CFG_LOCKFILE, None, true)?; let (mut config, expected_digest) = verify::config()?; diff --git a/src/api2/node/disks/directory.rs b/src/api2/node/disks/directory.rs index ae8a3974..75e0ea02 100644 --- a/src/api2/node/disks/directory.rs +++ b/src/api2/node/disks/directory.rs @@ -5,7 +5,6 @@ use ::serde::{Deserialize, Serialize}; use proxmox::api::{api, Permission, RpcEnvironment, RpcEnvironmentType}; use proxmox::api::section_config::SectionConfigData; use proxmox::api::router::Router; -use proxmox::tools::fs::open_file_locked; use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; use crate::tools::disks::{ @@ -18,6 +17,7 @@ use crate::server::WorkerTask; use crate::api2::types::*; use crate::config::datastore::{self, DataStoreConfig}; +use crate::backup::open_backup_lockfile; #[api( properties: { @@ -180,7 +180,7 @@ pub fn create_datastore_disk( systemd::start_unit(&mount_unit_name)?; if add_datastore { - let lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let lock = open_backup_lockfile(datastore::DATASTORE_CFG_LOCKFILE, None, true)?; let datastore: DataStoreConfig = serde_json::from_value(json!({ "name": name, "path": mount_point }))?; diff --git a/src/api2/node/network.rs b/src/api2/node/network.rs index 0dc321b8..ecf112a4 100644 --- a/src/api2/node/network.rs +++ b/src/api2/node/network.rs @@ -4,12 +4,12 @@ use ::serde::{Deserialize, Serialize}; use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; use proxmox::api::schema::parse_property_string; -use proxmox::tools::fs::open_file_locked; use crate::config::network::{self, NetworkConfig}; use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; use crate::api2::types::*; use crate::server::{WorkerTask}; +use crate::backup::open_backup_lockfile; fn split_interface_list(list: &str) -> Result<Vec<String>, Error> { let value = parse_property_string(&list, &NETWORK_INTERFACE_ARRAY_SCHEMA)?; @@ -238,7 +238,7 @@ pub fn create_interface( let interface_type = crate::tools::required_string_param(¶m, "type")?; let interface_type: NetworkInterfaceType = serde_json::from_value(interface_type.into())?; - let _lock = open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(network::NETWORK_LOCKFILE, None, true)?; let (mut config, _digest) = network::config()?; @@ -502,7 +502,7 @@ pub fn update_interface( param: Value, ) -> Result<(), Error> { - let _lock = open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(network::NETWORK_LOCKFILE, None, true)?; let (mut config, expected_digest) = network::config()?; @@ -642,8 +642,7 @@ pub fn update_interface( )] /// Remove network interface configuration. pub fn delete_interface(iface: String, digest: Option<String>) -> Result<(), Error> { - - let _lock = open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0), true)?; + let _lock = open_backup_lockfile(network::NETWORK_LOCKFILE, None, true)?; let (mut config, expected_digest) = network::config()?; diff --git a/src/backup/datastore.rs b/src/backup/datastore.rs index 0a5a52d1..848459e8 100644 --- a/src/backup/datastore.rs +++ b/src/backup/datastore.rs @@ -5,12 +5,11 @@ use std::sync::{Arc, Mutex}; use std::convert::TryFrom; use std::str::FromStr; use std::time::Duration; -use std::fs::File; use anyhow::{bail, format_err, Error}; use lazy_static::lazy_static; -use proxmox::tools::fs::{replace_file, file_read_optional_string, CreateOptions, open_file_locked}; +use proxmox::tools::fs::{replace_file, file_read_optional_string, CreateOptions}; use pbs_api_types::upid::UPID; use pbs_api_types::{Authid, GarbageCollectionStatus}; @@ -32,6 +31,8 @@ use pbs_tools::fs::{lock_dir_noblock, DirLockGuard}; use crate::config::datastore::{self, DataStoreConfig}; use crate::tools; +use crate::backup::{open_backup_lockfile, BackupLockGuard}; + lazy_static! { static ref DATASTORE_MAP: Mutex<HashMap<String, Arc<DataStore>>> = Mutex::new(HashMap::new()); @@ -797,12 +798,12 @@ impl DataStore { fn lock_manifest( &self, backup_dir: &BackupDir, - ) -> Result<File, Error> { + ) -> Result<BackupLockGuard, Error> { let path = self.manifest_lock_path(backup_dir)?; // update_manifest should never take a long time, so if someone else has // the lock we can simply block a bit and should get it soon - open_file_locked(&path, Duration::from_secs(5), true) + open_backup_lockfile(&path, Some(Duration::from_secs(5)), true) .map_err(|err| { format_err!( "unable to acquire manifest lock {:?} - {}", &path, err diff --git a/src/backup/mod.rs b/src/backup/mod.rs index 2f2c8426..c060c791 100644 --- a/src/backup/mod.rs +++ b/src/backup/mod.rs @@ -90,3 +90,29 @@ pub use verify::*; mod cached_chunk_reader; pub use cached_chunk_reader::*; + +pub struct BackupLockGuard(std::fs::File); + +/// Open or create a lock file owned by user "backup" and lock it. +/// +/// Owner/Group of the file is set to backup/backup. +/// File mode is 0660. +/// Default timeout is 10 seconds. +/// +/// Note: This method needs to be called by user "root" or "backup". +pub fn open_backup_lockfile<P: AsRef<std::path::Path>>( + path: P, + timeout: Option<std::time::Duration>, + exclusive: bool, +) -> Result<BackupLockGuard, Error> { + let user = backup_user()?; + let options = proxmox::tools::fs::CreateOptions::new() + .perm(nix::sys::stat::Mode::from_bits_truncate(0o660)) + .owner(user.uid) + .group(user.gid); + + let timeout = timeout.unwrap_or(std::time::Duration::new(10, 0)); + + let file = proxmox::tools::fs::open_file_locked(&path, timeout, exclusive, options)?; + Ok(BackupLockGuard(file)) +} diff --git a/src/config/acme/plugin.rs b/src/config/acme/plugin.rs index 960aec3a..fde800e2 100644 --- a/src/config/acme/plugin.rs +++ b/src/config/acme/plugin.rs @@ -12,6 +12,7 @@ use proxmox::api::{ use proxmox::tools::{fs::replace_file, fs::CreateOptions}; use crate::api2::types::PROXMOX_SAFE_ID_FORMAT; +use crate::backup::{open_backup_lockfile, BackupLockGuard}; pub const PLUGIN_ID_SCHEMA: Schema = StringSchema::new("ACME Challenge Plugin ID.") .format(&PROXMOX_SAFE_ID_FORMAT) @@ -142,11 +143,10 @@ fn init() -> SectionConfig { const ACME_PLUGIN_CFG_FILENAME: &str = pbs_buildcfg::configdir!("/acme/plugins.cfg"); const ACME_PLUGIN_CFG_LOCKFILE: &str = pbs_buildcfg::configdir!("/acme/.plugins.lck"); -const LOCK_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); -pub fn lock() -> Result<std::fs::File, Error> { +pub fn lock() -> Result<BackupLockGuard, Error> { super::make_acme_dir()?; - proxmox::tools::fs::open_file_locked(ACME_PLUGIN_CFG_LOCKFILE, LOCK_TIMEOUT, true) + open_backup_lockfile(ACME_PLUGIN_CFG_LOCKFILE, None, true) } pub fn config() -> Result<(PluginData, [u8; 32]), Error> { diff --git a/src/config/datastore.rs b/src/config/datastore.rs index d22fa316..9e37073d 100644 --- a/src/config/datastore.rs +++ b/src/config/datastore.rs @@ -14,12 +14,12 @@ use proxmox::api::{ }; use proxmox::tools::fs::{ - open_file_locked, replace_file, CreateOptions, }; use crate::api2::types::*; +use crate::backup::{open_backup_lockfile, BackupLockGuard}; lazy_static! { pub static ref CONFIG: SectionConfig = init(); @@ -138,8 +138,8 @@ pub const DATASTORE_CFG_FILENAME: &str = "/etc/proxmox-backup/datastore.cfg"; pub const DATASTORE_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.datastore.lck"; /// Get exclusive lock -pub fn lock_config() -> Result<std::fs::File, Error> { - open_file_locked(DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true) +pub fn lock_config() -> Result<BackupLockGuard, Error> { + open_backup_lockfile(DATASTORE_CFG_LOCKFILE, None, true) } pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { diff --git a/src/config/domains.rs b/src/config/domains.rs index 775c02f3..9f513a44 100644 --- a/src/config/domains.rs +++ b/src/config/domains.rs @@ -14,12 +14,12 @@ use proxmox::api::{ }; use proxmox::tools::fs::{ - open_file_locked, replace_file, CreateOptions, }; use crate::api2::types::*; +use crate::backup::{open_backup_lockfile, BackupLockGuard}; lazy_static! { pub static ref CONFIG: SectionConfig = init(); @@ -110,8 +110,8 @@ pub const DOMAINS_CFG_FILENAME: &str = "/etc/proxmox-backup/domains.cfg"; pub const DOMAINS_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.domains.lck"; /// Get exclusive lock -pub fn lock_config() -> Result<std::fs::File, Error> { - open_file_locked(DOMAINS_CFG_LOCKFILE, std::time::Duration::new(10, 0), true) +pub fn lock_config() -> Result<BackupLockGuard, Error> { + open_backup_lockfile(DOMAINS_CFG_LOCKFILE, None, true) } pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { diff --git a/src/config/drive.rs b/src/config/drive.rs index 57f6911f..9c20051f 100644 --- a/src/config/drive.rs +++ b/src/config/drive.rs @@ -26,13 +26,13 @@ use proxmox::{ }, }, tools::fs::{ - open_file_locked, replace_file, CreateOptions, }, }; use crate::{ + backup::{open_backup_lockfile, BackupLockGuard}, api2::types::{ DRIVE_NAME_SCHEMA, VirtualTapeDrive, @@ -79,8 +79,8 @@ pub const DRIVE_CFG_FILENAME: &str = "/etc/proxmox-backup/tape.cfg"; pub const DRIVE_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.tape.lck"; /// Get exclusive lock -pub fn lock() -> Result<std::fs::File, Error> { - open_file_locked(DRIVE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true) +pub fn lock() -> Result<BackupLockGuard, Error> { + open_backup_lockfile(DRIVE_CFG_LOCKFILE, None, true) } /// Read and parse the configuration file diff --git a/src/config/media_pool.rs b/src/config/media_pool.rs index 5ba1a6b2..e50992d8 100644 --- a/src/config/media_pool.rs +++ b/src/config/media_pool.rs @@ -21,13 +21,13 @@ use proxmox::{ } }, tools::fs::{ - open_file_locked, replace_file, CreateOptions, }, }; use crate::{ + backup::{open_backup_lockfile, BackupLockGuard}, api2::types::{ MEDIA_POOL_NAME_SCHEMA, MediaPoolConfig, @@ -59,8 +59,8 @@ pub const MEDIA_POOL_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.media-pool.lck"; /// Get exclusive lock -pub fn lock() -> Result<std::fs::File, Error> { - open_file_locked(MEDIA_POOL_CFG_LOCKFILE, std::time::Duration::new(10, 0), true) +pub fn lock() -> Result<BackupLockGuard, Error> { + open_backup_lockfile(MEDIA_POOL_CFG_LOCKFILE, None, true) } /// Read and parse the configuration file diff --git a/src/config/node.rs b/src/config/node.rs index 27e32cd1..dc3eeeb0 100644 --- a/src/config/node.rs +++ b/src/config/node.rs @@ -1,6 +1,4 @@ use std::collections::HashSet; -use std::fs::File; -use std::time::Duration; use anyhow::{bail, Error}; use nix::sys::stat::Mode; @@ -14,6 +12,7 @@ use proxmox_http::ProxyConfig; use pbs_buildcfg::configdir; +use crate::backup::{open_backup_lockfile, BackupLockGuard}; use crate::acme::AcmeClient; use crate::api2::types::{ AcmeAccountName, AcmeDomain, ACME_DOMAIN_PROPERTY_SCHEMA, HTTP_PROXY_SCHEMA, @@ -21,10 +20,9 @@ use crate::api2::types::{ const CONF_FILE: &str = configdir!("/node.cfg"); const LOCK_FILE: &str = configdir!("/.node.lck"); -const LOCK_TIMEOUT: Duration = Duration::from_secs(10); -pub fn lock() -> Result<File, Error> { - proxmox::tools::fs::open_file_locked(LOCK_FILE, LOCK_TIMEOUT, true) +pub fn lock() -> Result<BackupLockGuard, Error> { + open_backup_lockfile(LOCK_FILE, None, true) } /// Read the Node Config. diff --git a/src/config/tape_encryption_keys.rs b/src/config/tape_encryption_keys.rs index e3aab1d8..5ee0ac1f 100644 --- a/src/config/tape_encryption_keys.rs +++ b/src/config/tape_encryption_keys.rs @@ -18,12 +18,12 @@ use serde::{Deserialize, Serialize}; use proxmox::tools::fs::{ file_read_optional_string, replace_file, - open_file_locked, CreateOptions, }; use crate::{ backup::{ + open_backup_lockfile, Fingerprint, KeyConfig, }, @@ -187,11 +187,7 @@ pub fn save_key_configs(map: HashMap<Fingerprint, KeyConfig>) -> Result<(), Erro /// 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> { - let _lock = open_file_locked( - TAPE_KEYS_LOCKFILE, - std::time::Duration::new(10, 0), - true, - )?; + let _lock = open_backup_lockfile(TAPE_KEYS_LOCKFILE, None, true)?; let (mut key_map, _) = load_keys()?; let (mut config_map, _) = load_key_configs()?; diff --git a/src/config/tfa.rs b/src/config/tfa.rs index 341307db..efba0c13 100644 --- a/src/config/tfa.rs +++ b/src/config/tfa.rs @@ -4,7 +4,6 @@ use std::io::{self, Read, Seek, SeekFrom}; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::AsRawFd; use std::path::PathBuf; -use std::time::Duration; use anyhow::{bail, format_err, Error}; use nix::sys::stat::Mode; @@ -29,25 +28,25 @@ use proxmox::tools::AsHex; use pbs_buildcfg::configdir; use crate::api2::types::Userid; +use crate::backup::{open_backup_lockfile, BackupLockGuard}; /// Mapping of userid to TFA entry. pub type TfaUsers = HashMap<Userid, TfaUserData>; const CONF_FILE: &str = configdir!("/tfa.json"); const LOCK_FILE: &str = configdir!("/tfa.json.lock"); -const LOCK_TIMEOUT: Duration = Duration::from_secs(5); const CHALLENGE_DATA_PATH: &str = pbs_buildcfg::rundir!("/tfa/challenges"); /// U2F registration challenges time out after 2 minutes. const CHALLENGE_TIMEOUT: i64 = 2 * 60; -pub fn read_lock() -> Result<File, Error> { - proxmox::tools::fs::open_file_locked(LOCK_FILE, LOCK_TIMEOUT, false) +pub fn read_lock() -> Result<BackupLockGuard, Error> { + open_backup_lockfile(LOCK_FILE, None, false) } -pub fn write_lock() -> Result<File, Error> { - proxmox::tools::fs::open_file_locked(LOCK_FILE, LOCK_TIMEOUT, true) +pub fn write_lock() -> Result<BackupLockGuard, Error> { + open_backup_lockfile(LOCK_FILE, None, true) } /// Read the TFA entries. diff --git a/src/config/token_shadow.rs b/src/config/token_shadow.rs index 9f8bb2e0..a210ffb2 100644 --- a/src/config/token_shadow.rs +++ b/src/config/token_shadow.rs @@ -1,18 +1,17 @@ use std::collections::HashMap; -use std::time::Duration; use anyhow::{bail, format_err, Error}; use serde::{Serialize, Deserialize}; use serde_json::{from_value, Value}; -use proxmox::tools::fs::{open_file_locked, CreateOptions}; +use proxmox::tools::fs::CreateOptions; use crate::api2::types::Authid; use crate::auth; +use crate::backup::open_backup_lockfile; const LOCK_FILE: &str = pbs_buildcfg::configdir!("/token.shadow.lock"); const CONF_FILE: &str = pbs_buildcfg::configdir!("/token.shadow"); -const LOCK_TIMEOUT: Duration = Duration::from_secs(5); #[derive(Serialize, Deserialize)] #[serde(rename_all="kebab-case")] @@ -65,7 +64,7 @@ pub fn set_secret(tokenid: &Authid, secret: &str) -> Result<(), Error> { bail!("not an API token ID"); } - let _guard = open_file_locked(LOCK_FILE, LOCK_TIMEOUT, true)?; + let _guard = open_backup_lockfile(LOCK_FILE, None, true)?; let mut data = read_file()?; let hashed_secret = auth::encrypt_pw(secret)?; @@ -81,7 +80,7 @@ pub fn delete_secret(tokenid: &Authid) -> Result<(), Error> { bail!("not an API token ID"); } - let _guard = open_file_locked(LOCK_FILE, LOCK_TIMEOUT, true)?; + let _guard = open_backup_lockfile(LOCK_FILE, None, true)?; let mut data = read_file()?; data.remove(tokenid); diff --git a/src/server/jobstate.rs b/src/server/jobstate.rs index 96dd21aa..4b15fabc 100644 --- a/src/server/jobstate.rs +++ b/src/server/jobstate.rs @@ -37,18 +37,17 @@ //! # } //! //! ``` -use std::fs::File; use std::path::{Path, PathBuf}; -use std::time::Duration; use anyhow::{bail, format_err, Error}; use proxmox::tools::fs::{ - create_path, file_read_optional_string, open_file_locked, replace_file, CreateOptions, + create_path, file_read_optional_string, replace_file, CreateOptions, }; use serde::{Deserialize, Serialize}; use crate::{ - tools::systemd::time::{ + backup::{open_backup_lockfile, BackupLockGuard}, + tools::systemd::time::{ parse_calendar_event, compute_next_event, }, @@ -83,7 +82,7 @@ pub struct Job { jobname: String, /// The State of the job pub state: JobState, - _lock: File, + _lock: BackupLockGuard, } const JOB_STATE_BASEDIR: &str = "/var/lib/proxmox-backup/jobstates"; @@ -107,16 +106,13 @@ fn get_path(jobtype: &str, jobname: &str) -> PathBuf { path } -fn get_lock<P>(path: P) -> Result<File, Error> +fn get_lock<P>(path: P) -> Result<BackupLockGuard, Error> where P: AsRef<Path>, { let mut path = path.as_ref().to_path_buf(); path.set_extension("lck"); - let lock = open_file_locked(&path, Duration::new(10, 0), true)?; - let backup_user = crate::backup::backup_user()?; - nix::unistd::chown(&path, Some(backup_user.uid), Some(backup_user.gid))?; - Ok(lock) + open_backup_lockfile(&path, None, true) } /// Removes the statefile of a job, this is useful if we delete a job diff --git a/src/server/worker_task.rs b/src/server/worker_task.rs index 20518ece..be9a4794 100644 --- a/src/server/worker_task.rs +++ b/src/server/worker_task.rs @@ -14,7 +14,7 @@ use tokio::sync::oneshot; use proxmox::sys::linux::procfs; use proxmox::try_block; -use proxmox::tools::fs::{create_path, open_file_locked, replace_file, CreateOptions}; +use proxmox::tools::fs::{create_path, replace_file, CreateOptions}; use super::{UPID, UPIDExt}; @@ -24,6 +24,7 @@ use crate::server; use crate::tools::logrotate::{LogRotate, LogRotateFiles}; use crate::tools::{FileLogger, FileLogOptions}; use crate::api2::types::{Authid, TaskStateType}; +use crate::backup::{open_backup_lockfile, BackupLockGuard}; macro_rules! taskdir { ($subdir:expr) => (concat!(pbs_buildcfg::PROXMOX_BACKUP_LOG_DIR_M!(), "/tasks", $subdir)) @@ -313,13 +314,8 @@ pub struct TaskListInfo { pub state: Option<TaskState>, // endtime, status } -fn lock_task_list_files(exclusive: bool) -> Result<std::fs::File, Error> { - let backup_user = crate::backup::backup_user()?; - - let lock = open_file_locked(PROXMOX_BACKUP_TASK_LOCK_FN, std::time::Duration::new(10, 0), exclusive)?; - nix::unistd::chown(PROXMOX_BACKUP_TASK_LOCK_FN, Some(backup_user.uid), Some(backup_user.gid))?; - - Ok(lock) +fn lock_task_list_files(exclusive: bool) -> Result<BackupLockGuard, Error> { + open_backup_lockfile(PROXMOX_BACKUP_TASK_LOCK_FN, None, exclusive) } /// checks if the Task Archive is bigger that 'size_threshold' bytes, and @@ -481,7 +477,7 @@ pub struct TaskListInfoIterator { list: VecDeque<TaskListInfo>, end: bool, archive: Option<LogRotateFiles>, - lock: Option<File>, + lock: Option<BackupLockGuard>, } impl TaskListInfoIterator { diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs index fb4b6f47..a6cbed84 100644 --- a/src/tape/drive/mod.rs +++ b/src/tape/drive/mod.rs @@ -5,19 +5,21 @@ mod virtual_tape; mod lto; pub use lto::*; -use std::os::unix::io::AsRawFd; use std::path::PathBuf; use anyhow::{bail, format_err, Error}; use ::serde::{Deserialize}; use serde_json::Value; +use nix::fcntl::OFlag; +use nix::sys::stat::Mode; use proxmox::{ tools::{ Uuid, io::ReadExt, fs::{ - fchown, + lock_file, + atomic_open_or_create_file, file_read_optional_string, replace_file, CreateOptions, @@ -604,20 +606,34 @@ fn tape_device_path( pub struct DeviceLockGuard(std::fs::File); -// Acquires an exclusive lock on `device_path` -// // Uses systemd escape_unit to compute a file name from `device_path`, the try // to lock `/var/lock/<name>`. -fn lock_device_path(device_path: &str) -> Result<DeviceLockGuard, TapeLockError> { - +fn open_device_lock(device_path: &str) -> Result<std::fs::File, Error> { let lock_name = crate::tools::systemd::escape_unit(device_path, true); let mut path = std::path::PathBuf::from(crate::tape::DRIVE_LOCK_DIR); path.push(lock_name); + let user = crate::backup::backup_user()?; + let options = CreateOptions::new() + .perm(Mode::from_bits_truncate(0o660)) + .owner(user.uid) + .group(user.gid); + + atomic_open_or_create_file( + path, + OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_APPEND, + &[], + options, + ) +} + +// Acquires an exclusive lock on `device_path` +// +fn lock_device_path(device_path: &str) -> Result<DeviceLockGuard, TapeLockError> { + let mut file = open_device_lock(device_path)?; let timeout = std::time::Duration::new(10, 0); - let mut file = std::fs::OpenOptions::new().create(true).append(true).open(path)?; - if let Err(err) = proxmox::tools::fs::lock_file(&mut file, true, Some(timeout)) { + if let Err(err) = lock_file(&mut file, true, Some(timeout)) { if err.kind() == std::io::ErrorKind::Interrupted { return Err(TapeLockError::TimeOut); } else { @@ -625,9 +641,6 @@ fn lock_device_path(device_path: &str) -> Result<DeviceLockGuard, TapeLockError> } } - let backup_user = crate::backup::backup_user()?; - fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; - Ok(DeviceLockGuard(file)) } @@ -635,14 +648,10 @@ fn lock_device_path(device_path: &str) -> Result<DeviceLockGuard, TapeLockError> // non-blocking, and returning if the file is locked or not fn test_device_path_lock(device_path: &str) -> Result<bool, Error> { - let lock_name = crate::tools::systemd::escape_unit(device_path, true); - - let mut path = std::path::PathBuf::from(crate::tape::DRIVE_LOCK_DIR); - path.push(lock_name); + let mut file = open_device_lock(device_path)?; let timeout = std::time::Duration::new(0, 0); - let mut file = std::fs::OpenOptions::new().create(true).append(true).open(path)?; - match proxmox::tools::fs::lock_file(&mut file, true, Some(timeout)) { + match lock_file(&mut file, true, Some(timeout)) { // file was not locked, continue Ok(()) => {}, // file was locked, return true @@ -650,8 +659,5 @@ fn test_device_path_lock(device_path: &str) -> Result<bool, Error> { Err(err) => bail!("{}", err), } - let backup_user = crate::backup::backup_user()?; - fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; - Ok(false) } diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs index 3c5f9502..f48afa79 100644 --- a/src/tape/drive/virtual_tape.rs +++ b/src/tape/drive/virtual_tape.rs @@ -49,8 +49,9 @@ impl VirtualTapeDrive { let mut lock_path = std::path::PathBuf::from(&self.path); lock_path.push(".drive.lck"); + let options = CreateOptions::new(); let timeout = std::time::Duration::new(10, 0); - let lock = proxmox::tools::fs::open_file_locked(&lock_path, timeout, true)?; + let lock = proxmox::tools::fs::open_file_locked(&lock_path, timeout, true, options)?; Ok(VirtualTapeHandle { _lock: lock, diff --git a/src/tape/inventory.rs b/src/tape/inventory.rs index 4bb6d4f8..e97f07f3 100644 --- a/src/tape/inventory.rs +++ b/src/tape/inventory.rs @@ -24,8 +24,6 @@ use std::collections::{HashMap, BTreeMap}; use std::path::{Path, PathBuf}; -use std::os::unix::io::AsRawFd; -use std::fs::File; use std::time::Duration; use anyhow::{bail, Error}; @@ -35,9 +33,7 @@ use serde_json::json; use proxmox::tools::{ Uuid, fs::{ - open_file_locked, replace_file, - fchown, file_get_json, CreateOptions, }, @@ -51,6 +47,7 @@ use crate::{ MediaStatus, MediaLocation, }, + backup::{open_backup_lockfile, BackupLockGuard}, tape::{ TAPE_STATUS_DIR, MediaSet, @@ -149,17 +146,8 @@ impl Inventory { } /// Lock the database - fn lock(&self) -> Result<std::fs::File, Error> { - let file = open_file_locked(&self.lockfile_path, std::time::Duration::new(10, 0), true)?; - if cfg!(test) { - // We cannot use chown inside test environment (no permissions) - return Ok(file); - } - - let backup_user = crate::backup::backup_user()?; - fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; - - Ok(file) + fn lock(&self) -> Result<BackupLockGuard, Error> { + open_backup_lockfile(&self.lockfile_path, None, true) } fn load_media_db(path: &Path) -> Result<BTreeMap<Uuid, MediaStateEntry>, Error> { @@ -756,27 +744,16 @@ impl Inventory { } /// Lock a media pool -pub fn lock_media_pool(base_path: &Path, name: &str) -> Result<File, Error> { +pub fn lock_media_pool(base_path: &Path, name: &str) -> Result<BackupLockGuard, Error> { let mut path = base_path.to_owned(); path.push(format!(".pool-{}", name)); path.set_extension("lck"); - let timeout = std::time::Duration::new(10, 0); - let lock = proxmox::tools::fs::open_file_locked(&path, timeout, true)?; - - if cfg!(test) { - // We cannot use chown inside test environment (no permissions) - return Ok(lock); - } - - let backup_user = crate::backup::backup_user()?; - fchown(lock.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; - - Ok(lock) + open_backup_lockfile(&path, None, true) } /// Lock for media not assigned to any pool -pub fn lock_unassigned_media_pool(base_path: &Path) -> Result<File, Error> { +pub fn lock_unassigned_media_pool(base_path: &Path) -> Result<BackupLockGuard, Error> { // lock artificial "__UNASSIGNED__" pool to avoid races lock_media_pool(base_path, "__UNASSIGNED__") } @@ -788,22 +765,12 @@ pub fn lock_media_set( base_path: &Path, media_set_uuid: &Uuid, timeout: Option<Duration>, -) -> Result<File, Error> { +) -> Result<BackupLockGuard, Error> { let mut path = base_path.to_owned(); path.push(format!(".media-set-{}", media_set_uuid)); path.set_extension("lck"); - let timeout = timeout.unwrap_or(Duration::new(10, 0)); - let file = open_file_locked(&path, timeout, true)?; - if cfg!(test) { - // We cannot use chown inside test environment (no permissions) - return Ok(file); - } - - let backup_user = crate::backup::backup_user()?; - fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; - - Ok(file) + open_backup_lockfile(&path, timeout, true) } // shell completion helper diff --git a/src/tape/media_pool.rs b/src/tape/media_pool.rs index dd471551..7bb7fbdb 100644 --- a/src/tape/media_pool.rs +++ b/src/tape/media_pool.rs @@ -8,7 +8,6 @@ //! use std::path::{PathBuf, Path}; -use std::fs::File; use anyhow::{bail, Error}; use ::serde::{Deserialize, Serialize}; @@ -16,7 +15,7 @@ use ::serde::{Deserialize, Serialize}; use proxmox::tools::Uuid; use crate::{ - backup::Fingerprint, + backup::{Fingerprint, BackupLockGuard}, api2::types::{ MediaStatus, MediaLocation, @@ -61,7 +60,7 @@ pub struct MediaPool { inventory: Inventory, current_media_set: MediaSet, - current_media_set_lock: Option<File>, + current_media_set_lock: Option<BackupLockGuard>, } impl MediaPool { diff --git a/src/tools/memcom.rs b/src/tools/memcom.rs index 9921f5c9..87b73561 100644 --- a/src/tools/memcom.rs +++ b/src/tools/memcom.rs @@ -1,21 +1,17 @@ //! Memory based communication channel between proxy & daemon for things such as cache //! invalidation. -use std::ffi::CString; -use std::io; use std::os::unix::io::AsRawFd; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; -use anyhow::{bail, format_err, Error}; -use nix::errno::Errno; +use anyhow::Error; use nix::fcntl::OFlag; use nix::sys::mman::{MapFlags, ProtFlags}; use nix::sys::stat::Mode; use once_cell::sync::OnceCell; -use proxmox::sys::error::SysError; -use proxmox::tools::fd::Fd; +use proxmox::tools::fs::CreateOptions; use proxmox::tools::mmap::Mmap; /// In-memory communication channel. @@ -32,6 +28,7 @@ struct Head { static INSTANCE: OnceCell<Arc<Memcom>> = OnceCell::new(); const MEMCOM_FILE_PATH: &str = pbs_buildcfg::rundir!("/proxmox-backup-memcom"); +const EMPTY_PAGE: [u8; 4096] = [0u8; 4096]; impl Memcom { /// Open the memory based communication channel singleton. @@ -41,15 +38,20 @@ impl Memcom { // Actual work of `new`: fn open() -> Result<Arc<Self>, Error> { - let fd = match open_existing() { - Ok(fd) => fd, - Err(err) if err.not_found() => create_new()?, - Err(err) => bail!("failed to open {} - {}", MEMCOM_FILE_PATH, err), - }; + let user = crate::backup::backup_user()?; + let options = CreateOptions::new() + .perm(Mode::from_bits_truncate(0o660)) + .owner(user.uid) + .group(user.gid); + + let file = proxmox::tools::fs::atomic_open_or_create_file( + MEMCOM_FILE_PATH, + OFlag::O_RDWR | OFlag::O_CLOEXEC, + &EMPTY_PAGE, options)?; let mmap = unsafe { Mmap::<u8>::map_fd( - fd.as_raw_fd(), + file.as_raw_fd(), 0, 4096, ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, @@ -77,87 +79,3 @@ impl Memcom { .fetch_add(1, Ordering::AcqRel); } } - -/// The fast path opens an existing file. -fn open_existing() -> Result<Fd, nix::Error> { - Fd::open( - MEMCOM_FILE_PATH, - OFlag::O_RDWR | OFlag::O_CLOEXEC, - Mode::empty(), - ) -} - -/// Since we need to initialize the file, we also need a solid slow path where we create the file. -/// In order to make sure the next user's `open()` vs `mmap()` race against our `truncate()` call, -/// we create it in a temporary location and rotate it in place. -fn create_new() -> Result<Fd, Error> { - // create a temporary file: - let temp_file_name = format!("{}.{}", MEMCOM_FILE_PATH, unsafe { libc::getpid() }); - let fd = Fd::open( - temp_file_name.as_str(), - OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_RDWR | OFlag::O_CLOEXEC, - Mode::from_bits_truncate(0o660), - ) - .map_err(|err| { - format_err!( - "failed to create new in-memory communication file at {} - {}", - temp_file_name, - err - ) - })?; - - // let it be a page in size, it'll be initialized to zero by the kernel - nix::unistd::ftruncate(fd.as_raw_fd(), 4096) - .map_err(|err| format_err!("failed to set size of {} - {}", temp_file_name, err))?; - - // if this is the pbs-daemon (running as root) rather than the proxy (running as backup user), - // make sure the backup user can access the file: - if let Ok(backup_user) = crate::backup::backup_user() { - match nix::unistd::fchown(fd.as_raw_fd(), None, Some(backup_user.gid)) { - Ok(()) => (), - Err(err) if err.is_errno(Errno::EPERM) => { - // we're not the daemon (root), so the file is already owned by the backup user - } - Err(err) => bail!( - "failed to set group to 'backup' for {} - {}", - temp_file_name, - err - ), - } - } - - // rotate the file into place, but use `RENAME_NOREPLACE`, so in case 2 processes race against - // the initialization, the first one wins! - // TODO: nicer `renameat2()` wrapper in `proxmox::sys`? - let c_file_name = CString::new(temp_file_name.as_bytes()).unwrap(); - let new_path = CString::new(MEMCOM_FILE_PATH).unwrap(); - let rc = unsafe { - libc::renameat2( - -1, - c_file_name.as_ptr(), - -1, - new_path.as_ptr(), - libc::RENAME_NOREPLACE, - ) - }; - if rc == 0 { - return Ok(fd); - } - let err = io::Error::last_os_error(); - - // if another process has already raced ahead and created the file, let's just open theirs - // instead: - if err.kind() == io::ErrorKind::AlreadyExists { - // someone beat us to it: - drop(fd); - return open_existing().map_err(Error::from); - } - - // for any other errors, just bail out - bail!( - "failed to move file at {} into place at {} - {}", - temp_file_name, - MEMCOM_FILE_PATH, - err - ); -} -- 2.30.2 ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2021-07-20 11:52 UTC | newest] Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2021-07-16 8:28 [pbs-devel] [PATCH proxmox 1/2] new helper atomic_open_or_create_file() Dietmar Maurer 2021-07-16 8:28 ` [pbs-devel] [PATCH proxmox-backup 1/2] use new atomic_open_or_create_file Dietmar Maurer [not found] ` <<20210716082834.2354163-2-dietmar@proxmox.com> 2021-07-19 10:45 ` Fabian Grünbichler 2021-07-16 8:28 ` [pbs-devel] [PATCH proxmox-backup 2/2] add helpers to write configuration files Dietmar Maurer 2021-07-16 8:28 ` [pbs-devel] [PATCH proxmox 2/2] open_file_locked: add options parameter (CreateOptions) Dietmar Maurer [not found] ` <<20210716082834.2354163-4-dietmar@proxmox.com> 2021-07-19 10:44 ` Fabian Grünbichler [not found] ` <<20210716082834.2354163-1-dietmar@proxmox.com> 2021-07-19 10:44 ` [pbs-devel] [PATCH proxmox 1/2] new helper atomic_open_or_create_file() Fabian Grünbichler 2021-07-20 11:51 [pbs-devel] [PATCH proxmox-apt] depend on proxmox 0.12.0, bump version to 0.5.1-1 Dietmar Maurer 2021-07-20 11:51 ` [pbs-devel] [PATCH proxmox-backup 1/2] use new atomic_open_or_create_file Dietmar Maurer
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox