def get_avail_drive_letter(logger): """ returns a free Windows drive letter to mount image in """ if not sys.platform == "win32": raise NotImplementedError("only for windows") # get volumes from wmic wmic_out = subprocess_pretty_call( ["wmic", "logicaldisk", "get", "caption"], logger, check=True, decode=True) volumes = [line.strip()[:-1] for line in wmic_out[1:-1]] # get list of network mappings net_out = subprocess_pretty_call(["net", "use"], logger, check=True, decode=True) reg = r"\s+([A-Z])\:\s+\\" net_maps = [ re.match(reg, line).groups()[0] for line in net_out if re.match(reg, line) ] # merge and sort both volumes and network shares used = sorted(list(set(["A", "B", "C"] + volumes + net_maps))) # find the next available letter in alphabet (should be free) for letter in string.ascii_uppercase: if letter not in list(used): return "{}:".format(letter)
def release_virtual_device(device, logger): if bool(os.getenv("NO_UDISKS", False)): subprocess_pretty_call([losetup_exe, "--detach", device], logger) else: subprocess_pretty_call([ udisksctl_exe, "loop-delete", "--block-device", device, udisks_nou ], logger)
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 install_imdisk(logger, force=False): """ install imdisk manually (replicating steps from install.cmd) """ # assume already installed logger.std("Looking for {}".format(imdisk_exe)) if os.path.exists(imdisk_exe) and not force: logger.std("ImDisk already present.") return logger.std("Imdisk IS NOT present. Installing...") # install the driver and files cwd = os.getcwd() try: os.chdir(imdiskinst) ret, _ = subprocess_pretty_call( [ "rundll32.exe", "setupapi.dll,InstallHinfSection", "DefaultInstall", "132", ".\\imdisk.inf", ], logger, ) except Exception: ret = 1 finally: os.chdir(cwd) if ret != 0: raise OSError("Unable to install ImDisk driver. " "Please install manually from the File menu.") # start services failed = [] for service in ("imdsksvc", "awealloc", "imdisk"): if subprocess_pretty_call(["net", "start", service], logger)[0] != 0: failed.append(service) if failed: raise OSError("ImDisk installed but some " "service/driver failed to start: {}.\n" "Please install manually from the File menu.".format( " ".join(failed))) assert os.path.exists(imdisk_exe)
def unmount_data_partition(mount_point, device, logger): """ unmount data partition and free virtual resources """ if sys.platform == "linux": # sleep to prevent unmount failures time.sleep(5) if mount_point: # unmount using device path if bool(os.getenv("NO_UDISKS", False)): subprocess_pretty_call([umount_exe, device], logger) else: subprocess_pretty_call( [ udisksctl_exe, "unmount", "--block-device", device, udisks_nou ], logger, ) try: os.rmdir(mount_point) except (FileNotFoundError, PermissionError): pass # delete the loop device (might have already been deleted) release_virtual_device(device, logger) elif sys.platform == "darwin": if mount_point: # unmount subprocess_pretty_call([umount_exe, mount_point], logger) try: os.rmdir(mount_point) except FileNotFoundError: pass # detach image file (also unmounts if not already done) subprocess_pretty_call([hdiutil_exe, "detach", device], logger) elif sys.platform == "win32": # unmount using force (-D) as -d is not reliable subprocess_pretty_call([imdisk_exe, "-D", "-m", device], logger)
def _no_udisks(logger): """ guess loop device w/o udisks (using losetup/root) """ try: lines = subprocess_pretty_call([losetup_exe, "--find"], logger, check=True) except Exception as exp: logger.err(exp) return None return re.search(r"(\/dev\/loop[0-9]+)$", lines[-1].strip()).groups()[0]
def guess_next_loop_device(logger): def _no_udisks(logger): """ guess loop device w/o udisks (using losetup/root) """ try: lines = subprocess_pretty_call([losetup_exe, "--find"], logger, check=True, decode=True) except Exception as exp: logger.err(exp) return None return re.search(r"(\/dev\/loop[0-9]+)$", lines[-1].strip()).groups()[0] if bool(os.getenv("NO_UDISKS", False)): return _no_udisks(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 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) if sys.platform == "linux" and bool(os.getenv("NO_UDISKS", False)): # create a mount point in /tmp mount_point = tempfile.mkdtemp() try: subprocess_pretty_check_call( [mount_exe, "-t", "exfat", target_dev, mount_point], logger) except Exception: # ensure we release the loop device on mount failure unmount_data_partition(mount_point, target_dev, logger) raise return mount_point, target_dev elif sys.platform == "linux": # mount the loop-device (udisksctl sets the mount point) udisks_mount_ret, udisks_mount = subprocess_pretty_call( [udisksctl_exe, "mount", "--block-device", target_dev, udisks_nou], logger, check=False, decode=True, ) udisks_mount = udisks_mount[0].strip() if udisks_mount_ret != 0 and "AlreadyMounted" in udisks_mount: # was automatically mounted (gnome default) mount_point = re.search(r"at `(\/media\/.*)'\.$", udisks_mount).groups()[0] elif udisks_mount_ret == 0: # udisksctl always mounts under /media/ mount_point = re.search(r"at (\/media\/.+)\.$", udisks_mount).groups()[0] else: release_virtual_device(target_dev, logger) # release loop if attached raise OSError("failed to mount {}".format(target_dev)) return mount_point, target_dev elif sys.platform == "darwin": target_part = "{dev}s3".format(dev=target_dev) # create a mount point in /tmp mount_point = tempfile.mkdtemp() try: subprocess_pretty_check_call( [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, logger) raise return mount_point, target_dev elif sys.platform == "win32": mount_point = "{}\\".format(target_dev) # mount into the specified drive subprocess_pretty_check_call( [ imdisk_exe, "-a", "-f", image_fpath, "-o", "rw", "-t", "file", "-v", "3", "-m", target_dev, ], logger, ) return mount_point, target_dev
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) if sys.platform == "linux": # make sure it's not mounted (gnome automounts) if bool(os.getenv("NO_UDISKS", False)): subprocess_pretty_call([umount_exe, target_dev], logger) else: subprocess_pretty_call( [ 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_partition_label, target_dev], logger) 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, logger) elif sys.platform == "darwin": target_part = "{dev}s3".format(dev=target_dev) try: subprocess_pretty_check_call( [ diskutil_exe, "eraseVolume", "exfat", data_partition_label, target_part, ], logger, ) finally: # ensure we release the loop device on mount failure unmount_data_partition(None, target_dev, logger) elif sys.platform == "win32": # mount into specified path AND format try: subprocess_pretty_check_call( [ imdisk_exe, "-a", "-f", image_fpath, "-o", "rw", "-t", "file", "-v", "3", "-p", "/fs:exfat /V:{} /q /y".format(data_partition_label), "-m", target_dev, ], logger, ) finally: # ensure we release the loop device on mount failure unmount_data_partition(None, target_dev, logger)
def get_virtual_device(image_fpath, logger): """ create and return a loop device or drive letter we can format/mount """ if sys.platform == "linux": # find out offset for third partition from the root part size base_image = get_content("hotspot_master_image") disk_size = get_qemu_image_size(image_fpath, logger) offset, size = get_start_offset(base_image.get("root_partition_size"), disk_size) # prepare loop device if bool(os.getenv("NO_UDISKS", False)): loop_maker = subprocess_pretty_call( [ "/sbin/losetup", "--offset", str(offset), "--sizelimit", str(size), "--find", "--show", image_fpath, ], logger, check=True, decode=True, )[0].strip() else: loop_maker = subprocess_pretty_call( [ udisksctl_exe, "loop-setup", "--offset", str(offset), "--size", str(size), "--file", image_fpath, udisks_nou, ], logger, check=True, decode=True, )[0].strip() target_dev = re.search(r"(\/dev\/loop[0-9]+)\.?$", loop_maker).groups()[0] elif sys.platform == "darwin": # attach image to create loop devices hdiutil_out = subprocess_pretty_call( [hdiutil_exe, "attach", "-nomount", image_fpath], logger, check=True, decode=True, )[0].strip() target_dev = str(hdiutil_out.splitlines()[0].split()[0]) elif sys.platform == "win32": # make sure we have imdisk installed install_imdisk(logger) # get an available letter target_dev = get_avail_drive_letter(logger) return target_dev
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)