1 use anyhow
::{bail, Error}
;
3 use ::serde
::{Deserialize, Serialize}
;
5 use proxmox_router
::{Router, RpcEnvironment, RpcEnvironmentType, Permission}
;
6 use proxmox_schema
::api
;
7 use proxmox_section_config
::SectionConfigData
;
8 use proxmox_sys
::task_log
;
11 DataStoreConfig
, NODE_SCHEMA
, BLOCKDEVICE_NAME_SCHEMA
,
12 DATASTORE_SCHEMA
, UPID_SCHEMA
, PRIV_SYS_AUDIT
, PRIV_SYS_MODIFY
,
15 use crate::tools
::disks
::{
16 DiskManage
, FileSystemType
, DiskUsageType
,
17 create_file_system
, create_single_linux_partition
, get_fs_uuid
, get_disk_usage_info
,
19 use crate::tools
::systemd
::{self, types::*}
;
21 use proxmox_rest_server
::WorkerTask
;
23 const BASE_MOUNT_DIR
: &str = "/mnt/datastore/";
33 #[derive(Debug, Serialize, Deserialize)]
34 #[serde(rename_all="kebab-case")]
35 /// Datastore mount info.
36 pub struct DatastoreMountInfo
{
37 /// The path of the mount unit.
39 /// The name of the mount
43 /// The mounted device.
46 pub filesystem
: Option
<String
>,
48 pub options
: Option
<String
>,
61 description
: "List of systemd datastore mount units.",
64 type: DatastoreMountInfo
,
68 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_AUDIT
, false),
71 /// List systemd datastore mount units.
72 pub fn list_datastore_mounts() -> Result
<Vec
<DatastoreMountInfo
>, Error
> {
74 lazy_static
::lazy_static
! {
75 static ref MOUNT_NAME_REGEX
: regex
::Regex
= regex
::Regex
::new(r
"^mnt-datastore-(.+)\.mount$").unwrap();
78 let mut list
= Vec
::new();
80 let basedir
= "/etc/systemd/system";
81 for item
in proxmox_sys
::fs
::scan_subdir(libc
::AT_FDCWD
, basedir
, &MOUNT_NAME_REGEX
)?
{
83 let name
= item
.file_name().to_string_lossy().to_string();
85 let unitfile
= format
!("{}/{}", basedir
, name
);
86 let config
= systemd
::config
::parse_systemd_mount(&unitfile
)?
;
87 let data
: SystemdMountSection
= config
.lookup("Mount", "Mount")?
;
91 .strip_prefix(BASE_MOUNT_DIR
)
92 .unwrap_or_else(|| &data
.Where
)
95 list
.push(DatastoreMountInfo
{
100 filesystem
: data
.Type
,
101 options
: data
.Options
,
116 schema
: DATASTORE_SCHEMA
,
119 schema
: BLOCKDEVICE_NAME_SCHEMA
,
122 description
: "Configure a datastore using the directory.",
127 type: FileSystemType
,
136 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_MODIFY
, false),
139 /// Create a Filesystem on an unused disk. Will be mounted under '/mnt/datastore/<name>'.".
140 pub fn create_datastore_disk(
143 add_datastore
: Option
<bool
>,
144 filesystem
: Option
<FileSystemType
>,
145 rpcenv
: &mut dyn RpcEnvironment
,
146 ) -> Result
<String
, Error
> {
148 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
150 let auth_id
= rpcenv
.get_auth_id().unwrap();
152 let info
= get_disk_usage_info(&disk
, true)?
;
154 if info
.used
!= DiskUsageType
::Unused
{
155 bail
!("disk '{}' is already in use.", disk
);
158 let mount_point
= format
!("{}{}", BASE_MOUNT_DIR
, &name
);
160 // check if the default path does exist already and bail if it does
161 let default_path
= std
::path
::PathBuf
::from(&mount_point
);
163 match std
::fs
::metadata(&default_path
) {
164 Err(_
) => {}
, // path does not exist
166 bail
!("path {:?} already exists", default_path
);
170 let upid_str
= WorkerTask
::new_thread(
171 "dircreate", Some(name
.clone()), auth_id
, to_stdout
, move |worker
|
173 task_log
!(worker
, "create datastore '{}' on disk {}", name
, disk
);
175 let add_datastore
= add_datastore
.unwrap_or(false);
176 let filesystem
= filesystem
.unwrap_or(FileSystemType
::Ext4
);
178 let manager
= DiskManage
::new();
180 let disk
= manager
.disk_by_name(&disk
)?
;
182 let partition
= create_single_linux_partition(&disk
)?
;
183 create_file_system(&partition
, filesystem
)?
;
185 let uuid
= get_fs_uuid(&partition
)?
;
186 let uuid_path
= format
!("/dev/disk/by-uuid/{}", uuid
);
188 let mount_unit_name
= create_datastore_mount_unit(&name
, &mount_point
, filesystem
, &uuid_path
)?
;
190 crate::tools
::systemd
::reload_daemon()?
;
191 crate::tools
::systemd
::enable_unit(&mount_unit_name
)?
;
192 crate::tools
::systemd
::start_unit(&mount_unit_name
)?
;
195 let lock
= pbs_config
::datastore
::lock_config()?
;
196 let datastore
: DataStoreConfig
=
197 serde_json
::from_value(json
!({ "name": name, "path": mount_point }
))?
;
199 let (config
, _digest
) = pbs_config
::datastore
::config()?
;
201 if config
.sections
.get(&datastore
.name
).is_some() {
202 bail
!("datastore '{}' already exists.", datastore
.name
);
205 crate::api2
::config
::datastore
::do_create_datastore(lock
, config
, datastore
, Some(&worker
))?
;
222 schema
: DATASTORE_SCHEMA
,
227 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_MODIFY
, false),
230 /// Remove a Filesystem mounted under '/mnt/datastore/<name>'.".
231 pub fn delete_datastore_disk(name
: String
) -> Result
<(), Error
> {
233 let path
= format
!("{}{}", BASE_MOUNT_DIR
, name
);
234 // path of datastore cannot be changed
235 let (config
, _
) = pbs_config
::datastore
::config()?
;
236 let datastores
: Vec
<DataStoreConfig
> = config
.convert_to_typed_array("datastore")?
;
237 let conflicting_datastore
: Option
<DataStoreConfig
> = datastores
.into_iter()
238 .find(|ds
| ds
.path
== path
);
240 if let Some(conflicting_datastore
) = conflicting_datastore
{
241 bail
!("Can't remove '{}' since it's required by datastore '{}'",
242 conflicting_datastore
.path
, conflicting_datastore
.name
);
245 // disable systemd mount-unit
246 let mut mount_unit_name
= proxmox_sys
::systemd
::escape_unit(&path
, true);
247 mount_unit_name
.push_str(".mount");
248 crate::tools
::systemd
::disable_unit(&mount_unit_name
)?
;
250 // delete .mount-file
251 let mount_unit_path
= format
!("/etc/systemd/system/{}", mount_unit_name
);
252 let full_path
= std
::path
::Path
::new(&mount_unit_path
);
253 log
::info
!("removing systemd mount unit {:?}", full_path
);
254 std
::fs
::remove_file(&full_path
)?
;
256 // try to unmount, if that fails tell the user to reboot or unmount manually
257 let mut command
= std
::process
::Command
::new("umount");
259 match proxmox_sys
::command
::run_command(command
, None
) {
261 "Could not umount '{}' since it is busy. It will stay mounted \
262 until the next reboot or until unmounted manually!",
269 const ITEM_ROUTER
: Router
= Router
::new()
270 .delete(&API_METHOD_DELETE_DATASTORE_DISK
);
272 pub const ROUTER
: Router
= Router
::new()
273 .get(&API_METHOD_LIST_DATASTORE_MOUNTS
)
274 .post(&API_METHOD_CREATE_DATASTORE_DISK
)
275 .match_all("name", &ITEM_ROUTER
);
278 fn create_datastore_mount_unit(
279 datastore_name
: &str,
281 fs_type
: FileSystemType
,
283 ) -> Result
<String
, Error
> {
285 let mut mount_unit_name
= proxmox_sys
::systemd
::escape_unit(mount_point
, true);
286 mount_unit_name
.push_str(".mount");
288 let mount_unit_path
= format
!("/etc/systemd/system/{}", mount_unit_name
);
290 let unit
= SystemdUnitSection
{
291 Description
: format
!("Mount datatstore '{}' under '{}'", datastore_name
, mount_point
),
295 let install
= SystemdInstallSection
{
296 WantedBy
: Some(vec
!["multi-user.target".to_string()]),
300 let mount
= SystemdMountSection
{
301 What
: what
.to_string(),
302 Where
: mount_point
.to_string(),
303 Type
: Some(fs_type
.to_string()),
304 Options
: Some(String
::from("defaults")),
308 let mut config
= SectionConfigData
::new();
309 config
.set_data("Unit", "Unit", unit
)?
;
310 config
.set_data("Install", "Install", install
)?
;
311 config
.set_data("Mount", "Mount", mount
)?
;
313 systemd
::config
::save_systemd_mount(&mount_unit_path
, &config
)?
;