From ed09028fcd2c850c3b87b65de66187b214190150 Mon Sep 17 00:00:00 2001 From: Joseph Nuthalapati Date: Fri, 26 Oct 2018 18:34:09 +0530 Subject: [PATCH] udiskie: unmount drive as superuser Since storage devices are auto-mounted as root, they also need to be unmounted as root. The assumption here is that this wouldn't have any impact on being able to write to the devices. Fixes #1411 Signed-off-by: Joseph Nuthalapati Reviewed-by: James Valleroy --- actions/storage | 88 +++++++++++++++++++++++++++++-- plinth/modules/storage/udisks2.py | 70 ------------------------ plinth/modules/storage/views.py | 5 +- 3 files changed, 89 insertions(+), 74 deletions(-) diff --git a/actions/storage b/actions/storage index 83980c6a3..de43e6a62 100755 --- a/actions/storage +++ b/actions/storage @@ -15,16 +15,18 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # - """ Configuration helper for disks manager. """ import argparse +import json import re import subprocess import sys +from plinth import utils + def parse_arguments(): """Return parsed command line arguments as dictionary.""" @@ -40,8 +42,11 @@ def parse_arguments(): subparser = subparsers.add_parser( 'expand-partition', help='Expand a partition to take adjacent free space') - subparser.add_argument( - 'device', help='Partition which needs to be resized') + subparser.add_argument('device', + help='Partition which needs to be resized') + + subparser = subparsers.add_parser('eject', help='Eject a storage device') + subparser.add_argument('device', help='Path of the device to eject') subparsers.required = True return parser.parse_args() @@ -219,6 +224,83 @@ def _interpret_unit(value): return int(value) +def subcommand_eject(arguments): + """Eject a device by its path.""" + device_path = arguments.device + drive = eject_drive_of_device(device_path) + print(json.dumps(drive)) + + +def _get_options(): + """Return the common options used for udisks2 operations.""" + glib = utils.import_from_gi('GLib', '2.0') + options = glib.Variant( + 'a{sv}', {'auth.no_user_interaction': glib.Variant('b', True)}) + return options + + +def eject_drive_of_device(device_path): + """Eject a device after unmounting all of its partitions. + + Return the details (model, vendor) of drives ejected. + """ + udisks = utils.import_from_gi('UDisks', '2.0') + client = udisks.Client.new_sync() + object_manager = client.get_object_manager() + + found_objects = [ + obj for obj in object_manager.get_objects() + if obj.get_block() and obj.get_block().props.device == device_path + ] + + if not found_objects: + raise ValueError( + _('No such device - {device_path}').format( + device_path=device_path)) + + obj = found_objects[0] + + # Unmount filesystems + block_device = obj.get_block() + drive_object_path = block_device.props.drive + if drive_object_path != '/': + umount_all_filesystems_of_drive(drive_object_path) + else: + # Block device has not associated drive + umount_filesystem(obj.get_filesystem()) + + # Eject the drive + drive = client.get_drive_for_block(block_device) + if drive: + drive.call_eject_sync(_get_options(), None) + return { + 'vendor': drive.props.vendor, + 'model': drive.props.model, + } + + return None + + +def umount_filesystem(filesystem): + """Unmount a filesystem """ + if filesystem and filesystem.props.mount_points: + filesystem.call_unmount_sync(_get_options()) + + +def umount_all_filesystems_of_drive(drive_object_path): + """Unmount all filesystems on block devices of a drive.""" + udisks = utils.import_from_gi('UDisks', '2.0') + client = udisks.Client.new_sync() + object_manager = client.get_object_manager() + + for obj in object_manager.get_objects(): + block_device = obj.get_block() + if not block_device or block_device.props.drive != drive_object_path: + continue + + umount_filesystem(obj.get_filesystem()) + + def main(): """Parse arguments and perform all duties.""" arguments = parse_arguments() diff --git a/plinth/modules/storage/udisks2.py b/plinth/modules/storage/udisks2.py index 331f5c0f5..85022f5b5 100644 --- a/plinth/modules/storage/udisks2.py +++ b/plinth/modules/storage/udisks2.py @@ -24,14 +24,6 @@ from plinth import utils from plinth.modules.storage import format_bytes -def _get_options(): - """Return the common options used for udisks2 operations.""" - glib = utils.import_from_gi('GLib', '2.0') - options = glib.Variant( - 'a{sv}', {'auth.no_user_interaction': glib.Variant('b', True)}) - return options - - def list_devices(): """List devices that can be ejected.""" udisks = utils.import_from_gi('UDisks', '2.0') @@ -70,68 +62,6 @@ def list_devices(): return devices -def eject_drive_of_device(device_path): - """Eject a device after unmounting all of its partitions. - - Return the details (model, vendor) of drives ejected. - """ - udisks = utils.import_from_gi('UDisks', '2.0') - client = udisks.Client.new_sync() - object_manager = client.get_object_manager() - - found_objects = [ - obj for obj in object_manager.get_objects() - if obj.get_block() and obj.get_block().props.device == device_path - ] - - if not found_objects: - raise ValueError( - _('No such device - {device_path}').format( - device_path=device_path)) - - obj = found_objects[0] - - # Unmount filesystems - block_device = obj.get_block() - drive_object_path = block_device.props.drive - if drive_object_path != '/': - umount_all_filesystems_of_drive(drive_object_path) - else: - # Block device has not associated drive - umount_filesystem(obj.get_filesystem()) - - # Eject the drive - drive = client.get_drive_for_block(block_device) - if drive: - drive.call_eject_sync(_get_options(), None) - return { - 'vendor': drive.props.vendor, - 'model': drive.props.model, - } - - return None - - -def umount_filesystem(filesystem): - """Unmount a filesystem """ - if filesystem and filesystem.props.mount_points: - filesystem.call_unmount_sync(_get_options()) - - -def umount_all_filesystems_of_drive(drive_object_path): - """Unmount all filesystems on block devices of a drive.""" - udisks = utils.import_from_gi('UDisks', '2.0') - client = udisks.Client.new_sync() - object_manager = client.get_object_manager() - - for obj in object_manager.get_objects(): - block_device = obj.get_block() - if not block_device or block_device.props.drive != drive_object_path: - continue - - umount_filesystem(obj.get_filesystem()) - - def get_error_message(error): """Return an error message given an exception.""" udisks = utils.import_from_gi('UDisks', '2.0') diff --git a/plinth/modules/storage/views.py b/plinth/modules/storage/views.py index 80db7f7f0..248aab9a2 100644 --- a/plinth/modules/storage/views.py +++ b/plinth/modules/storage/views.py @@ -18,6 +18,7 @@ Views for storage module. """ +import json import logging import urllib.parse @@ -28,6 +29,7 @@ from django.urls import reverse from django.utils.translation import ugettext as _ from django.views.decorators.http import require_POST +from plinth import actions from plinth.modules import storage from plinth.utils import format_lazy, is_user_admin @@ -125,7 +127,8 @@ def eject(request, device_path): device_path = urllib.parse.unquote(device_path) try: - drive = udisks2.eject_drive_of_device(device_path) + drive = json.loads( + actions.superuser_run('storage', ['eject', device_path])) if drive: messages.success( request,