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
::DataStoreConfig
;
29 #[derive(Debug, Serialize, Deserialize)]
30 #[serde(rename_all="kebab-case")]
31 /// Datastore mount info.
32 pub struct DatastoreMountInfo
{
33 /// The path of the mount unit.
37 /// The mounted device.
40 pub filesystem
: Option
<String
>,
42 pub options
: Option
<String
>,
55 description
: "List of systemd datastore mount units.",
58 type: DatastoreMountInfo
,
62 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_AUDIT
, false),
65 /// List systemd datastore mount units.
66 pub fn list_datastore_mounts() -> Result
<Vec
<DatastoreMountInfo
>, Error
> {
68 lazy_static
::lazy_static
! {
69 static ref MOUNT_NAME_REGEX
: regex
::Regex
= regex
::Regex
::new(r
"^mnt-datastore-(.+)\.mount$").unwrap();
72 let mut list
= Vec
::new();
74 let basedir
= "/etc/systemd/system";
75 for item
in crate::tools
::fs
::scan_subdir(libc
::AT_FDCWD
, basedir
, &MOUNT_NAME_REGEX
)?
{
77 let name
= item
.file_name().to_string_lossy().to_string();
79 let unitfile
= format
!("{}/{}", basedir
, name
);
80 let config
= systemd
::config
::parse_systemd_mount(&unitfile
)?
;
81 let data
: SystemdMountSection
= config
.lookup("Mount", "Mount")?
;
83 list
.push(DatastoreMountInfo
{
87 filesystem
: data
.Type
,
88 options
: data
.Options
,
103 schema
: DATASTORE_SCHEMA
,
106 schema
: BLOCKDEVICE_NAME_SCHEMA
,
109 description
: "Configure a datastore using the directory.",
114 type: FileSystemType
,
123 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_MODIFY
, false),
126 /// Create a Filesystem on an unused disk. Will be mounted under '/mnt/datastore/<name>'.".
127 pub fn create_datastore_disk(
130 add_datastore
: Option
<bool
>,
131 filesystem
: Option
<FileSystemType
>,
132 rpcenv
: &mut dyn RpcEnvironment
,
133 ) -> Result
<String
, Error
> {
135 let to_stdout
= if rpcenv
.env_type() == RpcEnvironmentType
::CLI { true }
else { false }
;
137 let userid
: Userid
= rpcenv
.get_user().unwrap().parse()?
;
139 let info
= get_disk_usage_info(&disk
, true)?
;
141 if info
.used
!= DiskUsageType
::Unused
{
142 bail
!("disk '{}' is already in use.", disk
);
145 let upid_str
= WorkerTask
::new_thread(
146 "dircreate", Some(name
.clone()), userid
, to_stdout
, move |worker
|
148 worker
.log(format
!("create datastore '{}' on disk {}", name
, disk
));
150 let add_datastore
= add_datastore
.unwrap_or(false);
151 let filesystem
= filesystem
.unwrap_or(FileSystemType
::Ext4
);
153 let manager
= DiskManage
::new();
155 let disk
= manager
.clone().disk_by_name(&disk
)?
;
157 let partition
= create_single_linux_partition(&disk
)?
;
158 create_file_system(&partition
, filesystem
)?
;
160 let uuid
= get_fs_uuid(&partition
)?
;
161 let uuid_path
= format
!("/dev/disk/by-uuid/{}", uuid
);
163 let (mount_unit_name
, mount_point
) = create_datastore_mount_unit(&name
, filesystem
, &uuid_path
)?
;
165 systemd
::reload_daemon()?
;
166 systemd
::enable_unit(&mount_unit_name
)?
;
167 systemd
::start_unit(&mount_unit_name
)?
;
170 crate::api2
::config
::datastore
::create_datastore(json
!({ "name": name, "path": mount_point }
))?
187 schema
: DATASTORE_SCHEMA
,
192 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_MODIFY
, false),
195 /// Remove a Filesystem mounted under '/mnt/datastore/<name>'.".
196 pub fn delete_datastore_disk(name
: String
) -> Result
<(), Error
> {
198 let path
= format
!("/mnt/datastore/{}", name
);
199 // path of datastore cannot be changed
200 let (config
, _
) = crate::config
::datastore
::config()?
;
201 let datastores
: Vec
<DataStoreConfig
> = config
.convert_to_typed_array("datastore")?
;
202 let conflicting_datastore
: Option
<DataStoreConfig
> = datastores
.into_iter()
203 .filter(|ds
| ds
.path
== path
)
206 if let Some(conflicting_datastore
) = conflicting_datastore
{
207 bail
!("Can't remove '{}' since it's required by datastore '{}'",
208 conflicting_datastore
.path
, conflicting_datastore
.name
);
211 // disable systemd mount-unit
212 let mut mount_unit_name
= systemd
::escape_unit(&path
, true);
213 mount_unit_name
.push_str(".mount");
214 systemd
::disable_unit(&mount_unit_name
)?
;
216 // delete .mount-file
217 let mount_unit_path
= format
!("/etc/systemd/system/{}", mount_unit_name
);
218 let full_path
= std
::path
::Path
::new(&mount_unit_path
);
219 log
::info
!("removing systemd mount unit {:?}", full_path
);
220 std
::fs
::remove_file(&full_path
)?
;
222 // try to unmount, if that fails tell the user to reboot or unmount manually
223 let mut command
= std
::process
::Command
::new("umount");
225 match crate::tools
::run_command(command
, None
) {
227 "Could not umount '{}' since it is busy. It will stay mounted \
228 until the next reboot or until unmounted manually!",
235 const ITEM_ROUTER
: Router
= Router
::new()
236 .delete(&API_METHOD_DELETE_DATASTORE_DISK
);
238 pub const ROUTER
: Router
= Router
::new()
239 .get(&API_METHOD_LIST_DATASTORE_MOUNTS
)
240 .post(&API_METHOD_CREATE_DATASTORE_DISK
)
241 .match_all("name", &ITEM_ROUTER
);
244 fn create_datastore_mount_unit(
245 datastore_name
: &str,
246 fs_type
: FileSystemType
,
248 ) -> Result
<(String
, String
), Error
> {
250 let mount_point
= format
!("/mnt/datastore/{}", datastore_name
);
251 let mut mount_unit_name
= systemd
::escape_unit(&mount_point
, true);
252 mount_unit_name
.push_str(".mount");
254 let mount_unit_path
= format
!("/etc/systemd/system/{}", mount_unit_name
);
256 let unit
= SystemdUnitSection
{
257 Description
: format
!("Mount datatstore '{}' under '{}'", datastore_name
, mount_point
),
261 let install
= SystemdInstallSection
{
262 WantedBy
: Some(vec
!["multi-user.target".to_string()]),
266 let mount
= SystemdMountSection
{
267 What
: what
.to_string(),
268 Where
: mount_point
.clone(),
269 Type
: Some(fs_type
.to_string()),
270 Options
: Some(String
::from("defaults")),
274 let mut config
= SectionConfigData
::new();
275 config
.set_data("Unit", "Unit", unit
)?
;
276 config
.set_data("Install", "Install", install
)?
;
277 config
.set_data("Mount", "Mount", mount
)?
;
279 systemd
::config
::save_systemd_mount(&mount_unit_path
, &config
)?
;
281 Ok((mount_unit_name
, mount_point
))