1 use ::serde
::{Deserialize, Serialize}
;
6 use proxmox_router
::{http_bail, Permission, Router, RpcEnvironment}
;
7 use proxmox_schema
::{api, param_bail}
;
10 Authid
, LtoTapeDrive
, ScsiTapeChanger
, ScsiTapeChangerUpdater
, CHANGER_NAME_SCHEMA
,
11 PRIV_TAPE_AUDIT
, PRIV_TAPE_MODIFY
, PROXMOX_CONFIG_DIGEST_SCHEMA
, SLOT_ARRAY_SCHEMA
,
13 use pbs_config
::CachedUserInfo
;
14 use pbs_tape
::linux_list_drives
::{check_drive_path, linux_tape_changer_list}
;
21 type: ScsiTapeChanger
,
27 permission
: &Permission
::Privilege(&["tape", "device"], PRIV_TAPE_MODIFY
, false),
30 /// Create a new changer device
31 pub fn create_changer(config
: ScsiTapeChanger
) -> Result
<(), Error
> {
32 let _lock
= pbs_config
::drive
::lock()?
;
34 let (mut section_config
, _digest
) = pbs_config
::drive
::config()?
;
36 if section_config
.sections
.get(&config
.name
).is_some() {
37 param_bail
!("name", "Entry '{}' already exists", config
.name
);
40 let linux_changers
= linux_tape_changer_list();
42 check_drive_path(&linux_changers
, &config
.path
)?
;
44 let existing
: Vec
<ScsiTapeChanger
> = section_config
.convert_to_typed_array("changer")?
;
46 for changer
in existing
{
47 if changer
.path
== config
.path
{
50 "Path '{}' already in use by '{}'",
57 section_config
.set_data(&config
.name
, "changer", &config
)?
;
59 pbs_config
::drive
::save_config(§ion_config
)?
;
68 schema
: CHANGER_NAME_SCHEMA
,
73 type: ScsiTapeChanger
,
76 permission
: &Permission
::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_AUDIT
, false),
79 /// Get tape changer configuration
83 rpcenv
: &mut dyn RpcEnvironment
,
84 ) -> Result
<ScsiTapeChanger
, Error
> {
85 let (config
, digest
) = pbs_config
::drive
::config()?
;
87 let data
: ScsiTapeChanger
= config
.lookup("changer", &name
)?
;
89 rpcenv
["digest"] = hex
::encode(digest
).into();
99 description
: "The list of configured changers (with config digest).",
102 type: ScsiTapeChanger
,
106 description
: "List configured tape changer filtered by Tape.Audit privileges",
107 permission
: &Permission
::Anybody
,
111 pub fn list_changers(
113 rpcenv
: &mut dyn RpcEnvironment
,
114 ) -> Result
<Vec
<ScsiTapeChanger
>, Error
> {
115 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
116 let user_info
= CachedUserInfo
::new()?
;
118 let (config
, digest
) = pbs_config
::drive
::config()?
;
120 let list
: Vec
<ScsiTapeChanger
> = config
.convert_to_typed_array("changer")?
;
125 let privs
= user_info
.lookup_privs(&auth_id
, &["tape", "device", &changer
.name
]);
126 privs
& PRIV_TAPE_AUDIT
!= 0
130 rpcenv
["digest"] = hex
::encode(digest
).into();
135 #[derive(Serialize, Deserialize)]
136 #[serde(rename_all = "kebab-case")]
137 /// Deletable property name
138 pub enum DeletableProperty
{
139 /// Delete export-slots.
141 /// Delete eject-before-unload.
150 schema
: CHANGER_NAME_SCHEMA
,
153 type: ScsiTapeChangerUpdater
,
157 description
: "List of properties to delete.",
161 type: DeletableProperty
,
165 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
171 permission
: &Permission
::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY
, false),
174 /// Update a tape changer configuration
175 pub fn update_changer(
177 update
: ScsiTapeChangerUpdater
,
178 delete
: Option
<Vec
<DeletableProperty
>>,
179 digest
: Option
<String
>,
181 ) -> Result
<(), Error
> {
182 let _lock
= pbs_config
::drive
::lock()?
;
184 let (mut config
, expected_digest
) = pbs_config
::drive
::config()?
;
186 if let Some(ref digest
) = digest
{
187 let digest
= <[u8; 32]>::from_hex(digest
)?
;
188 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
191 let mut data
: ScsiTapeChanger
= config
.lookup("changer", &name
)?
;
193 if let Some(delete
) = delete
{
194 for delete_prop
in delete
{
196 DeletableProperty
::ExportSlots
=> {
197 data
.export_slots
= None
;
199 DeletableProperty
::EjectBeforeUnload
=> {
200 data
.eject_before_unload
= None
;
206 if let Some(path
) = update
.path
{
207 let changers
= linux_tape_changer_list();
208 check_drive_path(&changers
, &path
)?
;
212 if let Some(export_slots
) = update
.export_slots
{
213 let slots
: Value
= SLOT_ARRAY_SCHEMA
.parse_property_string(&export_slots
)?
;
214 let mut slots
: Vec
<String
> = slots
218 .map(|v
| v
.to_string())
222 if slots
.is_empty() {
223 data
.export_slots
= None
;
225 let slots
= slots
.join(",");
226 data
.export_slots
= Some(slots
);
230 if let Some(eject_before_unload
) = update
.eject_before_unload
{
231 data
.eject_before_unload
= Some(eject_before_unload
);
234 config
.set_data(&name
, "changer", &data
)?
;
236 pbs_config
::drive
::save_config(&config
)?
;
246 schema
: CHANGER_NAME_SCHEMA
,
251 permission
: &Permission
::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY
, false),
254 /// Delete a tape changer configuration
255 pub fn delete_changer(name
: String
, _param
: Value
) -> Result
<(), Error
> {
256 let _lock
= pbs_config
::drive
::lock()?
;
258 let (mut config
, _digest
) = pbs_config
::drive
::config()?
;
260 match config
.sections
.get(&name
) {
261 Some((section_type
, _
)) => {
262 if section_type
!= "changer" {
265 "Entry '{}' exists, but is not a changer device",
269 config
.sections
.remove(&name
);
273 "Delete changer '{}' failed - no such entry",
278 let drive_list
: Vec
<LtoTapeDrive
> = config
.convert_to_typed_array("lto")?
;
279 for drive
in drive_list
{
280 if let Some(changer
) = drive
.changer
{
284 "Delete changer '{}' failed - used by drive '{}'",
292 pbs_config
::drive
::save_config(&config
)?
;
297 const ITEM_ROUTER
: Router
= Router
::new()
298 .get(&API_METHOD_GET_CONFIG
)
299 .put(&API_METHOD_UPDATE_CHANGER
)
300 .delete(&API_METHOD_DELETE_CHANGER
);
302 pub const ROUTER
: Router
= Router
::new()
303 .get(&API_METHOD_LIST_CHANGERS
)
304 .post(&API_METHOD_CREATE_CHANGER
)
305 .match_all("name", &ITEM_ROUTER
);