public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [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 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

* 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

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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal