storage: Use udisks to list disks and df for disk space utilization

- Fetch disk information for all disks using udisks
- Call df as superuser so that all disks are listed (udisks doesn't need sudo)
- Improved implementation to check if device is removable

Signed-off-by: Joseph Nuthalapati <njoseph@thoughtworks.com>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Joseph Nuthalapati 2019-04-03 18:07:20 +05:30 committed by James Valleroy
parent 0ec1d7cbb2
commit a2d07fef0b
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
3 changed files with 61 additions and 63 deletions

View File

@ -48,6 +48,9 @@ def parse_arguments():
subparser = subparsers.add_parser('eject', help='Eject a storage device')
subparser.add_argument('device', help='Path of the device to eject')
subparsers.add_parser('usage-info',
help='Get information about disk space usage')
subparsers.required = True
return parser.parse_args()
@ -301,6 +304,15 @@ def umount_all_filesystems_of_drive(drive_object_path):
umount_filesystem(obj.get_filesystem())
def subcommand_usage_info(_):
"""Get information about disk space usage."""
command = [
'df', '--exclude-type=tmpfs', '--exclude-type=devtmpfs',
'--block-size=1', '--output=source,fstype,size,used,avail,pcent,target'
]
subprocess.run(command, check=True)
def main():
"""Parse arguments and perform all duties."""
arguments = parse_arguments()

View File

@ -17,7 +17,6 @@
"""
FreedomBox app to manage storage.
"""
import json
import logging
import subprocess
@ -26,11 +25,10 @@ from django.utils.translation import ugettext_lazy as _
from plinth import action_utils, actions, cfg
from plinth import service as service_module
from plinth import utils
from plinth.errors import PlinthError
from plinth.menu import main_menu
from plinth.utils import format_lazy, import_from_gi, is_user_admin
from .manifest import backup
from plinth.utils import format_lazy, import_from_gi
version = 3
@ -65,37 +63,59 @@ def init():
def get_disks():
"""Returns list of disks by combining information from df and lsblk."""
disks = _get_disks_from_udisks()
disks_from_df = _get_disks_from_df()
disks_from_lsblk = _get_disks_from_lsblk()
# Combine both sources of info dicts into one dict, based on mount point;
# note this discards disks without a (current) mount point.
combined_list = []
for disk_from_df in disks_from_df:
for disk_from_lsblk in disks_from_lsblk:
if disk_from_df['mount_point'] == disk_from_lsblk['mountpoint']:
disk_from_df.update(disk_from_lsblk)
combined_list.append(disk_from_df)
for disk in combined_list:
disk['is_removable'] = is_removable_device(disk)
# Add size info from df to the disks from udisks based on mount point.
for disk_from_udisks in disks:
for disk_from_df in disks_from_df:
if disk_from_udisks['mount_point'] == disk_from_df['mount_point']:
disk_from_udisks.update(disk_from_df)
return combined_list
return sorted(disks, key=lambda disk: disk['device'])
def _get_disks_from_udisks():
"""List devices that can be ejected."""
udisks = utils.import_from_gi('UDisks', '2.0')
client = udisks.Client.new_sync()
object_manager = client.get_object_manager()
devices = []
for obj in object_manager.get_objects():
if not obj.get_block():
continue
block = obj.get_block()
if block.props.id_usage != 'filesystem':
continue
device = {
'device': block.props.device,
'label': block.props.id_label,
'size': format_bytes(block.props.size),
'filesystem_type': block.props.id_type,
'is_removable': not block.props.hint_system
}
try:
device['mount_point'] = obj.get_filesystem().props.mount_points[0]
except Exception:
continue
devices.append(device)
return devices
def _get_disks_from_df():
"""Return the list of disks and free space available using 'df'."""
command = [
'df', '--exclude-type=tmpfs', '--exclude-type=devtmpfs',
'--block-size=1', '--output=source,fstype,size,used,avail,pcent,target'
]
try:
process = subprocess.run(command, stdout=subprocess.PIPE, check=True)
output = actions.superuser_run('storage', ['usage-info'])
except subprocess.CalledProcessError as exception:
logger.exception('Error getting disk information: %s', exception)
return []
output = process.stdout.decode()
disks = []
for line in output.splitlines()[1:]:
parts = line.split(maxsplit=6)
@ -114,25 +134,6 @@ def _get_disks_from_df():
return disks
def _get_disks_from_lsblk():
"""Return the list of disks and other information from 'lsblk'."""
command = [
'lsblk', '--json', '--output', 'label,kname,pkname,mountpoint,type'
]
try:
process = subprocess.run(command, stdout=subprocess.PIPE, check=True)
except subprocess.CalledProcessError as exception:
logger.exception('Error getting disk information: %s', exception)
return []
output = process.stdout.decode()
disks = json.loads(output)['blockdevices']
for disk in disks:
disk['dev_kname'] = '/dev/{0}'.format(disk['kname'])
return disks
def get_filesystem_type(mount_point='/'):
"""Returns the type of the filesystem mounted at mountpoint."""
for partition in psutil.disk_partitions():
@ -145,7 +146,7 @@ def get_filesystem_type(mount_point='/'):
def get_disk_info(mount_point):
"""Get information about the free space of a drive"""
disks = get_disks()
list_root = [disk for disk in disks if disk['mountpoint'] == mount_point]
list_root = [disk for disk in disks if disk['mount_point'] == mount_point]
if not list_root:
raise PlinthError('Mount point {} not found.'.format(mount_point))
@ -162,26 +163,11 @@ def get_disk_info(mount_point):
def get_root_device(disks):
"""Return the root partition's device from list of partitions."""
for disk in disks:
if is_root_partition(disk):
return disk['dev_kname']
if disk['mount_point'] == '/':
return disk['device']
return None
def is_removable_device(disk):
"""Return whether a given disk is a removable device or not."""
return not (is_root_partition(disk) or is_boot_partition(disk))
def is_root_partition(disk):
"""Returns whether this disk is mounted at /."""
return disk['mountpoint'] == '/'
def is_boot_partition(disk):
"""Returns whether this disk is mounted at /boot."""
return disk['mountpoint'] == '/boot'
def is_expandable(device):
"""Return the list of partitions that can be expanded."""
if not device:

View File

@ -66,10 +66,10 @@
<tbody>
{% for disk in disks %}
<tr>
<td>{{ disk.dev_kname }}</td>
<td>{{ disk.device }}</td>
<td>{{ disk.label|default_if_none:"" }}</td>
<td>{{ disk.mount_point }}</td>
<td>{{ disk.file_system_type }}</td>
<td>{{ disk.filesystem_type }}</td>
<td>
<div class="progress">
{% if disk.percent_used < 75 %}
@ -90,7 +90,7 @@
<td>
{% if disk.is_removable %}
<form class="form" method="post"
action="{% url 'storage:eject' disk.dev_kname|urlencode:'' %}">
action="{% url 'storage:eject' disk.device|urlencode:'' %}">
{% csrf_token %}
<button type="submit"