]> git.proxmox.com Git - proxmox-backup.git/blobdiff - src/api2/config/changer.rs
api: tape: don't allow overwriting of ids in changer/drive config
[proxmox-backup.git] / src / api2 / config / changer.rs
index 8404d20b47f14b210493e0fd1396631e801ac135..912866973e0ef840ed70d80039d978b7c36e7026 100644 (file)
@@ -1,85 +1,62 @@
-use anyhow::{bail, Error};
 use ::serde::{Deserialize, Serialize};
+use anyhow::Error;
+use hex::FromHex;
 use serde_json::Value;
 
-use proxmox::api::{
-    api,
-    Router,
-    RpcEnvironment,
-    schema::parse_property_string,
-};
+use proxmox_router::{http_bail, Permission, Router, RpcEnvironment};
+use proxmox_schema::{api, param_bail};
 
-use crate::{
-    config,
-    api2::types::{
-        PROXMOX_CONFIG_DIGEST_SCHEMA,
-        CHANGER_NAME_SCHEMA,
-        LINUX_DRIVE_PATH_SCHEMA,
-        SLOT_ARRAY_SCHEMA,
-        EXPORT_SLOT_LIST_SCHEMA,
-        DriveListEntry,
-        ScsiTapeChanger,
-        LinuxTapeDrive,
-    },
-    tape::{
-        linux_tape_changer_list,
-        check_drive_path,
-    },
+use pbs_api_types::{
+    Authid, LtoTapeDrive, ScsiTapeChanger, ScsiTapeChangerUpdater, CHANGER_NAME_SCHEMA,
+    PRIV_TAPE_AUDIT, PRIV_TAPE_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA, SLOT_ARRAY_SCHEMA,
 };
+use pbs_config::CachedUserInfo;
+use pbs_tape::linux_list_drives::{check_drive_path, linux_tape_changer_list};
 
 #[api(
     protected: true,
     input: {
         properties: {
-            name: {
-                schema: CHANGER_NAME_SCHEMA,
-            },
-            path: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
-            },
-            "export-slots": {
-                schema: EXPORT_SLOT_LIST_SCHEMA,
-                optional: true,
+            config: {
+                type: ScsiTapeChanger,
+                flatten: true,
             },
         },
     },
+    access: {
+        permission: &Permission::Privilege(&["tape", "device"], PRIV_TAPE_MODIFY, false),
+    },
 )]
 /// Create a new changer device
-pub fn create_changer(
-    name: String,
-    path: String,
-    export_slots: Option<String>,
-) -> Result<(), Error> {
+pub fn create_changer(config: ScsiTapeChanger) -> Result<(), Error> {
+    let _lock = pbs_config::drive::lock()?;
 
-    let _lock = config::drive::lock()?;
+    let (mut section_config, _digest) = pbs_config::drive::config()?;
 
-    let (mut config, _digest) = config::drive::config()?;
+    if section_config.sections.get(&config.name).is_some() {
+        param_bail!("name", "Entry '{}' already exists", config.name);
+    }
 
     let linux_changers = linux_tape_changer_list();
 
-    check_drive_path(&linux_changers, &path)?;
+    check_drive_path(&linux_changers, &config.path)?;
 
-    let existing: Vec<ScsiTapeChanger> = config.convert_to_typed_array("changer")?;
+    let existing: Vec<ScsiTapeChanger> = section_config.convert_to_typed_array("changer")?;
 
     for changer in existing {
-        if changer.name == name {
-            bail!("Entry '{}' already exists", name);
-        }
-
-        if changer.path == path {
-            bail!("Path '{}' already in use by '{}'", path, changer.name);
+        if changer.path == config.path {
+            param_bail!(
+                "path",
+                "Path '{}' already in use by '{}'",
+                config.path,
+                changer.name
+            );
         }
     }
 
-    let item = ScsiTapeChanger {
-        name: name.clone(),
-        path,
-        export_slots,
-    };
-
-    config.set_data(&name, "changer", &item)?;
+    section_config.set_data(&config.name, "changer", &config)?;
 
-    config::drive::save_config(&config)?;
+    pbs_config::drive::save_config(&section_config)?;
 
     Ok(())
 }
@@ -95,20 +72,21 @@ pub fn create_changer(
     returns: {
         type: ScsiTapeChanger,
     },
-
+    access: {
+        permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_AUDIT, false),
+    },
 )]
 /// Get tape changer configuration
 pub fn get_config(
     name: String,
     _param: Value,
-    mut rpcenv: &mut dyn RpcEnvironment,
+    rpcenv: &mut dyn RpcEnvironment,
 ) -> Result<ScsiTapeChanger, Error> {
-
-    let (config, digest) = config::drive::config()?;
+    let (config, digest) = pbs_config::drive::config()?;
 
     let data: ScsiTapeChanger = config.lookup("changer", &name)?;
 
-    rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
+    rpcenv["digest"] = hex::encode(digest).into();
 
     Ok(data)
 }
@@ -121,45 +99,47 @@ pub fn get_config(
         description: "The list of configured changers (with config digest).",
         type: Array,
         items: {
-            type: DriveListEntry,
+            type: ScsiTapeChanger,
         },
     },
+    access: {
+        description: "List configured tape changer filtered by Tape.Audit privileges",
+        permission: &Permission::Anybody,
+    },
 )]
 /// List changers
 pub fn list_changers(
     _param: Value,
-    mut rpcenv: &mut dyn RpcEnvironment,
-) -> Result<Vec<DriveListEntry>, Error> {
+    rpcenv: &mut dyn RpcEnvironment,
+) -> Result<Vec<ScsiTapeChanger>, Error> {
+    let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
+    let user_info = CachedUserInfo::new()?;
 
-    let (config, digest) = config::drive::config()?;
+    let (config, digest) = pbs_config::drive::config()?;
 
-    let changer_list: Vec<ScsiTapeChanger> = config.convert_to_typed_array("changer")?;
+    let list: Vec<ScsiTapeChanger> = config.convert_to_typed_array("changer")?;
 
-    let mut list = Vec::new();
+    let list = list
+        .into_iter()
+        .filter(|changer| {
+            let privs = user_info.lookup_privs(&auth_id, &["tape", "device", &changer.name]);
+            privs & PRIV_TAPE_AUDIT != 0
+        })
+        .collect();
 
-    for changer in changer_list {
-        list.push(DriveListEntry {
-            name: changer.name,
-            path: changer.path.clone(),
-            changer: None,
-            changer_drivenum: None,
-            vendor: None,
-            model: None,
-            serial: None,
-        });
-    }
+    rpcenv["digest"] = hex::encode(digest).into();
 
-    rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
     Ok(list)
 }
 #[api()]
 #[derive(Serialize, Deserialize)]
-#[allow(non_camel_case_types)]
 #[serde(rename_all = "kebab-case")]
 /// Deletable property name
 pub enum DeletableProperty {
     /// Delete export-slots.
-    export_slots,
+    ExportSlots,
+    /// Delete eject-before-unload.
+    EjectBeforeUnload,
 }
 
 #[api(
@@ -169,13 +149,9 @@ pub enum DeletableProperty {
             name: {
                 schema: CHANGER_NAME_SCHEMA,
             },
-            path: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
-                optional: true,
-            },
-            "export-slots": {
-                schema: EXPORT_SLOT_LIST_SCHEMA,
-                optional: true,
+            update: {
+                type: ScsiTapeChangerUpdater,
+                flatten: true,
             },
             delete: {
                 description: "List of properties to delete.",
@@ -191,23 +167,24 @@ pub enum DeletableProperty {
             },
          },
     },
+    access: {
+        permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY, false),
+    },
 )]
 /// Update a tape changer configuration
 pub fn update_changer(
     name: String,
-    path: Option<String>,
-    export_slots: Option<String>,
+    update: ScsiTapeChangerUpdater,
     delete: Option<Vec<DeletableProperty>>,
     digest: Option<String>,
     _param: Value,
 ) -> Result<(), Error> {
+    let _lock = pbs_config::drive::lock()?;
 
-    let _lock = config::drive::lock()?;
-
-    let (mut config, expected_digest) = config::drive::config()?;
+    let (mut config, expected_digest) = pbs_config::drive::config()?;
 
     if let Some(ref digest) = digest {
-        let digest = proxmox::tools::hex_to_digest(digest)?;
+        let digest = <[u8; 32]>::from_hex(digest)?;
         crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
     }
 
@@ -216,23 +193,24 @@ pub fn update_changer(
     if let Some(delete) = delete {
         for delete_prop in delete {
             match delete_prop {
-                DeletableProperty::export_slots => {
+                DeletableProperty::ExportSlots => {
                     data.export_slots = None;
                 }
+                DeletableProperty::EjectBeforeUnload => {
+                    data.eject_before_unload = None;
+                }
             }
         }
     }
 
-    if let Some(path) = path {
+    if let Some(path) = update.path {
         let changers = linux_tape_changer_list();
         check_drive_path(&changers, &path)?;
         data.path = path;
     }
 
-    if let Some(export_slots) = export_slots {
-        let slots: Value = parse_property_string(
-            &export_slots, &SLOT_ARRAY_SCHEMA
-        )?;
+    if let Some(export_slots) = update.export_slots {
+        let slots: Value = SLOT_ARRAY_SCHEMA.parse_property_string(&export_slots)?;
         let mut slots: Vec<String> = slots
             .as_array()
             .unwrap()
@@ -249,9 +227,13 @@ pub fn update_changer(
         }
     }
 
+    if let Some(eject_before_unload) = update.eject_before_unload {
+        data.eject_before_unload = Some(eject_before_unload);
+    }
+
     config.set_data(&name, "changer", &data)?;
 
-    config::drive::save_config(&config)?;
+    pbs_config::drive::save_config(&config)?;
 
     Ok(())
 }
@@ -265,34 +247,49 @@ pub fn update_changer(
             },
         },
     },
+    access: {
+        permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY, false),
+    },
 )]
 /// Delete a tape changer configuration
 pub fn delete_changer(name: String, _param: Value) -> Result<(), Error> {
+    let _lock = pbs_config::drive::lock()?;
 
-    let _lock = config::drive::lock()?;
-
-    let (mut config, _digest) = config::drive::config()?;
+    let (mut config, _digest) = pbs_config::drive::config()?;
 
     match config.sections.get(&name) {
         Some((section_type, _)) => {
             if section_type != "changer" {
-                bail!("Entry '{}' exists, but is not a changer device", name);
+                param_bail!(
+                    "name",
+                    "Entry '{}' exists, but is not a changer device",
+                    name
+                );
             }
             config.sections.remove(&name);
-        },
-        None => bail!("Delete changer '{}' failed - no such entry", name),
+        }
+        None => http_bail!(
+            NOT_FOUND,
+            "Delete changer '{}' failed - no such entry",
+            name
+        ),
     }
 
-    let drive_list: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
+    let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
     for drive in drive_list {
         if let Some(changer) = drive.changer {
             if changer == name {
-                bail!("Delete changer '{}' failed - used by drive '{}'", name, drive.name);
+                param_bail!(
+                    "name",
+                    "Delete changer '{}' failed - used by drive '{}'",
+                    name,
+                    drive.name
+                );
             }
         }
     }
 
-    config::drive::save_config(&config)?;
+    pbs_config::drive::save_config(&config)?;
 
     Ok(())
 }
@@ -302,7 +299,6 @@ const ITEM_ROUTER: Router = Router::new()
     .put(&API_METHOD_UPDATE_CHANGER)
     .delete(&API_METHOD_DELETE_CHANGER);
 
-
 pub const ROUTER: Router = Router::new()
     .get(&API_METHOD_LIST_CHANGERS)
     .post(&API_METHOD_CREATE_CHANGER)