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
;
10 Authid
, NODE_SCHEMA
, BLOCKDEVICE_NAME_SCHEMA
, DATASTORE_SCHEMA
, UPID_SCHEMA
,
11 PRIV_SYS_AUDIT
, PRIV_SYS_MODIFY
,
14 use crate::tools
::disks
::{
15 DiskManage
, FileSystemType
, DiskUsageType
,
16 create_file_system
, create_single_linux_partition
, get_fs_uuid
, get_disk_usage_info
,
18 use crate::tools
::systemd
::{self, types::*}
;
20 use crate::server
::WorkerTask
;
22 use crate::config
::datastore
::{self, DataStoreConfig}
;
23 use pbs_config
::open_backup_lockfile
;
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.
41 /// The mounted device.
44 pub filesystem
: Option
<String
>,
46 pub options
: Option
<String
>,
59 description
: "List of systemd datastore mount units.",
62 type: DatastoreMountInfo
,
66 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_AUDIT
, false),
69 /// List systemd datastore mount units.
70 pub fn list_datastore_mounts() -> Result
<Vec
<DatastoreMountInfo
>, Error
> {
72 lazy_static
::lazy_static
! {
73 static ref MOUNT_NAME_REGEX
: regex
::Regex
= regex
::Regex
::new(r
"^mnt-datastore-(.+)\.mount$").unwrap();
76 let mut list
= Vec
::new();
78 let basedir
= "/etc/systemd/system";
79 for item
in pbs_tools
::fs
::scan_subdir(libc
::AT_FDCWD
, basedir
, &MOUNT_NAME_REGEX
)?
{
81 let name
= item
.file_name().to_string_lossy().to_string();
83 let unitfile
= format
!("{}/{}", basedir
, name
);
84 let config
= systemd
::config
::parse_systemd_mount(&unitfile
)?
;
85 let data
: SystemdMountSection
= config
.lookup("Mount", "Mount")?
;
87 list
.push(DatastoreMountInfo
{
91 filesystem
: data
.Type
,
92 options
: data
.Options
,
107 schema
: DATASTORE_SCHEMA
,
110 schema
: BLOCKDEVICE_NAME_SCHEMA
,
113 description
: "Configure a datastore using the directory.",
118 type: FileSystemType
,
127 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_MODIFY
, false),
130 /// Create a Filesystem on an unused disk. Will be mounted under '/mnt/datastore/<name>'.".
131 pub fn create_datastore_disk(
134 add_datastore
: Option
<bool
>,
135 filesystem
: Option
<FileSystemType
>,
136 rpcenv
: &mut dyn RpcEnvironment
,
137 ) -> Result
<String
, Error
> {
139 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
141 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
143 let info
= get_disk_usage_info(&disk
, true)?
;
145 if info
.used
!= DiskUsageType
::Unused
{
146 bail
!("disk '{}' is already in use.", disk
);
149 let mount_point
= format
!("/mnt/datastore/{}", &name
);
151 // check if the default path does exist already and bail if it does
152 let default_path
= std
::path
::PathBuf
::from(&mount_point
);
154 match std
::fs
::metadata(&default_path
) {
155 Err(_
) => {}
, // path does not exist
157 bail
!("path {:?} already exists", default_path
);
161 let upid_str
= WorkerTask
::new_thread(
162 "dircreate", Some(name
.clone()), auth_id
, to_stdout
, move |worker
|
164 worker
.log(format
!("create datastore '{}' on disk {}", name
, disk
));
166 let add_datastore
= add_datastore
.unwrap_or(false);
167 let filesystem
= filesystem
.unwrap_or(FileSystemType
::Ext4
);
169 let manager
= DiskManage
::new();
171 let disk
= manager
.disk_by_name(&disk
)?
;
173 let partition
= create_single_linux_partition(&disk
)?
;
174 create_file_system(&partition
, filesystem
)?
;
176 let uuid
= get_fs_uuid(&partition
)?
;
177 let uuid_path
= format
!("/dev/disk/by-uuid/{}", uuid
);
179 let mount_unit_name
= create_datastore_mount_unit(&name
, &mount_point
, filesystem
, &uuid_path
)?
;
181 pbs_systemd
::reload_daemon()?
;
182 pbs_systemd
::enable_unit(&mount_unit_name
)?
;
183 pbs_systemd
::start_unit(&mount_unit_name
)?
;
186 let lock
= open_backup_lockfile(datastore
::DATASTORE_CFG_LOCKFILE
, None
, true)?
;
187 let datastore
: DataStoreConfig
=
188 serde_json
::from_value(json
!({ "name": name, "path": mount_point }
))?
;
190 let (config
, _digest
) = datastore
::config()?
;
192 if config
.sections
.get(&datastore
.name
).is_some() {
193 bail
!("datastore '{}' already exists.", datastore
.name
);
196 crate::api2
::config
::datastore
::do_create_datastore(lock
, config
, datastore
, Some(&worker
))?
;
213 schema
: DATASTORE_SCHEMA
,
218 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_MODIFY
, false),
221 /// Remove a Filesystem mounted under '/mnt/datastore/<name>'.".
222 pub fn delete_datastore_disk(name
: String
) -> Result
<(), Error
> {
224 let path
= format
!("/mnt/datastore/{}", name
);
225 // path of datastore cannot be changed
226 let (config
, _
) = crate::config
::datastore
::config()?
;
227 let datastores
: Vec
<DataStoreConfig
> = config
.convert_to_typed_array("datastore")?
;
228 let conflicting_datastore
: Option
<DataStoreConfig
> = datastores
.into_iter()
229 .find(|ds
| ds
.path
== path
);
231 if let Some(conflicting_datastore
) = conflicting_datastore
{
232 bail
!("Can't remove '{}' since it's required by datastore '{}'",
233 conflicting_datastore
.path
, conflicting_datastore
.name
);
236 // disable systemd mount-unit
237 let mut mount_unit_name
= pbs_systemd
::escape_unit(&path
, true);
238 mount_unit_name
.push_str(".mount");
239 pbs_systemd
::disable_unit(&mount_unit_name
)?
;
241 // delete .mount-file
242 let mount_unit_path
= format
!("/etc/systemd/system/{}", mount_unit_name
);
243 let full_path
= std
::path
::Path
::new(&mount_unit_path
);
244 log
::info
!("removing systemd mount unit {:?}", full_path
);
245 std
::fs
::remove_file(&full_path
)?
;
247 // try to unmount, if that fails tell the user to reboot or unmount manually
248 let mut command
= std
::process
::Command
::new("umount");
250 match pbs_tools
::run_command(command
, None
) {
252 "Could not umount '{}' since it is busy. It will stay mounted \
253 until the next reboot or until unmounted manually!",
260 const ITEM_ROUTER
: Router
= Router
::new()
261 .delete(&API_METHOD_DELETE_DATASTORE_DISK
);
263 pub const ROUTER
: Router
= Router
::new()
264 .get(&API_METHOD_LIST_DATASTORE_MOUNTS
)
265 .post(&API_METHOD_CREATE_DATASTORE_DISK
)
266 .match_all("name", &ITEM_ROUTER
);
269 fn create_datastore_mount_unit(
270 datastore_name
: &str,
272 fs_type
: FileSystemType
,
274 ) -> Result
<String
, Error
> {
276 let mut mount_unit_name
= pbs_systemd
::escape_unit(&mount_point
, true);
277 mount_unit_name
.push_str(".mount");
279 let mount_unit_path
= format
!("/etc/systemd/system/{}", mount_unit_name
);
281 let unit
= SystemdUnitSection
{
282 Description
: format
!("Mount datatstore '{}' under '{}'", datastore_name
, mount_point
),
286 let install
= SystemdInstallSection
{
287 WantedBy
: Some(vec
!["multi-user.target".to_string()]),
291 let mount
= SystemdMountSection
{
292 What
: what
.to_string(),
293 Where
: mount_point
.to_string(),
294 Type
: Some(fs_type
.to_string()),
295 Options
: Some(String
::from("defaults")),
299 let mut config
= SectionConfigData
::new();
300 config
.set_data("Unit", "Unit", unit
)?
;
301 config
.set_data("Install", "Install", install
)?
;
302 config
.set_data("Mount", "Mount", mount
)?
;
304 systemd
::config
::save_systemd_mount(&mount_unit_path
, &config
)?
;