]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/node/disks/directory.rs
tree-wide: fix needless borrows
[proxmox-backup.git] / src / api2 / node / disks / directory.rs
1 use anyhow::{bail, Error};
2 use serde_json::json;
3 use ::serde::{Deserialize, Serialize};
4
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;
9
10 use pbs_api_types::{
11 DataStoreConfig, NODE_SCHEMA, BLOCKDEVICE_NAME_SCHEMA,
12 DATASTORE_SCHEMA, UPID_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY,
13 };
14
15 use crate::tools::disks::{
16 DiskManage, FileSystemType, DiskUsageType,
17 create_file_system, create_single_linux_partition, get_fs_uuid, get_disk_usage_info,
18 };
19 use crate::tools::systemd::{self, types::*};
20
21 use proxmox_rest_server::WorkerTask;
22
23 const BASE_MOUNT_DIR: &str = "/mnt/datastore/";
24
25 #[api(
26 properties: {
27 "filesystem": {
28 type: FileSystemType,
29 optional: true,
30 },
31 },
32 )]
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.
38 pub unitfile: String,
39 /// The name of the mount
40 pub name: String,
41 /// The mount path.
42 pub path: String,
43 /// The mounted device.
44 pub device: String,
45 /// File system type
46 pub filesystem: Option<String>,
47 /// Mount options
48 pub options: Option<String>,
49 }
50
51 #[api(
52 protected: true,
53 input: {
54 properties: {
55 node: {
56 schema: NODE_SCHEMA,
57 },
58 }
59 },
60 returns: {
61 description: "List of systemd datastore mount units.",
62 type: Array,
63 items: {
64 type: DatastoreMountInfo,
65 },
66 },
67 access: {
68 permission: &Permission::Privilege(&["system", "disks"], PRIV_SYS_AUDIT, false),
69 },
70 )]
71 /// List systemd datastore mount units.
72 pub fn list_datastore_mounts() -> Result<Vec<DatastoreMountInfo>, Error> {
73
74 lazy_static::lazy_static! {
75 static ref MOUNT_NAME_REGEX: regex::Regex = regex::Regex::new(r"^mnt-datastore-(.+)\.mount$").unwrap();
76 }
77
78 let mut list = Vec::new();
79
80 let basedir = "/etc/systemd/system";
81 for item in proxmox_sys::fs::scan_subdir(libc::AT_FDCWD, basedir, &MOUNT_NAME_REGEX)? {
82 let item = item?;
83 let name = item.file_name().to_string_lossy().to_string();
84
85 let unitfile = format!("{}/{}", basedir, name);
86 let config = systemd::config::parse_systemd_mount(&unitfile)?;
87 let data: SystemdMountSection = config.lookup("Mount", "Mount")?;
88
89 let name = data
90 .Where
91 .strip_prefix(BASE_MOUNT_DIR)
92 .unwrap_or_else(|| &data.Where)
93 .to_string();
94
95 list.push(DatastoreMountInfo {
96 unitfile,
97 name,
98 device: data.What,
99 path: data.Where,
100 filesystem: data.Type,
101 options: data.Options,
102 });
103 }
104
105 Ok(list)
106 }
107
108 #[api(
109 protected: true,
110 input: {
111 properties: {
112 node: {
113 schema: NODE_SCHEMA,
114 },
115 name: {
116 schema: DATASTORE_SCHEMA,
117 },
118 disk: {
119 schema: BLOCKDEVICE_NAME_SCHEMA,
120 },
121 "add-datastore": {
122 description: "Configure a datastore using the directory.",
123 type: bool,
124 optional: true,
125 },
126 filesystem: {
127 type: FileSystemType,
128 optional: true,
129 },
130 }
131 },
132 returns: {
133 schema: UPID_SCHEMA,
134 },
135 access: {
136 permission: &Permission::Privilege(&["system", "disks"], PRIV_SYS_MODIFY, false),
137 },
138 )]
139 /// Create a Filesystem on an unused disk. Will be mounted under '/mnt/datastore/<name>'.".
140 pub fn create_datastore_disk(
141 name: String,
142 disk: String,
143 add_datastore: Option<bool>,
144 filesystem: Option<FileSystemType>,
145 rpcenv: &mut dyn RpcEnvironment,
146 ) -> Result<String, Error> {
147
148 let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI;
149
150 let auth_id = rpcenv.get_auth_id().unwrap();
151
152 let info = get_disk_usage_info(&disk, true)?;
153
154 if info.used != DiskUsageType::Unused {
155 bail!("disk '{}' is already in use.", disk);
156 }
157
158 let mount_point = format!("{}{}", BASE_MOUNT_DIR, &name);
159
160 // check if the default path does exist already and bail if it does
161 let default_path = std::path::PathBuf::from(&mount_point);
162
163 match std::fs::metadata(&default_path) {
164 Err(_) => {}, // path does not exist
165 Ok(_) => {
166 bail!("path {:?} already exists", default_path);
167 }
168 }
169
170 let upid_str = WorkerTask::new_thread(
171 "dircreate", Some(name.clone()), auth_id, to_stdout, move |worker|
172 {
173 task_log!(worker, "create datastore '{}' on disk {}", name, disk);
174
175 let add_datastore = add_datastore.unwrap_or(false);
176 let filesystem = filesystem.unwrap_or(FileSystemType::Ext4);
177
178 let manager = DiskManage::new();
179
180 let disk = manager.disk_by_name(&disk)?;
181
182 let partition = create_single_linux_partition(&disk)?;
183 create_file_system(&partition, filesystem)?;
184
185 let uuid = get_fs_uuid(&partition)?;
186 let uuid_path = format!("/dev/disk/by-uuid/{}", uuid);
187
188 let mount_unit_name = create_datastore_mount_unit(&name, &mount_point, filesystem, &uuid_path)?;
189
190 crate::tools::systemd::reload_daemon()?;
191 crate::tools::systemd::enable_unit(&mount_unit_name)?;
192 crate::tools::systemd::start_unit(&mount_unit_name)?;
193
194 if add_datastore {
195 let lock = pbs_config::datastore::lock_config()?;
196 let datastore: DataStoreConfig =
197 serde_json::from_value(json!({ "name": name, "path": mount_point }))?;
198
199 let (config, _digest) = pbs_config::datastore::config()?;
200
201 if config.sections.get(&datastore.name).is_some() {
202 bail!("datastore '{}' already exists.", datastore.name);
203 }
204
205 crate::api2::config::datastore::do_create_datastore(lock, config, datastore, Some(&worker))?;
206 }
207
208 Ok(())
209 })?;
210
211 Ok(upid_str)
212 }
213
214 #[api(
215 protected: true,
216 input: {
217 properties: {
218 node: {
219 schema: NODE_SCHEMA,
220 },
221 name: {
222 schema: DATASTORE_SCHEMA,
223 },
224 }
225 },
226 access: {
227 permission: &Permission::Privilege(&["system", "disks"], PRIV_SYS_MODIFY, false),
228 },
229 )]
230 /// Remove a Filesystem mounted under '/mnt/datastore/<name>'.".
231 pub fn delete_datastore_disk(name: String) -> Result<(), Error> {
232
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);
239
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);
243 }
244
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)?;
249
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)?;
255
256 // try to unmount, if that fails tell the user to reboot or unmount manually
257 let mut command = std::process::Command::new("umount");
258 command.arg(&path);
259 match proxmox_sys::command::run_command(command, None) {
260 Err(_) => bail!(
261 "Could not umount '{}' since it is busy. It will stay mounted \
262 until the next reboot or until unmounted manually!",
263 path
264 ),
265 Ok(_) => Ok(())
266 }
267 }
268
269 const ITEM_ROUTER: Router = Router::new()
270 .delete(&API_METHOD_DELETE_DATASTORE_DISK);
271
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);
276
277
278 fn create_datastore_mount_unit(
279 datastore_name: &str,
280 mount_point: &str,
281 fs_type: FileSystemType,
282 what: &str,
283 ) -> Result<String, Error> {
284
285 let mut mount_unit_name = proxmox_sys::systemd::escape_unit(mount_point, true);
286 mount_unit_name.push_str(".mount");
287
288 let mount_unit_path = format!("/etc/systemd/system/{}", mount_unit_name);
289
290 let unit = SystemdUnitSection {
291 Description: format!("Mount datatstore '{}' under '{}'", datastore_name, mount_point),
292 ..Default::default()
293 };
294
295 let install = SystemdInstallSection {
296 WantedBy: Some(vec!["multi-user.target".to_string()]),
297 ..Default::default()
298 };
299
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")),
305 ..Default::default()
306 };
307
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)?;
312
313 systemd::config::save_systemd_mount(&mount_unit_path, &config)?;
314
315 Ok(mount_unit_name)
316 }