diff --git a/build_pkg b/build_pkg old mode 100755 new mode 100644 diff --git a/data/multibootusb/grub.exe b/data/multibootusb/grub.exe old mode 100755 new mode 100644 diff --git a/multibootusb b/multibootusb old mode 100755 new mode 100644 diff --git a/scripts/config.py b/scripts/config.py index d6d1965..d4662a6 100644 --- a/scripts/config.py +++ b/scripts/config.py @@ -35,6 +35,7 @@ imager_usb_disk_selected = "" imager_lock = "" imager_percentage = "" imager_status_text = "" +imager_return = "" install_size = "" diff --git a/scripts/imager.py b/scripts/imager.py index 5862502..763360c 100644 --- a/scripts/imager.py +++ b/scripts/imager.py @@ -7,32 +7,43 @@ # under the terms of GNU General Public License, v.2 or above # WARNING : Any boot-able USB made using this module will destroy data stored on USB disk. -import os -import subprocess import collections +import io +import os import platform import signal +import time +import subprocess +import traceback + from PyQt5 import QtWidgets from .gui.ui_multibootusb import Ui_MainWindow from .gen import * -from . import iso from . import config +from . import iso +from . import osdriver from . import progressbar +from . import osdriver +from . import udisks +from . import usb + if platform.system() == "Windows": import win32com.client - -def dd_linux(): - import time - _input = "if=" + config.image_path - in_file_size = float(os.path.getsize(config.image_path)) - _output = "of=" + config.usb_disk - os.system("umount " + config.usb_disk + "1") - command = ['dd', _input, _output, "bs=1M", "oflag=sync"] - log("Executing ==> " + " ".join(command)) - dd_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False) - +def dd_iso_image(dd_progress_thread): + if platform.system() == "Windows": + dd_win() + else: + try: + _dd_iso_image(dd_progress_thread) + except: + o = io.StringIO() + traceback.print_exc(None, o) + log(o.getvalue()) + dd_progress_thread.set_error(o.getvalue()) + +def _dd_iso_image(dd_progress_thread): pbar = progressbar.ProgressBar( maxval=100, widgets=[ @@ -43,50 +54,74 @@ def dd_linux(): ] ).start() - while dd_process.poll() is None: - time.sleep(0.1) # If this time delay is not given, the Popen does not execute the actual command - dd_process.send_signal(signal.SIGUSR1) - dd_process.stderr.flush() - while True: - time.sleep(0.1) - out_error = dd_process.stderr.readline().decode() - if out_error: - if 'bytes' in out_error: - copied = int(out_error.split(' ', 1)[0]) - config.imager_percentage = round((float(copied) / float(in_file_size) * 100)) - pbar.update(config.imager_percentage) - break - - if dd_process.poll() is not None: - log("\nExecuting ==> sync") - os.sync() - log("ISO has been written to USB disk...") - return + + def gui_update(percentage): + config.imager_percentage = percentage + pbar.update(percentage) + + unmounted_contexts = [ + (usb.UnmountedContext(p[0], config.update_usb_mount), p[0]) for p + in udisks.find_partitions_on(config.usb_disk)] + really_unmounted = [] + try: + for c, pname in unmounted_contexts: + c.__enter__() + really_unmounted.append((c, pname)) + error = osdriver.dd_iso_image( + config.image_path, config.usb_disk, gui_update) + if error: + dd_progress_thread.set_error(error) + finally: + for c, pname in really_unmounted: + c.__exit__(None, None, None) + + +def dd_win_clean_usb(usb_disk_no): + """ + + """ + host_dir = multibootusb_host_dir() + temp_file = os.path.join(host_dir, "preference", "disk_part.txt") + diskpart_text_feed = 'select disk ' + str(usb_disk_no) + '\nclean\nexit' + write_to_file(temp_file, diskpart_text_feed) + config.status_text = 'Cleaning the disk...' + if subprocess.call('diskpart.exe -s ' + temp_file ) == 0: + return True + else: + return False def dd_win(): - windd = resource_path(os.path.join("data", "tools", "dd", "dd.exe")) - if os.path.exists(resource_path(os.path.join("data", "tools", "dd", "dd.exe"))): - log("dd exist") - _input = "if=" + config.image_path + windd = quote(resource_path(os.path.join("data", "tools", "dd", "dd.exe"))) + usb_disk_no = osdriver.get_physical_disk_number(config.usb_disk) + _input = "if=" + config.image_path.strip() in_file_size = float(os.path.getsize(config.image_path) / 1024 / 1024) - _output = "of=\\\.\\" + config.usb_disk - command = [windd, _input, _output, "bs=1M", "--progress"] - log("Executing ==> " + " ".join(command)) + _output = "of=\\\.\\PhysicalDrive" + str(usb_disk_no) + if dd_win_clean_usb(usb_disk_no) is False: + return + # _output = "od=" + config.usb_disk # 'od=' option should also work. + # command = [windd, _input, _output, "bs=1M", "--progress"] + command = windd + ' ' + _input + ' ' + _output + " bs=1M" + " --progress" + #log("Executing ==> " + " ".join(command)) + log("Executing ==> " + command) dd_process = subprocess.Popen(command, universal_newlines=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=False) + time.sleep(0.1) while dd_process.poll() is None: for line in iter(dd_process.stderr.readline, ''): line = line.strip() if 'error' in line.lower() or 'invalid' in line.lower(): - log("Error writing to disk...") + config.imager_return = False break if line and line[-1] == 'M': copied = float(line.strip('M').replace(',', '')) config.imager_percentage = round((copied / float(in_file_size) * 100)) - log("ISO has been written to USB disk...") + if config.imager_return is False: + log("Error writing to disk...") + else: + log("ISO has been written to USB disk...") return diff --git a/scripts/install.py b/scripts/install.py index 8bfc72b..5de34a4 100644 --- a/scripts/install.py +++ b/scripts/install.py @@ -198,11 +198,14 @@ def replace_syslinux_modules(syslinux_version, under_this_dir): try: with lzma.open(dst_path) as f: expanded = f.read() - except (OSError, IOError, lzma.LZMAError): + except lzma.LZMAError: + continue + except (OSError, IOError) as e: + log("%s while accessing %s." % (e, dst_path)) continue with open(dst_path, 'wb') as f: f.write(expanded) - log("Successfully dcompressed %s." % fname) + log("Successfully decompressed %s." % fname) continue try: os.remove(dst_path) @@ -230,13 +233,10 @@ def install_patch(): config.usb_mount, "multibootusb", iso_basename(config.image_path)) config.syslinux_version = isolinux_version(isolinux_path) - if config.distro == 'slitaz': + if config.distro in ['slitaz', 'ubunu']: replace_syslinux_modules(config.syslinux_version, distro_install_dir) - c32box_path = os.path.join(distro_install_dir, 'boot', 'isolinux', - 'c32box.c32') elif config.distro == 'gentoo': replace_syslinux_modules(config.syslinux_version, distro_install_dir) - elif config.distro == 'debian': iso_file_list = iso.iso_file_list(config.image_path) if not any(s.strip().lower().endswith("makeboot.sh") diff --git a/scripts/mbusb_gui.py b/scripts/mbusb_gui.py index 35398d0..3fcbbeb 100644 --- a/scripts/mbusb_gui.py +++ b/scripts/mbusb_gui.py @@ -31,7 +31,7 @@ from .distro import * from .qemu import * from .iso import * # from .imager import * -from .imager import Imager, dd_linux, dd_win +from .imager import Imager, dd_iso_image from . import persistence from . import config from . import admin @@ -731,8 +731,15 @@ Proceed with installation?'''.lstrip() % \ config.process_exist = None msgBox = QtWidgets.QMessageBox() - msgBox.setText("Image succesfully written to USB disk.") - msgBox.setInformativeText("Reboot to boot from USB or test it from Boot ISO/USB tab."); + if self.progress_thread_dd.error: + title = "Failed to write the iso image to the USB disk." + msg = self.progress_thread_dd.error + else: + title = "Image succesfully written to USB disk." + msg = "Reboot to boot from USB or test it from " \ + "Boot ISO/USB tab." + msgBox.setText(title) + msgBox.setInformativeText(msg); msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok) msgBox.setIcon(QtWidgets.QMessageBox.Information) msgBox.exec_() @@ -963,15 +970,15 @@ class DD_Progress(QtCore.QThread): def __init__(self): QtCore.QThread.__init__(self) - - if platform.system() == 'Linux': - self.thread = GenericThread(dd_linux) - elif platform.system() == 'Windows': - self.thread = GenericThread(dd_win) + self.error = None + self.thread = GenericThread(partial(dd_iso_image, self)) def __del__(self): self.wait() + def set_error(self, error): + self.error = error + def run(self): self.thread.start() while self.thread.isRunning(): diff --git a/scripts/osdriver.py b/scripts/osdriver.py index e1e593c..b562466 100644 --- a/scripts/osdriver.py +++ b/scripts/osdriver.py @@ -2,9 +2,12 @@ import logging import logging.handlers import os import platform +import queue import shutil +import signal import subprocess import sys +import time def log(message, info=True, error=False, debug=False, _print=True): @@ -59,6 +62,8 @@ def get_physical_disk_number(usb_disk): :param usb_disk: USB disk (Like F:) :return: Disk number. """ + import pythoncom + pythoncom.CoInitialize() partition, logical_disk = wmi_get_drive_info(usb_disk) log("Physical Device Number is %d" % partition.DiskIndex) return partition.DiskIndex @@ -136,6 +141,29 @@ class Base: else: log("%s succeeded." % str(cmd)) + + def dd_iso_image(self, input_, output, gui_update): + in_file_size = os.path.getsize(input_) + + cmd = [self.dd_exe, 'if=' + input_, + 'of=' + self.physical_disk(output), 'bs=1M'] + self.dd_iso_image_add_args(cmd, input_, output) + log('Executing => ' + str(cmd)) + kw_args = { + 'stdout' : subprocess.PIPE, + 'stderr' : subprocess.PIPE, + 'shell' : False, + } + self.add_dd_iso_image_popen_args(kw_args) + dd_process = subprocess.Popen(cmd, **kw_args) + errors = queue.Queue() + while dd_process.poll() is None: + self.dd_iso_image_readoutput(dd_process, gui_update, in_file_size, + errors) + error_list = [errors.get() for i in range(errors.qsize())] + return self.dd_iso_image_interpret_result( + dd_process.returncode, error_list) + class Windows(Base): def __init__(self): @@ -144,6 +172,39 @@ class Windows(Base): def dd_add_args(self, cmd_vec, input, output, bs, count): pass + def dd_iso_image_add_args(self, cmd_vec, input_, output): + cmd_vec.append('--progress') + + def add_dd_iso_image_popen_args(self, dd_iso_image_popen_args): + dd_iso_image_popen_args['universal_newlines'] = True + + def dd_iso_image_readoutput(self, dd_process, gui_update, in_file_size, + error_log): + for line in iter(dd_process.stderr.readline, ''): + line = line.strip() + if line: + l = line.replace(',', '') + if l[-1:] == 'M': + bytes_copied = float(l.rstrip('M')) * 1024 * 1024 + elif l.isdigit(): + bytes_copied = float(l) + else: + if 16 < error_log.qsize(): + error_log.get() + error_log.put(line) + continue + gui_update(bytes_copied / in_file_size * 100.) + continue + # Now the 'dd' process should have completed or going to soon. + + def dd_iso_image_interpret_result(self, returncode, error_list): + # dd.exe always returns 0 + if any([ 'invalid' in s or 'error' in s for s + in [l.lower() for l in error_list] ]): + return '\n'.join(error_list) + else: + return None + def physical_disk(self, usb_disk): return r'\\.\physicaldrive%d' % get_physical_disk_number(usb_disk) @@ -158,6 +219,37 @@ class Linux(Base): def dd_add_args(self, cmd_vec, input, output, bs, count): cmd_vec.append('conv=notrunc') + def dd_iso_image_add_args(self, cmd_vec, input_, output): + cmd_vec.append('oflag=sync') + + def add_dd_iso_image_popen_args(self, dd_iso_image_popen_args): + pass + + def dd_iso_image_readoutput(self, dd_process, gui_update, in_file_size, + error_log): + # If this time delay is not given, the Popen does not execute + # the actual command + time.sleep(0.1) + dd_process.send_signal(signal.SIGUSR1) + dd_process.stderr.flush() + while True: + time.sleep(0.1) + out_error = dd_process.stderr.readline().decode() + if out_error: + if 'bytes' in out_error: + bytes_copied = float(out_error.split(' ', 1)[0]) + gui_update( bytes_copied / in_file_size * 100. ) + break + if 16 < error_log.qsize(): + error_log.get() + error_log.put(out_error) + else: + # stderr is closed + break + + def dd_iso_image_interpret_result(self, returncode, error_list): + return None if returncode==0 else '\n'.join(error_list) + def physical_disk(self, usb_disk): return usb_disk.rstrip('0123456789') @@ -177,6 +269,7 @@ for func_name in [ 'run_dd', 'physical_disk', 'mbusb_log_file', + 'dd_iso_image', ]: globals()[func_name] = getattr(osdriver, func_name) diff --git a/scripts/syslinux.py b/scripts/syslinux.py index 8a8e1d4..48493fc 100644 --- a/scripts/syslinux.py +++ b/scripts/syslinux.py @@ -198,6 +198,12 @@ def syslinux_default(usb_disk): ''' if config.usb_gpt is False: log('\nExecuting ==> ' + mbr_install_cmd) + # + # Updating mbr using dd results in catastrophy. + # Windows will lose track of the filesystem and + # the target volume will go away. + # Never enable this code without proper work around! + # if subprocess.call(mbr_install_cmd, shell=True) == 0: log("\nmbr install is success...\n") return True diff --git a/scripts/udisks.py b/scripts/udisks.py index 0c330c6..be25ac1 100644 --- a/scripts/udisks.py +++ b/scripts/udisks.py @@ -15,18 +15,25 @@ import os import re -def node_mountpoint(node): +def de_mangle_mountpoint(raw): + return raw.replace('\\040', ' ').replace('\\011', '\t') \ + .replace('\\012', '\n').replace('\\0134', '\\') - def de_mangle(raw): - return raw.replace('\\040', ' ').replace('\\011', '\t').replace('\\012', - '\n').replace('\\0134', '\\') +def node_mountpoint(node): for line in open('/proc/mounts').readlines(): line = line.split() if line[0] == node: - return de_mangle(line[1]) + return de_mangle_mountpoint(line[1]) return None +def find_partitions_on(disk): + assert not disk[-1:].isdigit() + with open('/proc/mounts') as f: + relevant_lines = [l.split(' ') for l in f.readlines() + if l.startswith(disk)] + return [ [v[0], de_mangle_mountpoint(v[1])] + v[2:] for v + in relevant_lines ] class NoUDisks1(Exception): pass diff --git a/scripts/usb.py b/scripts/usb.py index ce02bc8..0fa56ee 100644 --- a/scripts/usb.py +++ b/scripts/usb.py @@ -476,7 +476,7 @@ class UnmountedContext: gen.log("Unmounted %s" % self.usb_disk) return self - def __exit__(self, type, value, traceback_): + def __exit__(self, type_, value, traceback_): if not self.is_relevant: return os.sync() # This should not be strictly necessary diff --git a/setup.py b/setup.py old mode 100755 new mode 100644