1 //! Disk query/management utilities for.
3 use std
::collections
::{HashMap, HashSet}
;
4 use std
::ffi
::{OsStr, OsString}
;
6 use std
::os
::unix
::ffi
::{OsStrExt, OsStringExt}
;
7 use std
::os
::unix
::fs
::MetadataExt
;
8 use std
::path
::{Path, PathBuf}
;
11 use anyhow
::{bail, format_err, Error}
;
13 use once_cell
::sync
::OnceCell
;
15 use ::serde
::{Deserialize, Serialize}
;
17 use proxmox
::sys
::error
::io_err_other
;
18 use proxmox
::sys
::linux
::procfs
::{MountInfo, mountinfo::Device}
;
19 use proxmox
::{io_bail, io_format_err}
;
20 use proxmox_schema
::api
;
22 use pbs_api_types
::{BLOCKDEVICE_NAME_REGEX, StorageStatus}
;
27 pub use zpool_status
::*;
29 pub use zpool_list
::*;
35 lazy_static
::lazy_static
!{
36 static ref ISCSI_PATH_REGEX
: regex
::Regex
=
37 regex
::Regex
::new(r
"host[^/]*/session[^/]*").unwrap();
40 /// Disk management context.
42 /// This provides access to disk information with some caching for faster querying of multiple
44 pub struct DiskManage
{
45 mount_info
: OnceCell
<MountInfo
>,
46 mounted_devices
: OnceCell
<HashSet
<dev_t
>>,
49 /// Information for a device as returned by lsblk.
50 #[derive(Deserialize)]
51 pub struct LsblkInfo
{
52 /// Path to the device.
54 /// Partition type GUID.
55 #[serde(rename = "parttype")]
56 partition_type
: Option
<String
>,
57 /// File system label.
58 #[serde(rename = "fstype")]
59 file_system_type
: Option
<String
>,
63 /// Create a new disk management context.
64 pub fn new() -> Arc
<Self> {
66 mount_info
: OnceCell
::new(),
67 mounted_devices
: OnceCell
::new(),
71 /// Get the current mount info. This simply caches the result of `MountInfo::read` from the
72 /// `proxmox::sys` module.
73 pub fn mount_info(&self) -> Result
<&MountInfo
, Error
> {
74 self.mount_info
.get_or_try_init(MountInfo
::read
)
77 /// Get a `Disk` from a device node (eg. `/dev/sda`).
78 pub fn disk_by_node
<P
: AsRef
<Path
>>(self: Arc
<Self>, devnode
: P
) -> io
::Result
<Disk
> {
79 let devnode
= devnode
.as_ref();
81 let meta
= std
::fs
::metadata(devnode
)?
;
82 if (meta
.mode() & libc
::S_IFBLK
) == libc
::S_IFBLK
{
83 self.disk_by_dev_num(meta
.rdev())
85 io_bail
!("not a block device: {:?}", devnode
);
89 /// Get a `Disk` for a specific device number.
90 pub fn disk_by_dev_num(self: Arc
<Self>, devnum
: dev_t
) -> io
::Result
<Disk
> {
91 self.disk_by_sys_path(format
!(
92 "/sys/dev/block/{}:{}",
93 unsafe { libc::major(devnum) }
,
94 unsafe { libc::minor(devnum) }
,
98 /// Get a `Disk` for a path in `/sys`.
99 pub fn disk_by_sys_path
<P
: AsRef
<Path
>>(self: Arc
<Self>, path
: P
) -> io
::Result
<Disk
> {
100 let device
= udev
::Device
::from_syspath(path
.as_ref())?
;
104 info
: Default
::default(),
108 /// Get a `Disk` for a name in `/sys/block/<name>`.
109 pub fn disk_by_name(self: Arc
<Self>, name
: &str) -> io
::Result
<Disk
> {
110 let syspath
= format
!("/sys/block/{}", name
);
111 self.disk_by_sys_path(&syspath
)
114 /// Gather information about mounted disks:
115 fn mounted_devices(&self) -> Result
<&HashSet
<dev_t
>, Error
> {
117 .get_or_try_init(|| -> Result
<_
, Error
> {
118 let mut mounted
= HashSet
::new();
120 for (_id
, mp
) in self.mount_info()?
{
121 let source
= match mp
.mount_source
.as_deref() {
126 let path
= Path
::new(source
);
127 if !path
.is_absolute() {
131 let meta
= match std
::fs
::metadata(path
) {
133 Err(ref err
) if err
.kind() == io
::ErrorKind
::NotFound
=> continue,
134 Err(other
) => return Err(Error
::from(other
)),
137 if (meta
.mode() & libc
::S_IFBLK
) != libc
::S_IFBLK
{
138 // not a block device
142 mounted
.insert(meta
.rdev());
149 /// Information about file system type and used device for a path
151 /// Returns tuple (fs_type, device, mount_source)
152 pub fn find_mounted_device(
154 path
: &std
::path
::Path
,
155 ) -> Result
<Option
<(String
, Device
, Option
<OsString
>)>, Error
> {
157 let stat
= nix
::sys
::stat
::stat(path
)?
;
158 let device
= Device
::from_dev_t(stat
.st_dev
);
160 let root_path
= std
::path
::Path
::new("/");
162 for (_id
, entry
) in self.mount_info()?
{
163 if entry
.root
== root_path
&& entry
.device
== device
{
164 return Ok(Some((entry
.fs_type
.clone(), entry
.device
, entry
.mount_source
.clone())));
171 /// Check whether a specific device node is mounted.
173 /// Note that this tries to `stat` the sources of all mount points without caching the result
174 /// of doing so, so this is always somewhat expensive.
175 pub fn is_devnum_mounted(&self, dev
: dev_t
) -> Result
<bool
, Error
> {
176 self.mounted_devices().map(|mounted
| mounted
.contains(&dev
))
180 /// Queries (and caches) various information about a specific disk.
182 /// This belongs to a `Disks` and provides information for a single disk.
184 manager
: Arc
<DiskManage
>,
185 device
: udev
::Device
,
189 /// Helper struct (so we can initialize this with Default)
191 /// We probably want this to be serializable to the same hash type we use in perl currently.
195 vendor
: OnceCell
<Option
<OsString
>>,
196 model
: OnceCell
<Option
<OsString
>>,
197 rotational
: OnceCell
<Option
<bool
>>,
198 // for perl: #[serde(rename = "devpath")]
199 ata_rotation_rate_rpm
: OnceCell
<Option
<u64>>,
200 // for perl: #[serde(rename = "devpath")]
201 device_path
: OnceCell
<Option
<PathBuf
>>,
202 wwn
: OnceCell
<Option
<OsString
>>,
203 serial
: OnceCell
<Option
<OsString
>>,
204 // for perl: #[serde(skip_serializing)]
205 partition_table_type
: OnceCell
<Option
<OsString
>>,
208 bus
: OnceCell
<Option
<OsString
>>,
210 fs_type
: OnceCell
<Option
<OsString
>>,
212 has_holders
: OnceCell
<bool
>,
214 is_mounted
: OnceCell
<bool
>,
218 /// Try to get the device number for this disk.
220 /// (In udev this can fail...)
221 pub fn devnum(&self) -> Result
<dev_t
, Error
> {
222 // not sure when this can fail...
225 .ok_or_else(|| format_err
!("failed to get device number"))
228 /// Get the sys-name of this device. (The final component in the `/sys` path).
229 pub fn sysname(&self) -> &OsStr
{
230 self.device
.sysname()
233 /// Get the this disk's `/sys` path.
234 pub fn syspath(&self) -> &Path
{
235 self.device
.syspath()
238 /// Get the device node in `/dev`, if any.
239 pub fn device_path(&self) -> Option
<&Path
> {
240 //self.device.devnode()
243 .get_or_init(|| self.device
.devnode().map(Path
::to_owned
))
245 .map(PathBuf
::as_path
)
248 /// Get the parent device.
249 pub fn parent(&self) -> Option
<Self> {
250 self.device
.parent().map(|parent
| Self {
251 manager
: self.manager
.clone(),
253 info
: Default
::default(),
257 /// Read from a file in this device's sys path.
259 /// Note: path must be a relative path!
260 pub fn read_sys(&self, path
: &Path
) -> io
::Result
<Option
<Vec
<u8>>> {
261 assert
!(path
.is_relative());
263 std
::fs
::read(self.syspath().join(path
))
266 if err
.kind() == io
::ErrorKind
::NotFound
{
274 /// Convenience wrapper for reading a `/sys` file which contains just a simple `OsString`.
275 pub fn read_sys_os_str
<P
: AsRef
<Path
>>(&self, path
: P
) -> io
::Result
<Option
<OsString
>> {
276 Ok(self.read_sys(path
.as_ref())?
.map(|mut v
| {
277 if Some(&b'
\n'
) == v
.last() {
280 OsString
::from_vec(v
)
284 /// Convenience wrapper for reading a `/sys` file which contains just a simple utf-8 string.
285 pub fn read_sys_str
<P
: AsRef
<Path
>>(&self, path
: P
) -> io
::Result
<Option
<String
>> {
286 Ok(match self.read_sys(path
.as_ref())?
{
287 Some(data
) => Some(String
::from_utf8(data
).map_err(io_err_other
)?
),
292 /// Convenience wrapper for unsigned integer `/sys` values up to 64 bit.
293 pub fn read_sys_u64
<P
: AsRef
<Path
>>(&self, path
: P
) -> io
::Result
<Option
<u64>> {
294 Ok(match self.read_sys_str(path
)?
{
295 Some(data
) => Some(data
.trim().parse().map_err(io_err_other
)?
),
300 /// Get the disk's size in bytes.
301 pub fn size(&self) -> io
::Result
<u64> {
302 Ok(*self.info
.size
.get_or_try_init(|| {
303 self.read_sys_u64("size")?
.map(|s
| s
*512).ok_or_else(|| {
305 "failed to get disk size from {:?}",
306 self.syspath().join("size"),
312 /// Get the device vendor (`/sys/.../device/vendor`) entry if available.
313 pub fn vendor(&self) -> io
::Result
<Option
<&OsStr
>> {
317 .get_or_try_init(|| self.read_sys_os_str("device/vendor"))?
319 .map(OsString
::as_os_str
))
322 /// Get the device model (`/sys/.../device/model`) entry if available.
323 pub fn model(&self) -> Option
<&OsStr
> {
326 .get_or_init(|| self.device
.property_value("ID_MODEL").map(OsStr
::to_owned
))
328 .map(OsString
::as_os_str
)
331 /// Check whether this is a rotational disk.
333 /// Returns `None` if there's no `queue/rotational` file, in which case no information is
334 /// known. `Some(false)` if `queue/rotational` is zero, `Some(true)` if it has a non-zero
336 pub fn rotational(&self) -> io
::Result
<Option
<bool
>> {
340 .get_or_try_init(|| -> io
::Result
<Option
<bool
>> {
341 Ok(self.read_sys_u64("queue/rotational")?
.map(|n
| n
!= 0))
345 /// Get the WWN if available.
346 pub fn wwn(&self) -> Option
<&OsStr
> {
349 .get_or_init(|| self.device
.property_value("ID_WWN").map(|v
| v
.to_owned()))
351 .map(OsString
::as_os_str
)
354 /// Get the device serial if available.
355 pub fn serial(&self) -> Option
<&OsStr
> {
360 .property_value("ID_SERIAL_SHORT")
361 .map(|v
| v
.to_owned())
364 .map(OsString
::as_os_str
)
367 /// Get the ATA rotation rate value from udev. This is not necessarily the same as sysfs'
368 /// `rotational` value.
369 pub fn ata_rotation_rate_rpm(&self) -> Option
<u64> {
370 *self.info
.ata_rotation_rate_rpm
.get_or_init(|| {
373 .property_value("ID_ATA_ROTATION_RATE_RPM")?
382 /// Get the partition table type, if any.
383 pub fn partition_table_type(&self) -> Option
<&OsStr
> {
385 .partition_table_type
388 .property_value("ID_PART_TABLE_TYPE")
389 .map(|v
| v
.to_owned())
392 .map(OsString
::as_os_str
)
395 /// Check if this contains a GPT partition table.
396 pub fn has_gpt(&self) -> bool
{
397 *self.info
.gpt
.get_or_init(|| {
398 self.partition_table_type()
404 /// Get the bus type used for this disk.
405 pub fn bus(&self) -> Option
<&OsStr
> {
408 .get_or_init(|| self.device
.property_value("ID_BUS").map(|v
| v
.to_owned()))
410 .map(OsString
::as_os_str
)
413 /// Attempt to guess the disk type.
414 pub fn guess_disk_type(&self) -> io
::Result
<DiskType
> {
415 Ok(match self.rotational()?
{
416 Some(false) => DiskType
::Ssd
,
417 Some(true) => DiskType
::Hdd
,
418 None
=> match self.ata_rotation_rate_rpm() {
419 Some(_
) => DiskType
::Hdd
,
420 None
=> match self.bus() {
421 Some(bus
) if bus
== "usb" => DiskType
::Usb
,
422 _
=> DiskType
::Unknown
,
428 /// Get the file system type found on the disk, if any.
430 /// Note that `None` may also just mean "unknown".
431 pub fn fs_type(&self) -> Option
<&OsStr
> {
436 .property_value("ID_FS_TYPE")
437 .map(|v
| v
.to_owned())
440 .map(OsString
::as_os_str
)
443 /// Check if there are any "holders" in `/sys`. This usually means the device is in use by
444 /// another kernel driver like the device mapper.
445 pub fn has_holders(&self) -> io
::Result
<bool
> {
449 .get_or_try_init(|| -> io
::Result
<bool
> {
450 let mut subdir
= self.syspath().to_owned();
451 subdir
.push("holders");
452 for entry
in std
::fs
::read_dir(subdir
)?
{
453 match entry?
.file_name().as_bytes() {
455 _
=> return Ok(true),
462 /// Check if this disk is mounted.
463 pub fn is_mounted(&self) -> Result
<bool
, Error
> {
467 .get_or_try_init(|| self.manager
.is_devnum_mounted(self.devnum()?
))?
)
470 /// Read block device stats
472 /// see https://www.kernel.org/doc/Documentation/block/stat.txt
473 pub fn read_stat(&self) -> std
::io
::Result
<Option
<BlockDevStat
>> {
474 if let Some(stat
) = self.read_sys(Path
::new("stat"))?
{
475 let stat
= unsafe { std::str::from_utf8_unchecked(&stat) }
;
476 let stat
: Vec
<u64> = stat
.split_ascii_whitespace().map(|s
| {
477 u64::from_str_radix(s
, 10).unwrap_or(0)
480 if stat
.len() < 15 { return Ok(None); }
482 return Ok(Some(BlockDevStat
{
484 read_sectors
: stat
[2],
485 write_ios
: stat
[4] + stat
[11], // write + discard
486 write_sectors
: stat
[6] + stat
[13], // write + discard
493 /// List device partitions
494 pub fn partitions(&self) -> Result
<HashMap
<u64, Disk
>, Error
> {
496 let sys_path
= self.syspath();
497 let device
= self.sysname().to_string_lossy().to_string();
499 let mut map
= HashMap
::new();
501 for item
in pbs_tools
::fs
::read_subdir(libc
::AT_FDCWD
, sys_path
)?
{
503 let name
= match item
.file_name().to_str() {
505 Err(_
) => continue, // skip non utf8 entries
508 if !name
.starts_with(&device
) { continue; }
510 let mut part_path
= sys_path
.to_owned();
511 part_path
.push(name
);
513 let disk_part
= self.manager
.clone().disk_by_sys_path(&part_path
)?
;
515 if let Some(partition
) = disk_part
.read_sys_u64("partition")?
{
516 map
.insert(partition
, disk_part
);
524 /// Returns disk usage information (total, used, avail)
525 pub fn disk_usage(path
: &std
::path
::Path
) -> Result
<StorageStatus
, Error
> {
527 let mut stat
: libc
::statfs64
= unsafe { std::mem::zeroed() }
;
531 let res
= path
.with_nix_path(|cstr
| unsafe { libc::statfs64(cstr.as_ptr(), &mut stat) }
)?
;
532 nix
::errno
::Errno
::result(res
)?
;
534 let bsize
= stat
.f_bsize
as u64;
537 total
: stat
.f_blocks
*bsize
,
538 used
: (stat
.f_blocks
-stat
.f_bfree
)*bsize
,
539 avail
: stat
.f_bavail
*bsize
,
544 #[derive(Debug, Serialize, Deserialize)]
545 #[serde(rename_all="lowercase")]
546 /// This is just a rough estimate for a "type" of disk.
551 /// May also be a USB-HDD.
554 /// May also be a USB-SSD.
557 /// Some kind of USB disk, but we don't know more than that.
562 /// Represents the contents of the /sys/block/<dev>/stat file.
563 pub struct BlockDevStat
{
565 pub read_sectors
: u64,
567 pub write_sectors
: u64,
568 pub io_ticks
: u64, // milliseconds
571 /// Use lsblk to read partition type uuids and file system types.
572 pub fn get_lsblk_info() -> Result
<Vec
<LsblkInfo
>, Error
> {
574 let mut command
= std
::process
::Command
::new("lsblk");
575 command
.args(&["--json", "-o", "path,parttype,fstype"]);
577 let output
= pbs_tools
::run_command(command
, None
)?
;
579 let mut output
: serde_json
::Value
= output
.parse()?
;
581 Ok(serde_json
::from_value(output
["blockdevices"].take())?
)
584 /// Get set of devices with a file system label.
586 /// The set is indexed by using the unix raw device number (dev_t is u64)
587 fn get_file_system_devices(
588 lsblk_info
: &[LsblkInfo
],
589 ) -> Result
<HashSet
<u64>, Error
> {
591 let mut device_set
: HashSet
<u64> = HashSet
::new();
593 for info
in lsblk_info
.iter() {
594 if info
.file_system_type
.is_some() {
595 let meta
= std
::fs
::metadata(&info
.path
)?
;
596 device_set
.insert(meta
.rdev());
604 #[derive(Debug, Serialize, Deserialize, PartialEq)]
605 #[serde(rename_all="lowercase")]
606 pub enum DiskUsageType
{
607 /// Disk is not used (as far we can tell)
611 /// Disk is used by LVM
613 /// Disk is used by ZFS
615 /// Disk is used by device-mapper
617 /// Disk has partitions
619 /// Disk contains a file system label
636 #[derive(Debug, Serialize, Deserialize)]
637 #[serde(rename_all="kebab-case")]
638 /// Information about how a Disk is used
639 pub struct DiskUsageInfo
{
640 /// Disk name (/sys/block/<name>)
642 pub used
: DiskUsageType
,
643 pub disk_type
: DiskType
,
644 pub status
: SmartStatus
,
646 pub wearout
: Option
<f64>,
648 pub vendor
: Option
<String
>,
650 pub model
: Option
<String
>,
652 pub wwn
: Option
<String
>,
656 pub serial
: Option
<String
>,
657 /// Linux device path (/dev/xxx)
658 pub devpath
: Option
<String
>,
659 /// Set if disk contains a GPT partition table
662 pub rpm
: Option
<u64>,
666 disk_manager
: Arc
<DiskManage
>,
667 lvm_devices
: &HashSet
<u64>,
668 zfs_devices
: &HashSet
<u64>,
670 ) -> Result
<DiskUsageType
, Error
> {
672 let mut sys_path
= std
::path
::PathBuf
::from("/sys/block");
673 sys_path
.push(device
);
675 let mut used
= DiskUsageType
::Unused
;
677 let mut found_lvm
= false;
678 let mut found_zfs
= false;
679 let mut found_mountpoints
= false;
680 let mut found_dm
= false;
681 let mut found_partitions
= false;
683 for item
in pbs_tools
::fs
::read_subdir(libc
::AT_FDCWD
, &sys_path
)?
{
685 let name
= match item
.file_name().to_str() {
687 Err(_
) => continue, // skip non utf8 entries
689 if !name
.starts_with(device
) { continue; }
691 found_partitions
= true;
693 let mut part_path
= sys_path
.clone();
694 part_path
.push(name
);
696 let data
= disk_manager
.clone().disk_by_sys_path(&part_path
)?
;
698 let devnum
= data
.devnum()?
;
700 if lvm_devices
.contains(&devnum
) {
704 if data
.is_mounted()?
{
705 found_mountpoints
= true;
708 if data
.has_holders()?
{
712 if zfs_devices
.contains(&devnum
) {
717 if found_mountpoints
{
718 used
= DiskUsageType
::Mounted
;
719 } else if found_lvm
{
720 used
= DiskUsageType
::LVM
;
721 } else if found_zfs
{
722 used
= DiskUsageType
::ZFS
;
724 used
= DiskUsageType
::DeviceMapper
;
725 } else if found_partitions
{
726 used
= DiskUsageType
::Partitions
;
733 /// Get disk usage information for a single disk
734 pub fn get_disk_usage_info(
737 ) -> Result
<DiskUsageInfo
, Error
> {
738 let mut filter
= Vec
::new();
739 filter
.push(disk
.to_string());
740 let mut map
= get_disks(Some(filter
), no_smart
)?
;
741 if let Some(info
) = map
.remove(disk
) {
744 bail
!("failed to get disk usage info - internal error"); // should not happen
748 /// Get disk usage information for multiple disks
750 // filter - list of device names (without leading /dev)
751 disks
: Option
<Vec
<String
>>,
752 // do no include data from smartctl
754 ) -> Result
<HashMap
<String
, DiskUsageInfo
>, Error
> {
756 let disk_manager
= DiskManage
::new();
758 let lsblk_info
= get_lsblk_info()?
;
760 let zfs_devices
= zfs_devices(&lsblk_info
, None
).or_else(|err
| -> Result
<HashSet
<u64>, Error
> {
761 eprintln
!("error getting zfs devices: {}", err
);
765 let lvm_devices
= get_lvm_devices(&lsblk_info
)?
;
767 let file_system_devices
= get_file_system_devices(&lsblk_info
)?
;
769 // fixme: ceph journals/volumes
771 let mut result
= HashMap
::new();
773 for item
in pbs_tools
::fs
::scan_subdir(libc
::AT_FDCWD
, "/sys/block", &BLOCKDEVICE_NAME_REGEX
)?
{
776 let name
= item
.file_name().to_str().unwrap().to_string();
778 if let Some(ref disks
) = disks
{
779 if !disks
.contains(&name
) { continue; }
782 let sys_path
= format
!("/sys/block/{}", name
);
784 if let Ok(target
) = std
::fs
::read_link(&sys_path
) {
785 if let Some(target
) = target
.to_str() {
786 if ISCSI_PATH_REGEX
.is_match(target
) { continue; }
// skip iSCSI devices
790 let disk
= disk_manager
.clone().disk_by_sys_path(&sys_path
)?
;
792 let devnum
= disk
.devnum()?
;
794 let size
= match disk
.size() {
796 Err(_
) => continue, // skip devices with unreadable size
799 let disk_type
= match disk
.guess_disk_type() {
800 Ok(disk_type
) => disk_type
,
801 Err(_
) => continue, // skip devices with undetectable type
804 let mut usage
= DiskUsageType
::Unused
;
806 if lvm_devices
.contains(&devnum
) {
807 usage
= DiskUsageType
::LVM
;
810 match disk
.is_mounted() {
811 Ok(true) => usage
= DiskUsageType
::Mounted
,
813 Err(_
) => continue, // skip devices with undetectable mount status
816 if zfs_devices
.contains(&devnum
) {
817 usage
= DiskUsageType
::ZFS
;
820 let vendor
= disk
.vendor().unwrap_or(None
).
821 map(|s
| s
.to_string_lossy().trim().to_string());
823 let model
= disk
.model().map(|s
| s
.to_string_lossy().into_owned());
825 let serial
= disk
.serial().map(|s
| s
.to_string_lossy().into_owned());
827 let devpath
= disk
.device_path().map(|p
| p
.to_owned())
828 .map(|p
| p
.to_string_lossy().to_string());
831 let wwn
= disk
.wwn().map(|s
| s
.to_string_lossy().into_owned());
833 if usage
!= DiskUsageType
::Mounted
{
834 match scan_partitions(disk_manager
.clone(), &lvm_devices
, &zfs_devices
, &name
) {
836 if part_usage
!= DiskUsageType
::Unused
{
840 Err(_
) => continue, // skip devices if scan_partitions fail
844 if usage
== DiskUsageType
::Unused
&& file_system_devices
.contains(&devnum
) {
845 usage
= DiskUsageType
::FileSystem
;
848 if usage
== DiskUsageType
::Unused
&& disk
.has_holders()?
{
849 usage
= DiskUsageType
::DeviceMapper
;
852 let mut status
= SmartStatus
::Unknown
;
853 let mut wearout
= None
;
856 if let Ok(smart
) = get_smart_data(&disk
, false) {
857 status
= smart
.status
;
858 wearout
= smart
.wearout
;
862 let info
= DiskUsageInfo
{
864 vendor
, model
, serial
, devpath
, size
, wwn
, disk_type
,
868 rpm
: disk
.ata_rotation_rate_rpm(),
871 result
.insert(name
, info
);
877 /// Try to reload the partition table
878 pub fn reread_partition_table(disk
: &Disk
) -> Result
<(), Error
> {
880 let disk_path
= match disk
.device_path() {
882 None
=> bail
!("disk {:?} has no node in /dev", disk
.syspath()),
885 let mut command
= std
::process
::Command
::new("blockdev");
886 command
.arg("--rereadpt");
887 command
.arg(disk_path
);
889 pbs_tools
::run_command(command
, None
)?
;
894 /// Initialize disk by writing a GPT partition table
895 pub fn inititialize_gpt_disk(disk
: &Disk
, uuid
: Option
<&str>) -> Result
<(), Error
> {
897 let disk_path
= match disk
.device_path() {
899 None
=> bail
!("disk {:?} has no node in /dev", disk
.syspath()),
902 let uuid
= uuid
.unwrap_or("R"); // R .. random disk GUID
904 let mut command
= std
::process
::Command
::new("sgdisk");
905 command
.arg(disk_path
);
906 command
.args(&["-U", uuid
]);
908 pbs_tools
::run_command(command
, None
)?
;
913 /// Create a single linux partition using the whole available space
914 pub fn create_single_linux_partition(disk
: &Disk
) -> Result
<Disk
, Error
> {
916 let disk_path
= match disk
.device_path() {
918 None
=> bail
!("disk {:?} has no node in /dev", disk
.syspath()),
921 let mut command
= std
::process
::Command
::new("sgdisk");
922 command
.args(&["-n1", "-t1:8300"]);
923 command
.arg(disk_path
);
925 pbs_tools
::run_command(command
, None
)?
;
927 let mut partitions
= disk
.partitions()?
;
929 match partitions
.remove(&1) {
930 Some(partition
) => Ok(partition
),
931 None
=> bail
!("unable to lookup device partition"),
936 #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
937 #[serde(rename_all="lowercase")]
938 pub enum FileSystemType
{
945 impl std
::fmt
::Display
for FileSystemType
{
946 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
947 let text
= match self {
948 FileSystemType
::Ext4
=> "ext4",
949 FileSystemType
::Xfs
=> "xfs",
951 write
!(f
, "{}", text
)
955 impl std
::str::FromStr
for FileSystemType
{
956 type Err
= serde_json
::Error
;
958 fn from_str(s
: &str) -> Result
<Self, Self::Err
> {
959 use serde
::de
::IntoDeserializer
;
960 Self::deserialize(s
.into_deserializer())
964 /// Create a file system on a disk or disk partition
965 pub fn create_file_system(disk
: &Disk
, fs_type
: FileSystemType
) -> Result
<(), Error
> {
967 let disk_path
= match disk
.device_path() {
969 None
=> bail
!("disk {:?} has no node in /dev", disk
.syspath()),
972 let fs_type
= fs_type
.to_string();
974 let mut command
= std
::process
::Command
::new("mkfs");
975 command
.args(&["-t", &fs_type
]);
976 command
.arg(disk_path
);
978 pbs_tools
::run_command(command
, None
)?
;
983 /// Block device name completion helper
984 pub fn complete_disk_name(_arg
: &str, _param
: &HashMap
<String
, String
>) -> Vec
<String
> {
985 let mut list
= Vec
::new();
987 let dir
= match pbs_tools
::fs
::scan_subdir(libc
::AT_FDCWD
, "/sys/block", &BLOCKDEVICE_NAME_REGEX
) {
989 Err(_
) => return list
,
993 if let Ok(item
) = item
{
994 let name
= item
.file_name().to_str().unwrap().to_string();
1002 /// Read the FS UUID (parse blkid output)
1004 /// Note: Calling blkid is more reliable than using the udev ID_FS_UUID property.
1005 pub fn get_fs_uuid(disk
: &Disk
) -> Result
<String
, Error
> {
1007 let disk_path
= match disk
.device_path() {
1009 None
=> bail
!("disk {:?} has no node in /dev", disk
.syspath()),
1012 let mut command
= std
::process
::Command
::new("blkid");
1013 command
.args(&["-o", "export"]);
1014 command
.arg(disk_path
);
1016 let output
= pbs_tools
::run_command(command
, None
)?
;
1018 for line
in output
.lines() {
1019 if let Some(uuid
) = line
.strip_prefix("UUID=") {
1020 return Ok(uuid
.to_string());
1024 bail
!("get_fs_uuid failed - missing UUID");