* [pbs-devel] [PATCH proxmox-backup v2 1/2] fix #5439: allow to reuse existing datastore
@ 2024-06-12 13:22 Gabriel Goller
2024-06-12 13:22 ` [pbs-devel] [PATCH proxmox-backup v2 2/2] web: disallow datastore in root, add reuse-datastore flag Gabriel Goller
2024-07-10 13:28 ` [pbs-devel] [PATCH proxmox-backup v2 1/2] fix #5439: allow to reuse existing datastore Fabian Grünbichler
0 siblings, 2 replies; 10+ messages in thread
From: Gabriel Goller @ 2024-06-12 13:22 UTC (permalink / raw)
To: pbs-devel
Disallow creating datastores in non-empty directories. Allow adding
existing datastores via a 'reuse-datastore' checkmark. This only checks
if all the necessary directories (.chunks + subdirectories and .lock)
are existing and have the correct permissions, then opens the
datastore.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
v2, thanks @Fabian:
- also check on frontend for root
- forbid datastore creation if dir not empty
- add reuse-datastore option
- verify chunkstore directories permissions and owners
pbs-datastore/src/chunk_store.rs | 49 ++++++++++++++++++++++++++++-
src/api2/config/datastore.rs | 54 +++++++++++++++++++++++++-------
src/api2/node/disks/directory.rs | 1 +
src/api2/node/disks/zfs.rs | 1 +
4 files changed, 93 insertions(+), 12 deletions(-)
diff --git a/pbs-datastore/src/chunk_store.rs b/pbs-datastore/src/chunk_store.rs
index 9f6289c9..077bc316 100644
--- a/pbs-datastore/src/chunk_store.rs
+++ b/pbs-datastore/src/chunk_store.rs
@@ -164,7 +164,7 @@ impl ChunkStore {
/// Note that this must be used with care, as it's dangerous to create two instances on the
/// same base path, as closing the underlying ProcessLocker drops all locks from this process
/// on the lockfile (even if separate FDs)
- pub(crate) fn open<P: Into<PathBuf>>(
+ pub fn open<P: Into<PathBuf>>(
name: &str,
base: P,
sync_level: DatastoreFSyncLevel,
@@ -563,6 +563,53 @@ impl ChunkStore {
// unwrap: only `None` in unit tests
ProcessLocker::try_exclusive_lock(self.locker.clone().unwrap())
}
+
+ /// Checks permissions and owner of passed path.
+ fn check_permissions<T: AsRef<Path>>(path: T, file_mode: u32) -> Result<(), Error> {
+ match nix::sys::stat::stat(path.as_ref()) {
+ Ok(stat) => {
+ if stat.st_uid != u32::from(pbs_config::backup_user()?.uid)
+ || stat.st_gid != u32::from(pbs_config::backup_group()?.gid)
+ || stat.st_mode & 0o700 != file_mode
+ {
+ bail!(
+ "unable to open existing chunk store path {:?} - permissions or owner not correct",
+ path.as_ref(),
+ );
+ }
+ }
+ Err(err) => {
+ bail!(
+ "unable to open existing chunk store path {:?} - {err}",
+ path.as_ref(),
+ );
+ }
+ }
+ Ok(())
+ }
+
+ /// Verify vital files in datastore. Checks the owner and permissions of: the chunkstore, it's
+ /// subdirectories and the lock file.
+ pub fn verify_chunkstore<T: AsRef<Path>>(path: T) -> Result<(), Error> {
+ // Check datastore root path perm/owner
+ ChunkStore::check_permissions(path.as_ref(), 0o700)?;
+
+ let chunk_dir = Self::chunk_dir(path.as_ref());
+ // Check datastore .chunks path perm/owner
+ ChunkStore::check_permissions(&chunk_dir, 0o700)?;
+
+ // Check all .chunks subdirectories
+ for i in 0..64 * 1024 {
+ let mut l1path = chunk_dir.clone();
+ l1path.push(format!("{:04x}", i));
+ ChunkStore::check_permissions(&l1path, 0o700)?;
+ }
+
+ // Check .lock file
+ let lockfile_path = Self::lockfile_path(path.as_ref());
+ ChunkStore::check_permissions(lockfile_path, 0o600)?;
+ Ok(())
+ }
}
#[test]
diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
index 6b742acb..f1e80b2d 100644
--- a/src/api2/config/datastore.rs
+++ b/src/api2/config/datastore.rs
@@ -1,7 +1,7 @@
use std::path::PathBuf;
use ::serde::{Deserialize, Serialize};
-use anyhow::Error;
+use anyhow::{bail, Error};
use hex::FromHex;
use serde_json::Value;
@@ -70,23 +70,48 @@ pub(crate) fn do_create_datastore(
_lock: BackupLockGuard,
mut config: SectionConfigData,
datastore: DataStoreConfig,
+ reuse_datastore: bool,
worker: Option<&dyn WorkerTaskContext>,
) -> Result<(), Error> {
let path: PathBuf = datastore.path.clone().into();
+ if path.parent().is_none() {
+ bail!("cannot create datastore in root path");
+ }
+
+ let datastore_empty = std::fs::read_dir(path.clone())?.all(|dir| {
+ dir.map_or(false, |file| {
+ file.file_name().to_str().map_or(false, |name| {
+ (name.starts_with('.')
+ && !(name == ".chunks" || name == ".lock" || name == ".gc-status"))
+ || name.starts_with("lost+found")
+ })
+ })
+ });
+
let tuning: DatastoreTuning = serde_json::from_value(
DatastoreTuning::API_SCHEMA
.parse_property_string(datastore.tuning.as_deref().unwrap_or(""))?,
)?;
- let backup_user = pbs_config::backup_user()?;
- let _store = ChunkStore::create(
- &datastore.name,
- path,
- backup_user.uid,
- backup_user.gid,
- worker,
- tuning.sync_level.unwrap_or_default(),
- )?;
+
+ if !datastore_empty && !reuse_datastore {
+ bail!("Directory is not empty!");
+ } else if !datastore_empty && reuse_datastore {
+ ChunkStore::verify_chunkstore(&path)?;
+
+ let _store =
+ ChunkStore::open(&datastore.name, path, tuning.sync_level.unwrap_or_default())?;
+ } else {
+ let backup_user = pbs_config::backup_user()?;
+ let _store = ChunkStore::create(
+ &datastore.name,
+ path,
+ backup_user.uid,
+ backup_user.gid,
+ worker,
+ tuning.sync_level.unwrap_or_default(),
+ )?;
+ }
config.set_data(&datastore.name, "datastore", &datastore)?;
@@ -103,6 +128,12 @@ pub(crate) fn do_create_datastore(
type: DataStoreConfig,
flatten: true,
},
+ "reuse-datastore": {
+ type: Boolean,
+ optional: true,
+ default: false,
+ description: "Re-use existing datastore directory."
+ }
},
},
access: {
@@ -112,6 +143,7 @@ pub(crate) fn do_create_datastore(
/// Create new datastore config.
pub fn create_datastore(
config: DataStoreConfig,
+ reuse_datastore: bool,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
let lock = pbs_config::datastore::lock_config()?;
@@ -156,7 +188,7 @@ pub fn create_datastore(
auth_id.to_string(),
to_stdout,
move |worker| {
- do_create_datastore(lock, section_config, config, Some(&worker))?;
+ do_create_datastore(lock, section_config, config, reuse_datastore, Some(&worker))?;
if let Some(prune_job_config) = prune_job_config {
do_create_prune_job(prune_job_config, Some(&worker))
diff --git a/src/api2/node/disks/directory.rs b/src/api2/node/disks/directory.rs
index 9f1112a9..25cfe784 100644
--- a/src/api2/node/disks/directory.rs
+++ b/src/api2/node/disks/directory.rs
@@ -217,6 +217,7 @@ pub fn create_datastore_disk(
lock,
config,
datastore,
+ false,
Some(&worker),
)?;
}
diff --git a/src/api2/node/disks/zfs.rs b/src/api2/node/disks/zfs.rs
index 673dc1bf..ad2129fc 100644
--- a/src/api2/node/disks/zfs.rs
+++ b/src/api2/node/disks/zfs.rs
@@ -323,6 +323,7 @@ pub fn create_zpool(
lock,
config,
datastore,
+ false,
Some(&worker),
)?;
}
--
2.43.0
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 2/2] web: disallow datastore in root, add reuse-datastore flag
2024-06-12 13:22 [pbs-devel] [PATCH proxmox-backup v2 1/2] fix #5439: allow to reuse existing datastore Gabriel Goller
@ 2024-06-12 13:22 ` Gabriel Goller
2024-07-22 6:45 ` Thomas Lamprecht
2024-07-10 13:28 ` [pbs-devel] [PATCH proxmox-backup v2 1/2] fix #5439: allow to reuse existing datastore Fabian Grünbichler
1 sibling, 1 reply; 10+ messages in thread
From: Gabriel Goller @ 2024-06-12 13:22 UTC (permalink / raw)
To: pbs-devel
Disallows creating a datastore in root on the frontend side, by
filtering the '/' path. Add reuse-flag to permit us to open existing
datastores.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
www/window/DataStoreEdit.js | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/www/window/DataStoreEdit.js b/www/window/DataStoreEdit.js
index b6115460..2565688e 100644
--- a/www/window/DataStoreEdit.js
+++ b/www/window/DataStoreEdit.js
@@ -61,6 +61,17 @@ Ext.define('PBS.DataStoreEdit', {
allowBlank: false,
fieldLabel: gettext('Backing Path'),
emptyText: gettext('An absolute path'),
+ validator: function(val) {
+ if (val.trim() === '/') {
+ return false;
+ }
+ return true;
+ },
+ },
+ {
+ xtype: 'checkbox',
+ name: 'reuse-datastore',
+ fieldLabel: gettext('Reuse existing datastore'),
},
],
column2: [
--
2.43.0
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [pbs-devel] [PATCH proxmox-backup v2 2/2] web: disallow datastore in root, add reuse-datastore flag
2024-06-12 13:22 ` [pbs-devel] [PATCH proxmox-backup v2 2/2] web: disallow datastore in root, add reuse-datastore flag Gabriel Goller
@ 2024-07-22 6:45 ` Thomas Lamprecht
2024-07-30 9:10 ` Gabriel Goller
0 siblings, 1 reply; 10+ messages in thread
From: Thomas Lamprecht @ 2024-07-22 6:45 UTC (permalink / raw)
To: Proxmox Backup Server development discussion, Gabriel Goller
Am 12/06/2024 um 15:22 schrieb Gabriel Goller:
> Disallows creating a datastore in root on the frontend side, by
> filtering the '/' path. Add reuse-flag to permit us to open existing
> datastores.
>
> Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
> ---
> www/window/DataStoreEdit.js | 11 +++++++++++
> 1 file changed, 11 insertions(+)
>
> diff --git a/www/window/DataStoreEdit.js b/www/window/DataStoreEdit.js
> index b6115460..2565688e 100644
> --- a/www/window/DataStoreEdit.js
> +++ b/www/window/DataStoreEdit.js
> @@ -61,6 +61,17 @@ Ext.define('PBS.DataStoreEdit', {
> allowBlank: false,
> fieldLabel: gettext('Backing Path'),
> emptyText: gettext('An absolute path'),
> + validator: function(val) {
> + if (val.trim() === '/') {
> + return false;
> + }
> + return true;
above could be reduced to:
validator: val => val?.trim() !== '/',
Note also the null check, not 100% sure from top of my head, but when
going from some value to deleting the field content (CTRL + A; DEL)
the validator might be called with a null/undefined value, or did you
check and ensure that it's then always an empty string?
Besides that, this seems quite unrelated to the "allow re-adding an
existing datastore" part and should be its own patch, which could
be applied up-front.
Oh, and does the backend disallow this already, or is this intended
as "unsafe" frontend only check for pure UX reason (can be fine, but
I would at least note that explicitly in the commit message).
> + },
> + },
> + {
> + xtype: 'checkbox',
> + name: 'reuse-datastore',
> + fieldLabel: gettext('Reuse existing datastore'),
hmm, not sure w.r.t. always showing a checkbox for some relatively niche
use case by default.
Maybe this is better split-off into either an advanced section or by
opening the dialogue through some new button (e.g. by adding a top bar
in the all-datastore summary page and add an "Add Datastore" split-
button there with the "Re-Add Existing Datastore" as menu button there
(like the shutdown + reboot/stop for guests in PVE).
> },
> ],
> column2: [
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [pbs-devel] [PATCH proxmox-backup v2 2/2] web: disallow datastore in root, add reuse-datastore flag
2024-07-22 6:45 ` Thomas Lamprecht
@ 2024-07-30 9:10 ` Gabriel Goller
0 siblings, 0 replies; 10+ messages in thread
From: Gabriel Goller @ 2024-07-30 9:10 UTC (permalink / raw)
To: Thomas Lamprecht; +Cc: Proxmox Backup Server development discussion
On 22.07.2024 08:45, Thomas Lamprecht wrote:
>Am 12/06/2024 um 15:22 schrieb Gabriel Goller:
>> Disallows creating a datastore in root on the frontend side, by
>> filtering the '/' path. Add reuse-flag to permit us to open existing
>> datastores.
>>
>> Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
>> ---
>> www/window/DataStoreEdit.js | 11 +++++++++++
>> 1 file changed, 11 insertions(+)
>>
>> diff --git a/www/window/DataStoreEdit.js b/www/window/DataStoreEdit.js
>> index b6115460..2565688e 100644
>> --- a/www/window/DataStoreEdit.js
>> +++ b/www/window/DataStoreEdit.js
>> @@ -61,6 +61,17 @@ Ext.define('PBS.DataStoreEdit', {
>> allowBlank: false,
>> fieldLabel: gettext('Backing Path'),
>> emptyText: gettext('An absolute path'),
>> + validator: function(val) {
>> + if (val.trim() === '/') {
>> + return false;
>> + }
>> + return true;
>
>above could be reduced to:
>
>validator: val => val?.trim() !== '/',
>
>
>Note also the null check, not 100% sure from top of my head, but when
>going from some value to deleting the field content (CTRL + A; DEL)
>the validator might be called with a null/undefined value, or did you
>check and ensure that it's then always an empty string?
Hmm I could not reproduce this, probably because we have the `allowBlank:
false` above? Either way, your line is cleaner, so I will update it :).
>Besides that, this seems quite unrelated to the "allow re-adding an
>existing datastore" part and should be its own patch, which could
>be applied up-front.
>Oh, and does the backend disallow this already, or is this intended
>as "unsafe" frontend only check for pure UX reason (can be fine, but
>I would at least note that explicitly in the commit message).
This is checked on the backend as well.
(src/api2/config/datastore.rs:77, with this series applied.)
Yes, the series started out as "disallow datastore creation in root" and
now transformed into "allow opening a existing datastore" (with all the
correct checks). I could factor out the "root-path-check" (frontend and
backend) and submit a separate series for that though?
>
>
>> + },
>> + },
>> + {
>> + xtype: 'checkbox',
>> + name: 'reuse-datastore',
>> + fieldLabel: gettext('Reuse existing datastore'),
>
>hmm, not sure w.r.t. always showing a checkbox for some relatively niche
>use case by default.
>Maybe this is better split-off into either an advanced section or by
>opening the dialogue through some new button (e.g. by adding a top bar
>in the all-datastore summary page and add an "Add Datastore" split-
>button there with the "Re-Add Existing Datastore" as menu button there
>(like the shutdown + reboot/stop for guests in PVE).
I'd agree with an "Advanced" button to toggle this option. Will include
it in the next version!
A split button in the datastore overview is IMO a bit hard to find, I
don't think the overview is used that much.
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [pbs-devel] [PATCH proxmox-backup v2 1/2] fix #5439: allow to reuse existing datastore
2024-06-12 13:22 [pbs-devel] [PATCH proxmox-backup v2 1/2] fix #5439: allow to reuse existing datastore Gabriel Goller
2024-06-12 13:22 ` [pbs-devel] [PATCH proxmox-backup v2 2/2] web: disallow datastore in root, add reuse-datastore flag Gabriel Goller
@ 2024-07-10 13:28 ` Fabian Grünbichler
2024-07-12 11:57 ` Gabriel Goller
1 sibling, 1 reply; 10+ messages in thread
From: Fabian Grünbichler @ 2024-07-10 13:28 UTC (permalink / raw)
To: Proxmox Backup Server development discussion
On June 12, 2024 3:22 pm, Gabriel Goller wrote:
> Disallow creating datastores in non-empty directories. Allow adding
> existing datastores via a 'reuse-datastore' checkmark. This only checks
> if all the necessary directories (.chunks + subdirectories and .lock)
> are existing and have the correct permissions, then opens the
> datastore.
>
> Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
> ---
>
> v2, thanks @Fabian:
> - also check on frontend for root
> - forbid datastore creation if dir not empty
> - add reuse-datastore option
> - verify chunkstore directories permissions and owners
>
> pbs-datastore/src/chunk_store.rs | 49 ++++++++++++++++++++++++++++-
> src/api2/config/datastore.rs | 54 +++++++++++++++++++++++++-------
> src/api2/node/disks/directory.rs | 1 +
> src/api2/node/disks/zfs.rs | 1 +
> 4 files changed, 93 insertions(+), 12 deletions(-)
>
> diff --git a/pbs-datastore/src/chunk_store.rs b/pbs-datastore/src/chunk_store.rs
> index 9f6289c9..077bc316 100644
> --- a/pbs-datastore/src/chunk_store.rs
> +++ b/pbs-datastore/src/chunk_store.rs
> @@ -164,7 +164,7 @@ impl ChunkStore {
> /// Note that this must be used with care, as it's dangerous to create two instances on the
> /// same base path, as closing the underlying ProcessLocker drops all locks from this process
> /// on the lockfile (even if separate FDs)
given this ^
> - pub(crate) fn open<P: Into<PathBuf>>(
> + pub fn open<P: Into<PathBuf>>(
wouldn't it make more sense to split out the path checks and call them
both in open and in verify_chunkstore below
> name: &str,
> base: P,
> sync_level: DatastoreFSyncLevel,
> @@ -563,6 +563,53 @@ impl ChunkStore {
> // unwrap: only `None` in unit tests
> ProcessLocker::try_exclusive_lock(self.locker.clone().unwrap())
> }
> +
> + /// Checks permissions and owner of passed path.
> + fn check_permissions<T: AsRef<Path>>(path: T, file_mode: u32) -> Result<(), Error> {
> + match nix::sys::stat::stat(path.as_ref()) {
> + Ok(stat) => {
> + if stat.st_uid != u32::from(pbs_config::backup_user()?.uid)
> + || stat.st_gid != u32::from(pbs_config::backup_group()?.gid)
> + || stat.st_mode & 0o700 != file_mode
> + {
> + bail!(
> + "unable to open existing chunk store path {:?} - permissions or owner not correct",
> + path.as_ref(),
> + );
> + }
> + }
> + Err(err) => {
> + bail!(
> + "unable to open existing chunk store path {:?} - {err}",
> + path.as_ref(),
> + );
> + }
> + }
> + Ok(())
> + }
> +
> + /// Verify vital files in datastore. Checks the owner and permissions of: the chunkstore, it's
> + /// subdirectories and the lock file.
> + pub fn verify_chunkstore<T: AsRef<Path>>(path: T) -> Result<(), Error> {
> + // Check datastore root path perm/owner
> + ChunkStore::check_permissions(path.as_ref(), 0o700)?;
> +
> + let chunk_dir = Self::chunk_dir(path.as_ref());
> + // Check datastore .chunks path perm/owner
> + ChunkStore::check_permissions(&chunk_dir, 0o700)?;
> +
> + // Check all .chunks subdirectories
> + for i in 0..64 * 1024 {
> + let mut l1path = chunk_dir.clone();
> + l1path.push(format!("{:04x}", i));
> + ChunkStore::check_permissions(&l1path, 0o700)?;
> + }
> +
> + // Check .lock file
> + let lockfile_path = Self::lockfile_path(path.as_ref());
> + ChunkStore::check_permissions(lockfile_path, 0o600)?;
> + Ok(())
> + }
> }
>
> #[test]
> diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
> index 6b742acb..f1e80b2d 100644
> --- a/src/api2/config/datastore.rs
> +++ b/src/api2/config/datastore.rs
> @@ -1,7 +1,7 @@
> use std::path::PathBuf;
>
> use ::serde::{Deserialize, Serialize};
> -use anyhow::Error;
> +use anyhow::{bail, Error};
> use hex::FromHex;
> use serde_json::Value;
>
> @@ -70,23 +70,48 @@ pub(crate) fn do_create_datastore(
> _lock: BackupLockGuard,
> mut config: SectionConfigData,
> datastore: DataStoreConfig,
> + reuse_datastore: bool,
> worker: Option<&dyn WorkerTaskContext>,
> ) -> Result<(), Error> {
> let path: PathBuf = datastore.path.clone().into();
>
> + if path.parent().is_none() {
> + bail!("cannot create datastore in root path");
> + }
> +
> + let datastore_empty = std::fs::read_dir(path.clone())?.all(|dir| {
doesn't this read_dir fail if `path` doesn't exist yet? prior to this
change, creating a datastore would also create the datastore dir itself
(and any parents!).
> + dir.map_or(false, |file| {
> + file.file_name().to_str().map_or(false, |name| {
> + (name.starts_with('.')
> + && !(name == ".chunks" || name == ".lock" || name == ".gc-status"))
> + || name.starts_with("lost+found")
I think the last check there could be == as well?
the indentation here is kinda whacky.. maybe it would be enough to
consider `.chunks` to mark a dir as non-empty?
> + })
> + })
>+ });
should we do this always? or restructure the ifs below and just do it
for the non-reusing create path?
> +
> let tuning: DatastoreTuning = serde_json::from_value(
> DatastoreTuning::API_SCHEMA
> .parse_property_string(datastore.tuning.as_deref().unwrap_or(""))?,
> )?;
> - let backup_user = pbs_config::backup_user()?;
> - let _store = ChunkStore::create(
> - &datastore.name,
> - path,
> - backup_user.uid,
> - backup_user.gid,
> - worker,
> - tuning.sync_level.unwrap_or_default(),
> - )?;
> +
> + if !datastore_empty && !reuse_datastore {
> + bail!("Directory is not empty!");
> + } else if !datastore_empty && reuse_datastore {
> + ChunkStore::verify_chunkstore(&path)?;
> +
> + let _store =
> + ChunkStore::open(&datastore.name, path, tuning.sync_level.unwrap_or_default())?;
(continued from first comment above) and leave out this call here?
> + } else {
> + let backup_user = pbs_config::backup_user()?;
> + let _store = ChunkStore::create(
> + &datastore.name,
> + path,
> + backup_user.uid,
> + backup_user.gid,
> + worker,
> + tuning.sync_level.unwrap_or_default(),
> + )?;
> + }
>
> config.set_data(&datastore.name, "datastore", &datastore)?;
>
> @@ -103,6 +128,12 @@ pub(crate) fn do_create_datastore(
> type: DataStoreConfig,
> flatten: true,
> },
> + "reuse-datastore": {
> + type: Boolean,
> + optional: true,
> + default: false,
> + description: "Re-use existing datastore directory."
> + }
> },
> },
> access: {
> @@ -112,6 +143,7 @@ pub(crate) fn do_create_datastore(
> /// Create new datastore config.
> pub fn create_datastore(
> config: DataStoreConfig,
> + reuse_datastore: bool,
> rpcenv: &mut dyn RpcEnvironment,
> ) -> Result<String, Error> {
> let lock = pbs_config::datastore::lock_config()?;
> @@ -156,7 +188,7 @@ pub fn create_datastore(
> auth_id.to_string(),
> to_stdout,
> move |worker| {
> - do_create_datastore(lock, section_config, config, Some(&worker))?;
> + do_create_datastore(lock, section_config, config, reuse_datastore, Some(&worker))?;
>
> if let Some(prune_job_config) = prune_job_config {
> do_create_prune_job(prune_job_config, Some(&worker))
> diff --git a/src/api2/node/disks/directory.rs b/src/api2/node/disks/directory.rs
> index 9f1112a9..25cfe784 100644
> --- a/src/api2/node/disks/directory.rs
> +++ b/src/api2/node/disks/directory.rs
> @@ -217,6 +217,7 @@ pub fn create_datastore_disk(
> lock,
> config,
> datastore,
> + false,
> Some(&worker),
> )?;
> }
> diff --git a/src/api2/node/disks/zfs.rs b/src/api2/node/disks/zfs.rs
> index 673dc1bf..ad2129fc 100644
> --- a/src/api2/node/disks/zfs.rs
> +++ b/src/api2/node/disks/zfs.rs
> @@ -323,6 +323,7 @@ pub fn create_zpool(
> lock,
> config,
> datastore,
> + false,
> Some(&worker),
> )?;
> }
> --
> 2.43.0
>
>
>
> _______________________________________________
> pbs-devel mailing list
> pbs-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
>
>
>
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [pbs-devel] [PATCH proxmox-backup v2 1/2] fix #5439: allow to reuse existing datastore
2024-07-10 13:28 ` [pbs-devel] [PATCH proxmox-backup v2 1/2] fix #5439: allow to reuse existing datastore Fabian Grünbichler
@ 2024-07-12 11:57 ` Gabriel Goller
2024-07-15 11:23 ` Fabian Grünbichler
0 siblings, 1 reply; 10+ messages in thread
From: Gabriel Goller @ 2024-07-12 11:57 UTC (permalink / raw)
To: Proxmox Backup Server development discussion
On 10.07.2024 15:28, Fabian Grünbichler wrote:
>On June 12, 2024 3:22 pm, Gabriel Goller wrote:
>> Disallow creating datastores in non-empty directories. Allow adding
>> existing datastores via a 'reuse-datastore' checkmark. This only checks
>> if all the necessary directories (.chunks + subdirectories and .lock)
>> are existing and have the correct permissions, then opens the
>> datastore.
>>
>> Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
>> ---
>>
>> v2, thanks @Fabian:
>> - also check on frontend for root
>> - forbid datastore creation if dir not empty
>> - add reuse-datastore option
>> - verify chunkstore directories permissions and owners
>>
>> pbs-datastore/src/chunk_store.rs | 49 ++++++++++++++++++++++++++++-
>> src/api2/config/datastore.rs | 54 +++++++++++++++++++++++++-------
>> src/api2/node/disks/directory.rs | 1 +
>> src/api2/node/disks/zfs.rs | 1 +
>> 4 files changed, 93 insertions(+), 12 deletions(-)
>>
>> diff --git a/pbs-datastore/src/chunk_store.rs b/pbs-datastore/src/chunk_store.rs
>> index 9f6289c9..077bc316 100644
>> --- a/pbs-datastore/src/chunk_store.rs
>> +++ b/pbs-datastore/src/chunk_store.rs
>> @@ -164,7 +164,7 @@ impl ChunkStore {
>> /// Note that this must be used with care, as it's dangerous to create two instances on the
>> /// same base path, as closing the underlying ProcessLocker drops all locks from this process
>> /// on the lockfile (even if separate FDs)
>
>given this ^
>
>> - pub(crate) fn open<P: Into<PathBuf>>(
>> + pub fn open<P: Into<PathBuf>>(
>
>wouldn't it make more sense to split out the path checks and call them
>both in open and in verify_chunkstore below
Why call them both in open and verify_chunkstore? create_datastore
always calls the verify method before open, so we could move this to
the verify function? I think I don't understand this correctly...
>> name: &str,
>> base: P,
>> sync_level: DatastoreFSyncLevel,
>> @@ -563,6 +563,53 @@ impl ChunkStore {
>> // unwrap: only `None` in unit tests
>> ProcessLocker::try_exclusive_lock(self.locker.clone().unwrap())
>> }
>> +
>> + /// Checks permissions and owner of passed path.
>> + fn check_permissions<T: AsRef<Path>>(path: T, file_mode: u32) -> Result<(), Error> {
>> + match nix::sys::stat::stat(path.as_ref()) {
>> + Ok(stat) => {
>> + if stat.st_uid != u32::from(pbs_config::backup_user()?.uid)
>> + || stat.st_gid != u32::from(pbs_config::backup_group()?.gid)
>> + || stat.st_mode & 0o700 != file_mode
>> + {
>> + bail!(
>> + "unable to open existing chunk store path {:?} - permissions or owner not correct",
>> + path.as_ref(),
>> + );
>> + }
>> + }
>> + Err(err) => {
>> + bail!(
>> + "unable to open existing chunk store path {:?} - {err}",
>> + path.as_ref(),
>> + );
>> + }
>> + }
>> + Ok(())
>> + }
>> +
>> + /// Verify vital files in datastore. Checks the owner and permissions of: the chunkstore, it's
>> + /// subdirectories and the lock file.
>> + pub fn verify_chunkstore<T: AsRef<Path>>(path: T) -> Result<(), Error> {
>> + // Check datastore root path perm/owner
>> + ChunkStore::check_permissions(path.as_ref(), 0o700)?;
>> +
>> + let chunk_dir = Self::chunk_dir(path.as_ref());
>> + // Check datastore .chunks path perm/owner
>> + ChunkStore::check_permissions(&chunk_dir, 0o700)?;
>> +
>> + // Check all .chunks subdirectories
>> + for i in 0..64 * 1024 {
>> + let mut l1path = chunk_dir.clone();
>> + l1path.push(format!("{:04x}", i));
>> + ChunkStore::check_permissions(&l1path, 0o700)?;
>> + }
>> +
>> + // Check .lock file
>> + let lockfile_path = Self::lockfile_path(path.as_ref());
>> + ChunkStore::check_permissions(lockfile_path, 0o600)?;
>> + Ok(())
>> + }
>> }
>>
>> #[test]
>> diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
>> index 6b742acb..f1e80b2d 100644
>> --- a/src/api2/config/datastore.rs
>> +++ b/src/api2/config/datastore.rs
>> @@ -1,7 +1,7 @@
>> use std::path::PathBuf;
>>
>> use ::serde::{Deserialize, Serialize};
>> -use anyhow::Error;
>> +use anyhow::{bail, Error};
>> use hex::FromHex;
>> use serde_json::Value;
>>
>> @@ -70,23 +70,48 @@ pub(crate) fn do_create_datastore(
>> _lock: BackupLockGuard,
>> mut config: SectionConfigData,
>> datastore: DataStoreConfig,
>> + reuse_datastore: bool,
>> worker: Option<&dyn WorkerTaskContext>,
>> ) -> Result<(), Error> {
>> let path: PathBuf = datastore.path.clone().into();
>>
>> + if path.parent().is_none() {
>> + bail!("cannot create datastore in root path");
>> + }
>> +
>> + let datastore_empty = std::fs::read_dir(path.clone())?.all(|dir| {
>
>doesn't this read_dir fail if `path` doesn't exist yet? prior to this
>change, creating a datastore would also create the datastore dir itself
>(and any parents!).
Yes, correct. Changed it to another map_or.
>> + dir.map_or(false, |file| {
>> + file.file_name().to_str().map_or(false, |name| {
>> + (name.starts_with('.')
>> + && !(name == ".chunks" || name == ".lock" || name == ".gc-status"))
>> + || name.starts_with("lost+found")
>
>I think the last check there could be == as well?
Yep.
>the indentation here is kinda whacky.. maybe it would be enough to
>consider `.chunks` to mark a dir as non-empty?
Hmm, yeah this would make everything simpler.
>> + })
>> + })
>>+ });
>
>should we do this always? or restructure the ifs below and just do it
>for the non-reusing create path?
Oh, yeah that would work. We already check the existance of `.chunks`
with the verify method (though implicitly) so that should work.
>> +
>> let tuning: DatastoreTuning = serde_json::from_value(
>> DatastoreTuning::API_SCHEMA
>> .parse_property_string(datastore.tuning.as_deref().unwrap_or(""))?,
>> )?;
>> - let backup_user = pbs_config::backup_user()?;
>> - let _store = ChunkStore::create(
>> - &datastore.name,
>> - path,
>> - backup_user.uid,
>> - backup_user.gid,
>> - worker,
>> - tuning.sync_level.unwrap_or_default(),
>> - )?;
>> +
>> + if !datastore_empty && !reuse_datastore {
>> + bail!("Directory is not empty!");
>> + } else if !datastore_empty && reuse_datastore {
>> + ChunkStore::verify_chunkstore(&path)?;
>> +
>> + let _store =
>> + ChunkStore::open(&datastore.name, path, tuning.sync_level.unwrap_or_default())?;
>
>(continued from first comment above) and leave out this call here?
>
>> + } else {
>> + let backup_user = pbs_config::backup_user()?;
>> + let _store = ChunkStore::create(
>> + &datastore.name,
>> + path,
>> + backup_user.uid,
>> + backup_user.gid,
>> + worker,
>> + tuning.sync_level.unwrap_or_default(),
>> + )?;
>> + }
>>
>> config.set_data(&datastore.name, "datastore", &datastore)?;
>>
>> @@ -103,6 +128,12 @@ pub(crate) fn do_create_datastore(
>> type: DataStoreConfig,
>> flatten: true,
>> },
>> + "reuse-datastore": {
>> + type: Boolean,
>> + optional: true,
>> + default: false,
>> + description: "Re-use existing datastore directory."
>> + }
>> },
>> },
>> access: {
>> @@ -112,6 +143,7 @@ pub(crate) fn do_create_datastore(
>> /// Create new datastore config.
>> pub fn create_datastore(
>> config: DataStoreConfig,
>> + reuse_datastore: bool,
>> rpcenv: &mut dyn RpcEnvironment,
>> ) -> Result<String, Error> {
>> let lock = pbs_config::datastore::lock_config()?;
>> @@ -156,7 +188,7 @@ pub fn create_datastore(
>> auth_id.to_string(),
>> to_stdout,
>> move |worker| {
>> - do_create_datastore(lock, section_config, config, Some(&worker))?;
>> + do_create_datastore(lock, section_config, config, reuse_datastore, Some(&worker))?;
>>
>> if let Some(prune_job_config) = prune_job_config {
>> do_create_prune_job(prune_job_config, Some(&worker))
>> diff --git a/src/api2/node/disks/directory.rs b/src/api2/node/disks/directory.rs
>> index 9f1112a9..25cfe784 100644
>> --- a/src/api2/node/disks/directory.rs
>> +++ b/src/api2/node/disks/directory.rs
>> @@ -217,6 +217,7 @@ pub fn create_datastore_disk(
>> lock,
>> config,
>> datastore,
>> + false,
>> Some(&worker),
>> )?;
>> }
>> diff --git a/src/api2/node/disks/zfs.rs b/src/api2/node/disks/zfs.rs
>> index 673dc1bf..ad2129fc 100644
>> --- a/src/api2/node/disks/zfs.rs
>> +++ b/src/api2/node/disks/zfs.rs
>> @@ -323,6 +323,7 @@ pub fn create_zpool(
>> lock,
>> config,
>> datastore,
>> + false,
>> Some(&worker),
>> )?;
>> }
>> --
>> 2.43.0
>>
>>
>>
>> _______________________________________________
>> pbs-devel mailing list
>> pbs-devel@lists.proxmox.com
>> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
>>
>>
>>
>
>
>_______________________________________________
>pbs-devel mailing list
>pbs-devel@lists.proxmox.com
>https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
>
>
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [pbs-devel] [PATCH proxmox-backup v2 1/2] fix #5439: allow to reuse existing datastore
2024-07-12 11:57 ` Gabriel Goller
@ 2024-07-15 11:23 ` Fabian Grünbichler
2024-07-18 9:27 ` Gabriel Goller
0 siblings, 1 reply; 10+ messages in thread
From: Fabian Grünbichler @ 2024-07-15 11:23 UTC (permalink / raw)
To: Proxmox Backup Server development discussion
On July 12, 2024 1:57 pm, Gabriel Goller wrote:
> On 10.07.2024 15:28, Fabian Grünbichler wrote:
>>On June 12, 2024 3:22 pm, Gabriel Goller wrote:
>>> Disallow creating datastores in non-empty directories. Allow adding
>>> existing datastores via a 'reuse-datastore' checkmark. This only checks
>>> if all the necessary directories (.chunks + subdirectories and .lock)
>>> are existing and have the correct permissions, then opens the
>>> datastore.
>>>
>>> Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
>>> ---
>>>
>>> v2, thanks @Fabian:
>>> - also check on frontend for root
>>> - forbid datastore creation if dir not empty
>>> - add reuse-datastore option
>>> - verify chunkstore directories permissions and owners
>>>
>>> pbs-datastore/src/chunk_store.rs | 49 ++++++++++++++++++++++++++++-
>>> src/api2/config/datastore.rs | 54 +++++++++++++++++++++++++-------
>>> src/api2/node/disks/directory.rs | 1 +
>>> src/api2/node/disks/zfs.rs | 1 +
>>> 4 files changed, 93 insertions(+), 12 deletions(-)
>>>
>>> diff --git a/pbs-datastore/src/chunk_store.rs b/pbs-datastore/src/chunk_store.rs
>>> index 9f6289c9..077bc316 100644
>>> --- a/pbs-datastore/src/chunk_store.rs
>>> +++ b/pbs-datastore/src/chunk_store.rs
>>> @@ -164,7 +164,7 @@ impl ChunkStore {
>>> /// Note that this must be used with care, as it's dangerous to create two instances on the
>>> /// same base path, as closing the underlying ProcessLocker drops all locks from this process
>>> /// on the lockfile (even if separate FDs)
>>
>>given this ^
this part here was important ;)
>>
>>> - pub(crate) fn open<P: Into<PathBuf>>(
>>> + pub fn open<P: Into<PathBuf>>(
>>
>>wouldn't it make more sense to split out the path checks and call them
>>both in open and in verify_chunkstore below
>
> Why call them both in open and verify_chunkstore? create_datastore
> always calls the verify method before open, so we could move this to
> the verify function? I think I don't understand this correctly...
see below..
>>> name: &str,
>>> base: P,
>>> sync_level: DatastoreFSyncLevel,
>>> @@ -563,6 +563,53 @@ impl ChunkStore {
>>> // unwrap: only `None` in unit tests
>>> ProcessLocker::try_exclusive_lock(self.locker.clone().unwrap())
>>> }
>
>>> +
>>> let tuning: DatastoreTuning = serde_json::from_value(
>>> DatastoreTuning::API_SCHEMA
>>> .parse_property_string(datastore.tuning.as_deref().unwrap_or(""))?,
>>> )?;
>>> - let backup_user = pbs_config::backup_user()?;
>>> - let _store = ChunkStore::create(
>>> - &datastore.name,
>>> - path,
>>> - backup_user.uid,
>>> - backup_user.gid,
>>> - worker,
>>> - tuning.sync_level.unwrap_or_default(),
>>> - )?;
>>> +
>>> + if !datastore_empty && !reuse_datastore {
>>> + bail!("Directory is not empty!");
>>> + } else if !datastore_empty && reuse_datastore {
>>> + ChunkStore::verify_chunkstore(&path)?;
>>> +
>>> + let _store =
>>> + ChunkStore::open(&datastore.name, path, tuning.sync_level.unwrap_or_default())?;
>>
>>(continued from first comment above) and leave out this call here?
this was the continuation ;) if we drop the open call here (to avoid
side-effects via the process locker), then we need the path checks in
verify *and* open. hope that makes sense now :)
>>
>>> + } else {
>>> + let backup_user = pbs_config::backup_user()?;
>>> + let _store = ChunkStore::create(
>>> + &datastore.name,
>>> + path,
>>> + backup_user.uid,
>>> + backup_user.gid,
>>> + worker,
>>> + tuning.sync_level.unwrap_or_default(),
>>> + )?;
>>> + }
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [pbs-devel] [PATCH proxmox-backup v2 1/2] fix #5439: allow to reuse existing datastore
2024-07-15 11:23 ` Fabian Grünbichler
@ 2024-07-18 9:27 ` Gabriel Goller
2024-07-18 11:05 ` Fabian Grünbichler
0 siblings, 1 reply; 10+ messages in thread
From: Gabriel Goller @ 2024-07-18 9:27 UTC (permalink / raw)
To: Proxmox Backup Server development discussion
On 15.07.2024 13:23, Fabian Grünbichler wrote:
>On July 12, 2024 1:57 pm, Gabriel Goller wrote:
>> On 10.07.2024 15:28, Fabian Grünbichler wrote:
>>>On June 12, 2024 3:22 pm, Gabriel Goller wrote:
>>>> Disallow creating datastores in non-empty directories. Allow adding
>>>> existing datastores via a 'reuse-datastore' checkmark. This only checks
>>>> if all the necessary directories (.chunks + subdirectories and .lock)
>>>> are existing and have the correct permissions, then opens the
>>>> datastore.
>>>>
>>>> Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
>>>> ---
>>>>
>>>> v2, thanks @Fabian:
>>>> - also check on frontend for root
>>>> - forbid datastore creation if dir not empty
>>>> - add reuse-datastore option
>>>> - verify chunkstore directories permissions and owners
>>>>
>>>> pbs-datastore/src/chunk_store.rs | 49 ++++++++++++++++++++++++++++-
>>>> src/api2/config/datastore.rs | 54 +++++++++++++++++++++++++-------
>>>> src/api2/node/disks/directory.rs | 1 +
>>>> src/api2/node/disks/zfs.rs | 1 +
>>>> 4 files changed, 93 insertions(+), 12 deletions(-)
>>>>
>>>> diff --git a/pbs-datastore/src/chunk_store.rs b/pbs-datastore/src/chunk_store.rs
>>>> index 9f6289c9..077bc316 100644
>>>> --- a/pbs-datastore/src/chunk_store.rs
>>>> +++ b/pbs-datastore/src/chunk_store.rs
>>>> @@ -164,7 +164,7 @@ impl ChunkStore {
>>>> /// Note that this must be used with care, as it's dangerous to create two instances on the
>>>> /// same base path, as closing the underlying ProcessLocker drops all locks from this process
>>>> /// on the lockfile (even if separate FDs)
>>>
>>>given this ^
>
>this part here was important ;)
>
>>>
>>>> - pub(crate) fn open<P: Into<PathBuf>>(
>>>> + pub fn open<P: Into<PathBuf>>(
>>>
>>>wouldn't it make more sense to split out the path checks and call them
>>>both in open and in verify_chunkstore below
>>
>> Why call them both in open and verify_chunkstore? create_datastore
>> always calls the verify method before open, so we could move this to
>> the verify function? I think I don't understand this correctly...
>
>see below..
>
>>>> name: &str,
>>>> base: P,
>>>> sync_level: DatastoreFSyncLevel,
>>>> @@ -563,6 +563,53 @@ impl ChunkStore {
>>>> // unwrap: only `None` in unit tests
>>>> ProcessLocker::try_exclusive_lock(self.locker.clone().unwrap())
>>>> }
>>
>>>> +
>>>> let tuning: DatastoreTuning = serde_json::from_value(
>>>> DatastoreTuning::API_SCHEMA
>>>> .parse_property_string(datastore.tuning.as_deref().unwrap_or(""))?,
>>>> )?;
>>>> - let backup_user = pbs_config::backup_user()?;
>>>> - let _store = ChunkStore::create(
>>>> - &datastore.name,
>>>> - path,
>>>> - backup_user.uid,
>>>> - backup_user.gid,
>>>> - worker,
>>>> - tuning.sync_level.unwrap_or_default(),
>>>> - )?;
>>>> +
>>>> + if !datastore_empty && !reuse_datastore {
>>>> + bail!("Directory is not empty!");
>>>> + } else if !datastore_empty && reuse_datastore {
>>>> + ChunkStore::verify_chunkstore(&path)?;
>>>> +
>>>> + let _store =
>>>> + ChunkStore::open(&datastore.name, path, tuning.sync_level.unwrap_or_default())?;
>>>
>>>(continued from first comment above) and leave out this call here?
>
>this was the continuation ;) if we drop the open call here (to avoid
>side-effects via the process locker), then we need the path checks in
>verify *and* open. hope that makes sense now :)
>
Ooh, I think I got it...
So basically we could just call `ChunkStore::verify_chunkstore` in
`ChunkStore::open` and drop the `open` call above, right?
>>>
>>>> + } else {
>>>> + let backup_user = pbs_config::backup_user()?;
>>>> + let _store = ChunkStore::create(
>>>> + &datastore.name,
>>>> + path,
>>>> + backup_user.uid,
>>>> + backup_user.gid,
>>>> + worker,
>>>> + tuning.sync_level.unwrap_or_default(),
>>>> + )?;
>>>> + }
>
>
>_______________________________________________
>pbs-devel mailing list
>pbs-devel@lists.proxmox.com
>https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [pbs-devel] [PATCH proxmox-backup v2 1/2] fix #5439: allow to reuse existing datastore
2024-07-18 9:27 ` Gabriel Goller
@ 2024-07-18 11:05 ` Fabian Grünbichler
2024-07-18 12:18 ` Gabriel Goller
0 siblings, 1 reply; 10+ messages in thread
From: Fabian Grünbichler @ 2024-07-18 11:05 UTC (permalink / raw)
To: Proxmox Backup Server development discussion
On July 18, 2024 11:27 am, Gabriel Goller wrote:
> On 15.07.2024 13:23, Fabian Grünbichler wrote:
>>On July 12, 2024 1:57 pm, Gabriel Goller wrote:
>>> On 10.07.2024 15:28, Fabian Grünbichler wrote:
>>>>On June 12, 2024 3:22 pm, Gabriel Goller wrote:
>>>>> Disallow creating datastores in non-empty directories. Allow adding
>>>>> existing datastores via a 'reuse-datastore' checkmark. This only checks
>>>>> if all the necessary directories (.chunks + subdirectories and .lock)
>>>>> are existing and have the correct permissions, then opens the
>>>>> datastore.
>>>>>
>>>>> Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
>>>>> ---
>>>>>
>>>>> v2, thanks @Fabian:
>>>>> - also check on frontend for root
>>>>> - forbid datastore creation if dir not empty
>>>>> - add reuse-datastore option
>>>>> - verify chunkstore directories permissions and owners
>>>>>
>>>>> pbs-datastore/src/chunk_store.rs | 49 ++++++++++++++++++++++++++++-
>>>>> src/api2/config/datastore.rs | 54 +++++++++++++++++++++++++-------
>>>>> src/api2/node/disks/directory.rs | 1 +
>>>>> src/api2/node/disks/zfs.rs | 1 +
>>>>> 4 files changed, 93 insertions(+), 12 deletions(-)
>>>>>
>>>>> diff --git a/pbs-datastore/src/chunk_store.rs b/pbs-datastore/src/chunk_store.rs
>>>>> index 9f6289c9..077bc316 100644
>>>>> --- a/pbs-datastore/src/chunk_store.rs
>>>>> +++ b/pbs-datastore/src/chunk_store.rs
>>>>> @@ -164,7 +164,7 @@ impl ChunkStore {
>>>>> /// Note that this must be used with care, as it's dangerous to create two instances on the
>>>>> /// same base path, as closing the underlying ProcessLocker drops all locks from this process
>>>>> /// on the lockfile (even if separate FDs)
>>>>
>>>>given this ^
>>
>>this part here was important ;)
>>
>>>>
>>>>> - pub(crate) fn open<P: Into<PathBuf>>(
>>>>> + pub fn open<P: Into<PathBuf>>(
>>>>
>>>>wouldn't it make more sense to split out the path checks and call them
>>>>both in open and in verify_chunkstore below
>>>
>>> Why call them both in open and verify_chunkstore? create_datastore
>>> always calls the verify method before open, so we could move this to
>>> the verify function? I think I don't understand this correctly...
>>
>>see below..
>>
>>>>> name: &str,
>>>>> base: P,
>>>>> sync_level: DatastoreFSyncLevel,
>>>>> @@ -563,6 +563,53 @@ impl ChunkStore {
>>>>> // unwrap: only `None` in unit tests
>>>>> ProcessLocker::try_exclusive_lock(self.locker.clone().unwrap())
>>>>> }
>>>
>>>>> +
>>>>> let tuning: DatastoreTuning = serde_json::from_value(
>>>>> DatastoreTuning::API_SCHEMA
>>>>> .parse_property_string(datastore.tuning.as_deref().unwrap_or(""))?,
>>>>> )?;
>>>>> - let backup_user = pbs_config::backup_user()?;
>>>>> - let _store = ChunkStore::create(
>>>>> - &datastore.name,
>>>>> - path,
>>>>> - backup_user.uid,
>>>>> - backup_user.gid,
>>>>> - worker,
>>>>> - tuning.sync_level.unwrap_or_default(),
>>>>> - )?;
>>>>> +
>>>>> + if !datastore_empty && !reuse_datastore {
>>>>> + bail!("Directory is not empty!");
>>>>> + } else if !datastore_empty && reuse_datastore {
>>>>> + ChunkStore::verify_chunkstore(&path)?;
>>>>> +
>>>>> + let _store =
>>>>> + ChunkStore::open(&datastore.name, path, tuning.sync_level.unwrap_or_default())?;
>>>>
>>>>(continued from first comment above) and leave out this call here?
>>
>>this was the continuation ;) if we drop the open call here (to avoid
>>side-effects via the process locker), then we need the path checks in
>>verify *and* open. hope that makes sense now :)
>>
>
> Ooh, I think I got it...
> So basically we could just call `ChunkStore::verify_chunkstore` in
> `ChunkStore::open` and drop the `open` call above, right?
no, what I meant is:
in open, split out the checks there (that base is absolute, and that the
chunk dir is accessible) into a new helper fn. then call that new helper
fn in open (so that the behaviour of open stays the same).
then, also call this helper in your patch here - it doesn't really give
much extra benefit atm, but it makes it less easy to miss that this
should be done in both places when adding new checks.
I don't think we want to do the full verification on each open, but when
creating a datastore config entry with an existing datastore, we want to
do both the extensive checks and those we'd do when opening it.
of course another option would be to have a single helper, and a
parameter that controls whether the extensive checks should be done or
not. even harder to miss that there are two related sets of checks then
;)
>>>>
>>>>> + } else {
>>>>> + let backup_user = pbs_config::backup_user()?;
>>>>> + let _store = ChunkStore::create(
>>>>> + &datastore.name,
>>>>> + path,
>>>>> + backup_user.uid,
>>>>> + backup_user.gid,
>>>>> + worker,
>>>>> + tuning.sync_level.unwrap_or_default(),
>>>>> + )?;
>>>>> + }
>>
>>
>>_______________________________________________
>>pbs-devel mailing list
>>pbs-devel@lists.proxmox.com
>>https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
>
>
> _______________________________________________
> pbs-devel mailing list
> pbs-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
>
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [pbs-devel] [PATCH proxmox-backup v2 1/2] fix #5439: allow to reuse existing datastore
2024-07-18 11:05 ` Fabian Grünbichler
@ 2024-07-18 12:18 ` Gabriel Goller
0 siblings, 0 replies; 10+ messages in thread
From: Gabriel Goller @ 2024-07-18 12:18 UTC (permalink / raw)
To: Proxmox Backup Server development discussion
>>
>> Ooh, I think I got it...
>> So basically we could just call `ChunkStore::verify_chunkstore` in
>> `ChunkStore::open` and drop the `open` call above, right?
>
>no, what I meant is:
>
>in open, split out the checks there (that base is absolute, and that the
>chunk dir is accessible) into a new helper fn. then call that new helper
>fn in open (so that the behaviour of open stays the same).
>
>then, also call this helper in your patch here - it doesn't really give
>much extra benefit atm, but it makes it less easy to miss that this
>should be done in both places when adding new checks.
>
>I don't think we want to do the full verification on each open, but when
>creating a datastore config entry with an existing datastore, we want to
>do both the extensive checks and those we'd do when opening it.
>
>of course another option would be to have a single helper, and a
>parameter that controls whether the extensive checks should be done or
>not. even harder to miss that there are two related sets of checks then
>;)
>
Got it! Didn't have my coffee yet, sorry about that :)
Will submit a new version soon!
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2024-07-30 9:10 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-06-12 13:22 [pbs-devel] [PATCH proxmox-backup v2 1/2] fix #5439: allow to reuse existing datastore Gabriel Goller
2024-06-12 13:22 ` [pbs-devel] [PATCH proxmox-backup v2 2/2] web: disallow datastore in root, add reuse-datastore flag Gabriel Goller
2024-07-22 6:45 ` Thomas Lamprecht
2024-07-30 9:10 ` Gabriel Goller
2024-07-10 13:28 ` [pbs-devel] [PATCH proxmox-backup v2 1/2] fix #5439: allow to reuse existing datastore Fabian Grünbichler
2024-07-12 11:57 ` Gabriel Goller
2024-07-15 11:23 ` Fabian Grünbichler
2024-07-18 9:27 ` Gabriel Goller
2024-07-18 11:05 ` Fabian Grünbichler
2024-07-18 12:18 ` Gabriel Goller
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox