Commit 7f65944e authored by rgaudin's avatar rgaudin
Browse files

Loop device permission (mode) change on Linux

On Linux, loop-device setup, mount, unmount and removal is done in userspace thanks to udisks2.
Now that we format the third partition on the host (before copying large files into it),
we need to have the permission to mkfs on the loop device.

At this step, the following now applies:

	check wether we can write on loop device (Sys requirements advises user to be member of the `disk` group)
	change mode if we can't (requesting elevation)
	restore mode after we've formatted (requesting elevation – usually before previous elevation expiration)

Additionaly, to prevent unexpected request for elevation at the format step (usually 30-90mn into the process), on startup:
	attempt to guess loop device path on start. This might fail or be wrong (another app uses the guessed loop before the format step) but that should be rare.
	change mode of guessed loop device (along with SD card, so a single request right on startup)
	restore mode at end of process (along with SD card)

	if we could not properly guess, elevation will be requested at format step.

Also enforcing usage of logger everywhere (subprocess calls for mount) as missing logger leads to painful diagnostics
parent 95c94d8f
......@@ -75,7 +75,7 @@ def get_partition_size(image_fpath, start_bytes, logger):
return full_size - start_bytes
def install_imdisk(logger=None, force=False):
def install_imdisk(logger, force=False):
''' install imdisk manually (replicating steps from install.cmd) '''
# assume already installed
......@@ -116,7 +116,7 @@ def install_imdisk(logger=None, force=False):
.format(" ".join(failed)))
def install_imdisk_via_cmd(logger=None):
def install_imdisk_via_cmd(logger):
''' install imdisk via its .cmd file (silent mode)
doesn't provide much feedback '''
......@@ -133,7 +133,7 @@ def install_imdisk_via_cmd(logger=None):
os.chdir(cwd)
def uninstall_imdisk(logger=None):
def uninstall_imdisk(logger):
''' uninstall imdisk using its uninstaller script '''
# set silent variable to prevent popup
......@@ -149,7 +149,7 @@ def uninstall_imdisk(logger=None):
os.chdir(cwd)
def get_avail_drive_letter(logger=None):
def get_avail_drive_letter(logger):
''' returns a free Windows drive letter to mount image in '''
if not sys.platform == "win32":
......@@ -177,7 +177,59 @@ def get_avail_drive_letter(logger=None):
return "{}:".format(letter)
def test_mount_procedure(image_fpath, logger=None, thorough=False):
def can_write_on(fpath):
''' whether user can read+write on specified file (used with loops) '''
return os.access(fpath, os.R_OK | os.W_OK)
def allow_write_on(fpath, logger):
''' sets o+rw on path and return previous perms for others (0755) '''
si = os.stat(fpath)
subprocess_pretty_call(['chmod', '-v', 'o+rw', fpath], logger,
check=True, as_admin=True)
return oct(si.st_mode)[-4:]
def restore_mode(fpath, mode, logger):
''' sets specified mode to specified file '''
subprocess_pretty_call(['chmod', '-v', mode, fpath], logger,
check=True, as_admin=True)
def guess_next_loop_device(logger):
try:
lines = subprocess_pretty_call([udisksctl_exe, 'dump'], logger,
check=True, decode=True)
except Exception as exp:
logger.err(exp)
return None
devnum = None # loopX
devtype = None # Block
for line in lines:
if line.startswith('/'):
try:
devnum = re.search(r'^.*loop([0-9]+)\:$', line).groups()[0]
except Exception:
devnum = None
if line.startswith(' org'):
try:
devtype = re.search(r'\.([a-zA-Z]+)\:$', line).groups()[0]
except Exception:
devtype = None
if devnum and devtype == 'Block' and line.startswith(' Size:'):
size = int(re.search(r'\s+Size\:\s+([0-9]+)$', line).groups()[0])
if size == 0:
return "/dev/loop{}".format(devnum)
def test_mount_procedure(image_fpath, logger, thorough=False):
''' whether we are able to mount and unmount data partition
usefull to ensure setup is OK before starting process
......@@ -215,7 +267,7 @@ def test_mount_procedure(image_fpath, logger=None, thorough=False):
pass # failed to unmount (outch)
def get_virtual_device(image_fpath, logger=None):
def get_virtual_device(image_fpath, logger):
''' create and return a loop device or drive letter we can format/mount '''
if sys.platform == "linux":
......@@ -256,7 +308,7 @@ def get_virtual_device(image_fpath, logger=None):
return target_dev
def format_data_partition(image_fpath, logger=None):
def format_data_partition(image_fpath, logger):
''' format the QEMU image's 3rd part in exfat on host '''
target_dev = get_virtual_device(image_fpath, logger)
......@@ -267,15 +319,22 @@ def format_data_partition(image_fpath, logger=None):
[udisksctl_exe, 'unmount',
'--block-device', target_dev, udisks_nou], logger)
# change mode via elevation if we can't format it
previous_mode = None
if not can_write_on(target_dev):
previous_mode = allow_write_on(target_dev, logger)
# format the data partition
try:
subprocess_pretty_check_call(
[mkfs_exe, '-n', 'data', target_dev], logger)
except Exception:
raise
finally:
# remove write rights we just added
if previous_mode:
restore_mode(target_dev, previous_mode, logger)
# ensure we release the loop device on mount failure
unmount_data_partition(None, target_dev)
unmount_data_partition(None, target_dev, logger)
elif sys.platform == "darwin":
target_part = "{dev}s3".format(dev=target_dev)
......@@ -283,11 +342,9 @@ def format_data_partition(image_fpath, logger=None):
try:
subprocess_pretty_check_call(
[diskutil_exe, 'eraseVolume', 'exfat', 'data', target_part], logger)
except Exception:
raise
finally:
# ensure we release the loop device on mount failure
unmount_data_partition(None, target_dev)
unmount_data_partition(None, target_dev, logger)
elif sys.platform == "win32":
# mount into specified path AND format
......@@ -298,14 +355,12 @@ def format_data_partition(image_fpath, logger=None):
'-v', '3',
'-p', '/fs:exfat /V:data /q /y',
'-m', target_dev], logger)
except Exception:
raise
finally:
# ensure we release the loop device on mount failure
unmount_data_partition(None, target_dev)
unmount_data_partition(None, target_dev, logger)
def mount_data_partition(image_fpath, logger=None):
def mount_data_partition(image_fpath, logger):
''' mount the QEMU image's 3rd part and return its mount point/drive '''
target_dev = get_virtual_device(image_fpath, logger)
......@@ -341,7 +396,7 @@ def mount_data_partition(image_fpath, logger=None):
[mount_exe, '-t', 'exfat', target_part, mount_point], logger)
except Exception:
# ensure we release the loop device on mount failure
unmount_data_partition(mount_point, target_dev)
unmount_data_partition(mount_point, target_dev, logger)
raise
return mount_point, target_dev
......@@ -355,7 +410,7 @@ def mount_data_partition(image_fpath, logger=None):
return mount_point, target_dev
def unmount_data_partition(mount_point, device, logger=None):
def unmount_data_partition(mount_point, device, logger):
''' unmount data partition and free virtual resources '''
if sys.platform == "linux":
......
......@@ -23,7 +23,7 @@ def startup_info_args():
return {'startupinfo': si}
def subprocess_pretty_call(cmd, logger=None, stdin=None,
def subprocess_pretty_call(cmd, logger, stdin=None,
check=False, decode=False, as_admin=False):
''' flexible subprocess helper running separately and using the logger
......
......@@ -4,7 +4,8 @@ from backend.content import (get_collection, get_content,
get_all_contents_for, isremote, get_content_cache,
get_alien_content)
from backend.download import download_content, unzip_file
from backend.mount import mount_data_partition, unmount_data_partition, test_mount_procedure, format_data_partition
from backend.mount import mount_data_partition, unmount_data_partition, test_mount_procedure, format_data_partition, guess_next_loop_device
from backend.mount import can_write_on, allow_write_on, restore_mode
from backend.util import subprocess_pretty_check_call, subprocess_pretty_call
from backend.sysreq import host_matches_requirements, requirements_url
import data
......@@ -41,16 +42,23 @@ def run_installation(name, timezone, language, wifi_pwd, admin_account, kalite,
image_building_path = os.path.join(build_dir, "plug-{}.BUILDING.img".format(today))
image_error_path = os.path.join(build_dir, "plug-{}.ERROR.img".format(today))
# loop device mode on linux (for mkfs in userspace)
if sys.platform == "linux":
loop_dev = guess_next_loop_device(logger)
if loop_dev and not can_write_on(loop_dev):
logger.step("Change loop device mode ({})".format(sd_card))
previous_loop_mode = allow_write_on(loop_dev, logger)
else:
previous_loop_mode = None
# Prepare SD Card
if sd_card:
logger.step("Change {} ownership".format(sd_card))
logger.step("Change SD-card device mode ({})".format(sd_card))
if sys.platform == "linux":
subprocess_pretty_check_call(
["chmod", "-c", "o+w", sd_card], logger, as_admin=True)
allow_write_on(sd_card, logger)
elif sys.platform == "darwin":
subprocess_pretty_check_call(["diskutil", "unmountDisk", sd_card], logger)
subprocess_pretty_check_call(
["chmod", "-v", "o+w", sd_card], logger, as_admin=True)
allow_write_on(sd_card, logger)
elif sys.platform == "win32":
logger.step("Format SD card {}".format(sd_card))
matches = re.findall(r"\\\\.\\PHYSICALDRIVE(\d*)", sd_card)
......@@ -92,7 +100,8 @@ def run_installation(name, timezone, language, wifi_pwd, admin_account, kalite,
.format(image_building_path))
logger.step("Testing mount procedure")
test_mount_procedure(image_building_path, logger, thorough=True)
if not test_mount_procedure(image_building_path, logger, True):
raise ValueError("thorough mount procedure failed")
# harmonize options
packages = [] if zim_install is None else zim_install
......@@ -278,13 +287,17 @@ def run_installation(name, timezone, language, wifi_pwd, admin_account, kalite,
error = None
finally:
if sys.platform == "linux" and loop_dev and previous_loop_mode:
logger.step("Restoring loop device ({}) mode".format(loop_dev))
restore_mode(loop_dev, previous_loop_mode, logger)
if sd_card:
logger.step("Restoring SD-card device ({}) mode".format(sd_card))
if sys.platform == "linux":
subprocess_pretty_call(
["chmod", "-c", "o-w", sd_card], logger, as_admin=True)
restore_mode(sd_card, '0660', logger)
elif sys.platform == "darwin":
subprocess_pretty_call(
["chmod", "-v", "o-w", sd_card], logger, as_admin=True)
restore_mode(sd_card, '0660', logger)
# display durations summary
logger.summary()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment