1 use anyhow
::{bail, Error}
;
3 use ::serde
::{Deserialize, Serialize}
;
5 use proxmox
::api
::{api, Permission, RpcEnvironment, RpcEnvironmentType}
;
6 use proxmox
::api
::section_config
::SectionConfigData
;
7 use proxmox
::api
::router
::Router
;
9 use crate::config
::acl
::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}
;
10 use crate::tools
::disks
::{
11 DiskManage
, FileSystemType
, DiskUsageType
,
12 create_file_system
, create_single_linux_partition
, get_fs_uuid
, get_disk_usage_info
,
14 use crate::tools
::systemd
::{self, types::*}
;
16 use crate::server
::WorkerTask
;
18 use crate::api2
::types
::*;
19 use crate::config
::datastore
::{self, DataStoreConfig}
;
20 use crate::backup
::open_backup_lockfile
;
30 #[derive(Debug, Serialize, Deserialize)]
31 #[serde(rename_all="kebab-case")]
32 /// Datastore mount info.
33 pub struct DatastoreMountInfo
{
34 /// The path of the mount unit.
38 /// The mounted device.
41 pub filesystem
: Option
<String
>,
43 pub options
: Option
<String
>,
56 description
: "List of systemd datastore mount units.",
59 type: DatastoreMountInfo
,
63 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_AUDIT
, false),
66 /// List systemd datastore mount units.
67 pub fn list_datastore_mounts() -> Result
<Vec
<DatastoreMountInfo
>, Error
> {
69 lazy_static
::lazy_static
! {
70 static ref MOUNT_NAME_REGEX
: regex
::Regex
= regex
::Regex
::new(r
"^mnt-datastore-(.+)\.mount$").unwrap();
73 let mut list
= Vec
::new();
75 let basedir
= "/etc/systemd/system";
76 for item
in pbs_tools
::fs
::scan_subdir(libc
::AT_FDCWD
, basedir
, &MOUNT_NAME_REGEX
)?
{
78 let name
= item
.file_name().to_string_lossy().to_string();
80 let unitfile
= format
!("{}/{}", basedir
, name
);
81 let config
= systemd
::config
::parse_systemd_mount(&unitfile
)?
;
82 let data
: SystemdMountSection
= config
.lookup("Mount", "Mount")?
;
84 list
.push(DatastoreMountInfo
{
88 filesystem
: data
.Type
,
89 options
: data
.Options
,
104 schema
: DATASTORE_SCHEMA
,
107 schema
: BLOCKDEVICE_NAME_SCHEMA
,
110 description
: "Configure a datastore using the directory.",
115 type: FileSystemType
,
124 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_MODIFY
, false),
127 /// Create a Filesystem on an unused disk. Will be mounted under '/mnt/datastore/<name>'.".
128 pub fn create_datastore_disk(
131 add_datastore
: Option
<bool
>,
132 filesystem
: Option
<FileSystemType
>,
133 rpcenv
: &mut dyn RpcEnvironment
,
134 ) -> Result
<String
, Error
> {
136 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
138 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
140 let info
= get_disk_usage_info(&disk
, true)?
;
142 if info
.used
!= DiskUsageType
::Unused
{
143 bail
!("disk '{}' is already in use.", disk
);
146 let mount_point
= format
!("/mnt/datastore/{}", &name
);
148 // check if the default path does exist already and bail if it does
149 let default_path
= std
::path
::PathBuf
::from(&mount_point
);
151 match std
::fs
::metadata(&default_path
) {
152 Err(_
) => {}
, // path does not exist
154 bail
!("path {:?} already exists", default_path
);
158 let upid_str
= WorkerTask
::new_thread(
159 "dircreate", Some(name
.clone()), auth_id
, to_stdout
, move |worker
|
161 worker
.log(format
!("create datastore '{}' on disk {}", name
, disk
));
163 let add_datastore
= add_datastore
.unwrap_or(false);
164 let filesystem
= filesystem
.unwrap_or(FileSystemType
::Ext4
);
166 let manager
= DiskManage
::new();
168 let disk
= manager
.disk_by_name(&disk
)?
;
170 let partition
= create_single_linux_partition(&disk
)?
;
171 create_file_system(&partition
, filesystem
)?
;
173 let uuid
= get_fs_uuid(&partition
)?
;
174 let uuid_path
= format
!("/dev/disk/by-uuid/{}", uuid
);
176 let mount_unit_name
= create_datastore_mount_unit(&name
, &mount_point
, filesystem
, &uuid_path
)?
;
178 pbs_systemd
::reload_daemon()?
;
179 pbs_systemd
::enable_unit(&mount_unit_name
)?
;
180 pbs_systemd
::start_unit(&mount_unit_name
)?
;
183 let lock
= open_backup_lockfile(datastore
::DATASTORE_CFG_LOCKFILE
, None
, true)?
;
184 let datastore
: DataStoreConfig
=
185 serde_json
::from_value(json
!({ "name": name, "path": mount_point }
))?
;
187 let (config
, _digest
) = datastore
::config()?
;
189 if config
.sections
.get(&datastore
.name
).is_some() {
190 bail
!("datastore '{}' already exists.", datastore
.name
);
193 crate::api2
::config
::datastore
::do_create_datastore(lock
, config
, datastore
, Some(&worker
))?
;
210 schema
: DATASTORE_SCHEMA
,
215 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_MODIFY
, false),
218 /// Remove a Filesystem mounted under '/mnt/datastore/<name>'.".
219 pub fn delete_datastore_disk(name
: String
) -> Result
<(), Error
> {
221 let path
= format
!("/mnt/datastore/{}", name
);
222 // path of datastore cannot be changed
223 let (config
, _
) = crate::config
::datastore
::config()?
;
224 let datastores
: Vec
<DataStoreConfig
> = config
.convert_to_typed_array("datastore")?
;
225 let conflicting_datastore
: Option
<DataStoreConfig
> = datastores
.into_iter()
226 .find(|ds
| ds
.path
== path
);
228 if let Some(conflicting_datastore
) = conflicting_datastore
{
229 bail
!("Can't remove '{}' since it's required by datastore '{}'",
230 conflicting_datastore
.path
, conflicting_datastore
.name
);
233 // disable systemd mount-unit
234 let mut mount_unit_name
= pbs_systemd
::escape_unit(&path
, true);
235 mount_unit_name
.push_str(".mount");
236 pbs_systemd
::disable_unit(&mount_unit_name
)?
;
238 // delete .mount-file
239 let mount_unit_path
= format
!("/etc/systemd/system/{}", mount_unit_name
);
240 let full_path
= std
::path
::Path
::new(&mount_unit_path
);
241 log
::info
!("removing systemd mount unit {:?}", full_path
);
242 std
::fs
::remove_file(&full_path
)?
;
244 // try to unmount, if that fails tell the user to reboot or unmount manually
245 let mut command
= std
::process
::Command
::new("umount");
247 match pbs_tools
::run_command(command
, None
) {
249 "Could not umount '{}' since it is busy. It will stay mounted \
250 until the next reboot or until unmounted manually!",
257 const ITEM_ROUTER
: Router
= Router
::new()
258 .delete(&API_METHOD_DELETE_DATASTORE_DISK
);
260 pub const ROUTER
: Router
= Router
::new()
261 .get(&API_METHOD_LIST_DATASTORE_MOUNTS
)
262 .post(&API_METHOD_CREATE_DATASTORE_DISK
)
263 .match_all("name", &ITEM_ROUTER
);
266 fn create_datastore_mount_unit(
267 datastore_name
: &str,
269 fs_type
: FileSystemType
,
271 ) -> Result
<String
, Error
> {
273 let mut mount_unit_name
= pbs_systemd
::escape_unit(&mount_point
, true);
274 mount_unit_name
.push_str(".mount");
276 let mount_unit_path
= format
!("/etc/systemd/system/{}", mount_unit_name
);
278 let unit
= SystemdUnitSection
{
279 Description
: format
!("Mount datatstore '{}' under '{}'", datastore_name
, mount_point
),
283 let install
= SystemdInstallSection
{
284 WantedBy
: Some(vec
!["multi-user.target".to_string()]),
288 let mount
= SystemdMountSection
{
289 What
: what
.to_string(),
290 Where
: mount_point
.to_string(),
291 Type
: Some(fs_type
.to_string()),
292 Options
: Some(String
::from("defaults")),
296 let mut config
= SectionConfigData
::new();
297 config
.set_data("Unit", "Unit", unit
)?
;
298 config
.set_data("Install", "Install", install
)?
;
299 config
.set_data("Mount", "Mount", mount
)?
;
301 systemd
::config
::save_systemd_mount(&mount_unit_path
, &config
)?
;