2022-07-25 14:08:31 +00:00
|
|
|
"""Wrapper for block device functions."""
|
|
|
|
import subprocess
|
|
|
|
import json
|
|
|
|
import typing
|
|
|
|
|
2022-07-30 14:48:33 +00:00
|
|
|
from selfprivacy_api.utils import WriteUserData
|
2022-10-27 14:01:11 +00:00
|
|
|
from selfprivacy_api.utils.singleton_metaclass import SingletonMetaclass
|
2022-07-30 14:48:33 +00:00
|
|
|
|
2022-07-25 14:08:31 +00:00
|
|
|
|
|
|
|
def get_block_device(device_name):
|
|
|
|
"""
|
|
|
|
Return a block device by name.
|
|
|
|
"""
|
|
|
|
lsblk_output = subprocess.check_output(
|
|
|
|
[
|
|
|
|
"lsblk",
|
|
|
|
"-J",
|
|
|
|
"-b",
|
|
|
|
"-o",
|
2022-08-25 17:03:56 +00:00
|
|
|
"NAME,PATH,FSAVAIL,FSSIZE,FSTYPE,FSUSED,MOUNTPOINTS,LABEL,UUID,SIZE,MODEL,SERIAL,TYPE",
|
|
|
|
f"/dev/{device_name}",
|
2022-07-25 14:08:31 +00:00
|
|
|
]
|
|
|
|
)
|
|
|
|
lsblk_output = lsblk_output.decode("utf-8")
|
|
|
|
lsblk_output = json.loads(lsblk_output)
|
2022-08-25 17:03:56 +00:00
|
|
|
return lsblk_output["blockdevices"][0]
|
2022-07-25 14:08:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
def resize_block_device(block_device) -> bool:
|
|
|
|
"""
|
|
|
|
Resize a block device. Return True if successful.
|
|
|
|
"""
|
|
|
|
resize_command = ["resize2fs", block_device]
|
2022-08-25 17:03:56 +00:00
|
|
|
try:
|
|
|
|
subprocess.check_output(resize_command, shell=False)
|
|
|
|
except subprocess.CalledProcessError:
|
|
|
|
return False
|
|
|
|
return True
|
2022-07-25 14:08:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
class BlockDevice:
|
|
|
|
"""
|
|
|
|
A block device.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, block_device):
|
|
|
|
self.name = block_device["name"]
|
|
|
|
self.path = block_device["path"]
|
2022-08-25 17:03:56 +00:00
|
|
|
self.fsavail = str(block_device["fsavail"])
|
|
|
|
self.fssize = str(block_device["fssize"])
|
2022-07-25 14:08:31 +00:00
|
|
|
self.fstype = block_device["fstype"]
|
2022-08-25 17:03:56 +00:00
|
|
|
self.fsused = str(block_device["fsused"])
|
|
|
|
self.mountpoints = block_device["mountpoints"]
|
2022-07-25 14:08:31 +00:00
|
|
|
self.label = block_device["label"]
|
|
|
|
self.uuid = block_device["uuid"]
|
2022-08-25 17:03:56 +00:00
|
|
|
self.size = str(block_device["size"])
|
2022-07-30 14:48:33 +00:00
|
|
|
self.model = block_device["model"]
|
|
|
|
self.serial = block_device["serial"]
|
|
|
|
self.type = block_device["type"]
|
2022-07-25 14:08:31 +00:00
|
|
|
self.locked = False
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
def __repr__(self):
|
2022-08-25 17:03:56 +00:00
|
|
|
return f"<BlockDevice {self.name} of size {self.size} mounted at {self.mountpoints}>"
|
2022-07-25 14:08:31 +00:00
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
return self.name == other.name
|
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
return hash(self.name)
|
|
|
|
|
|
|
|
def stats(self) -> typing.Dict[str, typing.Any]:
|
|
|
|
"""
|
|
|
|
Update current data and return a dictionary of stats.
|
|
|
|
"""
|
|
|
|
device = get_block_device(self.name)
|
2022-08-25 17:03:56 +00:00
|
|
|
self.fsavail = str(device["fsavail"])
|
|
|
|
self.fssize = str(device["fssize"])
|
2022-07-25 14:08:31 +00:00
|
|
|
self.fstype = device["fstype"]
|
2022-08-25 17:03:56 +00:00
|
|
|
self.fsused = str(device["fsused"])
|
|
|
|
self.mountpoints = device["mountpoints"]
|
2022-07-25 14:08:31 +00:00
|
|
|
self.label = device["label"]
|
|
|
|
self.uuid = device["uuid"]
|
2022-08-25 17:03:56 +00:00
|
|
|
self.size = str(device["size"])
|
2022-07-30 14:48:33 +00:00
|
|
|
self.model = device["model"]
|
|
|
|
self.serial = device["serial"]
|
|
|
|
self.type = device["type"]
|
2022-07-25 14:08:31 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
"name": self.name,
|
|
|
|
"path": self.path,
|
|
|
|
"fsavail": self.fsavail,
|
|
|
|
"fssize": self.fssize,
|
|
|
|
"fstype": self.fstype,
|
|
|
|
"fsused": self.fsused,
|
2022-08-25 17:03:56 +00:00
|
|
|
"mountpoints": self.mountpoints,
|
2022-07-25 14:08:31 +00:00
|
|
|
"label": self.label,
|
|
|
|
"uuid": self.uuid,
|
|
|
|
"size": self.size,
|
2022-07-30 14:48:33 +00:00
|
|
|
"model": self.model,
|
|
|
|
"serial": self.serial,
|
|
|
|
"type": self.type,
|
2022-07-25 14:08:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
def resize(self):
|
|
|
|
"""
|
|
|
|
Resize the block device.
|
|
|
|
"""
|
|
|
|
if not self.locked:
|
|
|
|
self.locked = True
|
|
|
|
resize_block_device(self.path)
|
|
|
|
self.locked = False
|
|
|
|
|
2022-07-30 14:48:33 +00:00
|
|
|
def mount(self) -> bool:
|
|
|
|
"""
|
|
|
|
Mount the block device.
|
|
|
|
"""
|
|
|
|
with WriteUserData() as user_data:
|
|
|
|
if "volumes" not in user_data:
|
|
|
|
user_data["volumes"] = []
|
|
|
|
# Check if the volume is already mounted
|
|
|
|
for volume in user_data["volumes"]:
|
|
|
|
if volume["device"] == self.path:
|
|
|
|
return False
|
|
|
|
user_data["volumes"].append(
|
|
|
|
{
|
|
|
|
"device": self.path,
|
|
|
|
"mountPoint": f"/volumes/{self.name}",
|
|
|
|
"fsType": self.fstype,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
return True
|
|
|
|
|
|
|
|
def unmount(self) -> bool:
|
|
|
|
"""
|
|
|
|
Unmount the block device.
|
|
|
|
"""
|
|
|
|
with WriteUserData() as user_data:
|
|
|
|
if "volumes" not in user_data:
|
|
|
|
user_data["volumes"] = []
|
|
|
|
# Check if the volume is already mounted
|
|
|
|
for volume in user_data["volumes"]:
|
|
|
|
if volume["device"] == self.path:
|
|
|
|
user_data["volumes"].remove(volume)
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2022-07-25 14:08:31 +00:00
|
|
|
|
2022-10-27 14:01:11 +00:00
|
|
|
class BlockDevices(metaclass=SingletonMetaclass):
|
2022-07-25 14:08:31 +00:00
|
|
|
"""Singleton holding all Block devices"""
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.block_devices = []
|
|
|
|
self.update()
|
|
|
|
|
|
|
|
def update(self) -> None:
|
|
|
|
"""
|
|
|
|
Update the list of block devices.
|
|
|
|
"""
|
|
|
|
devices = []
|
|
|
|
lsblk_output = subprocess.check_output(
|
|
|
|
[
|
|
|
|
"lsblk",
|
|
|
|
"-J",
|
|
|
|
"-b",
|
|
|
|
"-o",
|
2022-08-25 17:03:56 +00:00
|
|
|
"NAME,PATH,FSAVAIL,FSSIZE,FSTYPE,FSUSED,MOUNTPOINTS,LABEL,UUID,SIZE,MODEL,SERIAL,TYPE",
|
2022-07-25 14:08:31 +00:00
|
|
|
]
|
|
|
|
)
|
|
|
|
lsblk_output = lsblk_output.decode("utf-8")
|
|
|
|
lsblk_output = json.loads(lsblk_output)
|
|
|
|
for device in lsblk_output["blockdevices"]:
|
2022-07-30 14:48:33 +00:00
|
|
|
# Ignore devices with type "rom"
|
|
|
|
if device["type"] == "rom":
|
|
|
|
continue
|
2022-07-25 14:08:31 +00:00
|
|
|
if device["fstype"] is None:
|
|
|
|
if "children" in device:
|
|
|
|
for child in device["children"]:
|
|
|
|
if child["fstype"] == "ext4":
|
|
|
|
device = child
|
|
|
|
break
|
|
|
|
devices.append(device)
|
|
|
|
# Add new devices and delete non-existent devices
|
|
|
|
for device in devices:
|
|
|
|
if device["name"] not in [
|
|
|
|
block_device.name for block_device in self.block_devices
|
|
|
|
]:
|
|
|
|
self.block_devices.append(BlockDevice(device))
|
|
|
|
for block_device in self.block_devices:
|
|
|
|
if block_device.name not in [device["name"] for device in devices]:
|
|
|
|
self.block_devices.remove(block_device)
|
|
|
|
|
|
|
|
def get_block_device(self, name: str) -> typing.Optional[BlockDevice]:
|
|
|
|
"""
|
|
|
|
Return a block device by name.
|
|
|
|
"""
|
|
|
|
for block_device in self.block_devices:
|
|
|
|
if block_device.name == name:
|
|
|
|
return block_device
|
|
|
|
return None
|
|
|
|
|
|
|
|
def get_block_devices(self) -> typing.List[BlockDevice]:
|
|
|
|
"""
|
|
|
|
Return a list of block devices.
|
|
|
|
"""
|
|
|
|
return self.block_devices
|
|
|
|
|
|
|
|
def get_block_devices_by_mountpoint(
|
|
|
|
self, mountpoint: str
|
|
|
|
) -> typing.List[BlockDevice]:
|
|
|
|
"""
|
|
|
|
Return a list of block devices with a given mountpoint.
|
|
|
|
"""
|
|
|
|
block_devices = []
|
|
|
|
for block_device in self.block_devices:
|
2022-08-25 17:03:56 +00:00
|
|
|
if mountpoint in block_device.mountpoints:
|
2022-07-25 14:08:31 +00:00
|
|
|
block_devices.append(block_device)
|
|
|
|
return block_devices
|
2023-07-27 23:31:28 +00:00
|
|
|
|
|
|
|
def get_root_block_device(self) -> BlockDevice:
|
|
|
|
"""
|
|
|
|
Return the root block device.
|
|
|
|
"""
|
|
|
|
for block_device in self.block_devices:
|
|
|
|
if "/" in block_device.mountpoints:
|
|
|
|
return block_device
|
|
|
|
raise RuntimeError("No root block device found")
|