1 use anyhow
::{bail, Error}
;
3 use ::serde
::{Deserialize, Serialize}
;
5 use proxmox
::api
::{api, Permission, RpcEnvironment, RpcEnvironmentType, HttpError}
;
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 proxmox
::api
::error
::StatusCode
;
20 use crate::config
::datastore
::DataStoreConfig
;
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 crate::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
= if rpcenv
.env_type() == RpcEnvironmentType
::CLI { true }
else { false }
;
138 let userid
: Userid
= rpcenv
.get_user().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 upid_str
= WorkerTask
::new_thread(
147 "dircreate", Some(name
.clone()), userid
, to_stdout
, move |worker
|
149 worker
.log(format
!("create datastore '{}' on disk {}", name
, disk
));
151 let add_datastore
= add_datastore
.unwrap_or(false);
152 let filesystem
= filesystem
.unwrap_or(FileSystemType
::Ext4
);
154 let manager
= DiskManage
::new();
156 let disk
= manager
.clone().disk_by_name(&disk
)?
;
158 let partition
= create_single_linux_partition(&disk
)?
;
159 create_file_system(&partition
, filesystem
)?
;
161 let uuid
= get_fs_uuid(&partition
)?
;
162 let uuid_path
= format
!("/dev/disk/by-uuid/{}", uuid
);
164 let (mount_unit_name
, mount_point
) = create_datastore_mount_unit(&name
, filesystem
, &uuid_path
)?
;
166 systemd
::reload_daemon()?
;
167 systemd
::enable_unit(&mount_unit_name
)?
;
168 systemd
::start_unit(&mount_unit_name
)?
;
171 crate::api2
::config
::datastore
::create_datastore(json
!({ "name": name, "path": mount_point }
))?
188 schema
: DATASTORE_SCHEMA
,
193 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_MODIFY
, false),
196 /// Remove a Filesystem mounted under '/mnt/datastore/<name>'.".
197 pub fn delete_datastore_disk(
199 rpcenv
: &mut dyn RpcEnvironment
,
200 ) -> Result
<(), Error
> {
201 let path
= format
!("/mnt/datastore/{}", name
);
202 // path of datastore cannot be changed
203 let (config
, _
) = crate::config
::datastore
::config()?
;
204 let datastores
: Vec
<DataStoreConfig
> = config
.convert_to_typed_array("datastore")?
;
205 let conflicting_datastore
: Option
<DataStoreConfig
> = datastores
.into_iter()
206 .filter(|ds
| ds
.path
== path
)
209 match conflicting_datastore
{
210 Some(conflicting_datastore
) =>
211 Err(Error
::from(HttpError
::new(StatusCode
::CONFLICT
,
212 format
!("Can't remove '{}' since it's required by datastore '{}'",
213 conflicting_datastore
.path
,
214 conflicting_datastore
.name
)))),
216 // disable systemd mount-unit
217 let mut mount_unit_name
= systemd
::escape_unit(&path
, true);
218 mount_unit_name
.push_str(".mount");
219 systemd
::disable_unit(mount_unit_name
.as_str())?
;
221 // delete .mount-file
222 let mount_unit_path
= format
!("/etc/systemd/system/{}", mount_unit_name
);
223 let full_path
= std
::path
::Path
::new(mount_unit_path
.as_str());
224 log
::info
!("removing {:?}", full_path
);
225 std
::fs
::remove_file(&full_path
)?
;
227 // try to unmount, if that fails tell the user to reboot or unmount manually
228 let mut command
= std
::process
::Command
::new("umount");
229 command
.arg(path
.as_str());
230 match crate::tools
::run_command(command
, None
) {
232 "Could not umount '{}' since it is busy. It will stay mounted \
233 until the next reboot or until unmounted manually!",
242 const ITEM_ROUTER
: Router
= Router
::new()
243 .delete(&API_METHOD_DELETE_DATASTORE_DISK
);
245 pub const ROUTER
: Router
= Router
::new()
246 .get(&API_METHOD_LIST_DATASTORE_MOUNTS
)
247 .post(&API_METHOD_CREATE_DATASTORE_DISK
)
248 .match_all("name", &ITEM_ROUTER
);
251 fn create_datastore_mount_unit(
252 datastore_name
: &str,
253 fs_type
: FileSystemType
,
255 ) -> Result
<(String
, String
), Error
> {
257 let mount_point
= format
!("/mnt/datastore/{}", datastore_name
);
258 let mut mount_unit_name
= systemd
::escape_unit(&mount_point
, true);
259 mount_unit_name
.push_str(".mount");
261 let mount_unit_path
= format
!("/etc/systemd/system/{}", mount_unit_name
);
263 let unit
= SystemdUnitSection
{
264 Description
: format
!("Mount datatstore '{}' under '{}'", datastore_name
, mount_point
),
268 let install
= SystemdInstallSection
{
269 WantedBy
: Some(vec
!["multi-user.target".to_string()]),
273 let mount
= SystemdMountSection
{
274 What
: what
.to_string(),
275 Where
: mount_point
.clone(),
276 Type
: Some(fs_type
.to_string()),
277 Options
: Some(String
::from("defaults")),
281 let mut config
= SectionConfigData
::new();
282 config
.set_data("Unit", "Unit", unit
)?
;
283 config
.set_data("Install", "Install", install
)?
;
284 config
.set_data("Mount", "Mount", mount
)?
;
286 systemd
::config
::save_systemd_mount(&mount_unit_path
, &config
)?
;
288 Ok((mount_unit_name
, mount_point
))