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 <njoseph@thoughtworks.com>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Joseph Nuthalapati 2018-10-26 18:34:09 +05:30 committed by James Valleroy
parent c2dc2cb2e2
commit ed09028fcd
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
3 changed files with 89 additions and 74 deletions

View File

@ -15,16 +15,18 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
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()

View File

@ -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')

View File

@ -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,