def get_disks_list(): ''' This method is used by the BurnerGUI when the user clicks the ComboBox. It grabs all disk ids and then for every disk we get the name and size. Sizes will be converted to GB (not GiB). NOTE: We do no return all disks that are found! Example: disk id: /dev/rdisk2 disk name: APPLE SD Card Reader USB disk size: 16.03 ''' disks = list() for disk_id in get_disk_ids(): # change disk to raw to increase performance disk_id = disk_id[:5] + 'r' + disk_id[5:] # get the disk manufacturer and size in GB disk_name, disk_size = get_disk_name_size(disk_id) disk = {'id': disk_id, 'name': disk_name, 'size': disk_size} # make sure we do not list any potential hard drive or too small SD card if disk['size'] < 3.5 or disk['size'] > 16.5: # GB debugger('Ignoring {}'.format(disk)) else: debugger('Listing {}'.format(disk)) disks.append(disk) return disks
def get_disk_mount(disk_id): TEMP_DIR = 'C:\\temp\\kano-burner\\' disk_mount = '' # mount point e.g. C:\ or D:\ # extract the id of the physical disk, required by diskpart # e.g. \\?\Device\Harddisk[id]\Partition0 id = int(disk_id.split("Harddisk")[1][0]) # access by string index alone is dangerous! # create a diskpart script to find the mount point for the given disk diskpart_detail_script = 'select disk {} \ndetail disk'.format(id) write_file_contents(TEMP_DIR + "detail_disk.txt", diskpart_detail_script) # run the created diskpart script cmd = 'diskpart /s {}'.format(TEMP_DIR + "detail_disk.txt") output, error, return_code = run_cmd_no_pipe(cmd) if not return_code: debugger('Ran diskpart detail script') else: debugger('[ERROR] ' + error.strip('\n')) return # now the mount point is the third word on the last line of the output disk_mount = output.splitlines()[-1].split()[2] return disk_mount
def get_disks_list(): ''' This method is used by the BurnerGUI when the user clicks the ComboBox. It grabs all disk ids, disk names, and disk sizes separately and then matches them by index. Sizes will be converted to GB (not GiB). NOTE: We do no return all disks that are found! Example: disk id: /dev/sda disk name: Sandisk Ultra USB disk size: 16.03 ''' disks = list() for disk_id in get_disk_ids(): # get the disk manufacturer and size in GB disk_name, disk_size = get_disk_name_size(disk_id) disk = {'id': disk_id, 'name': disk_name, 'size': disk_size} # make sure we do not list any potential hard drive or too small SD card if disk['size'] < 3.5 or disk['size'] > 16.5: # GB debugger('Ignoring {}'.format(disk)) else: debugger('Listing {}'.format(disk)) disks.append(disk) return disks
def close_all_explorer_windows(): cmd = '{}\\nircmd.exe win close class "CabinetWClass"'.format(_nircmd_path) _, error, return_code = run_cmd_no_pipe(cmd) if not return_code: debugger('Closed all Explorer windows') else: debugger('[ERROR]: ' + error.strip('\n'))
def unmount_disk(disk_mount): cmd = "mountvol {}:\\ /D".format(disk_mount) error, _, return_code = run_cmd_no_pipe(cmd) if not return_code: debugger('{}:\\ successfully unmounted'.format(disk_mount)) else: debugger('[ERROR]: ' + error.strip('\n'))
def test_write(disk_mount): cmd = '{}\\dd.exe if=/dev/random of=\\\\.\\{}: bs=4M count=10'.format(_dd_path, disk_mount) _, output, return_code = run_cmd_no_pipe(cmd) if not return_code: debugger('Written 40M random data to {}:\\'.format(disk_mount)) else: debugger('[ERROR]: ' + output.strip('\n'))
def format_disk(disk_id): cmd = 'diskutil eraseDisk fat32 UNTITLED {}'.format(disk_id) _, error, return_code = run_cmd(cmd) if not return_code: debugger('{} successfully erased and formatted'.format(disk_id)) else: debugger('[ERROR] ' + error.strip('\n'))
def unmount_disk(disk_id): cmd = 'diskutil unmountDisk {}'.format(disk_id) _, error, return_code = run_cmd(cmd) if not return_code: debugger('{} successfully unmounted'.format(disk_id)) else: debugger('[ERROR] ' + error.strip('\n'))
def unzip_kano_os(os_path, dest_path): cmd = '"{}\\7za.exe" e "{}" -o"{}"'.format(_7zip_path, os_path, dest_path) _, output, return_code = run_cmd_no_pipe(cmd) if not return_code: debugger('Unzipped Kano OS successfully') else: debugger('[ERROR]: ' + output.strip('\n'))
def format_disk(disk_id): cmd = 'mkdosfs -I -F 32 -v {}'.format(disk_id) _, error, return_code = run_cmd(cmd) if not return_code: debugger('{} successfully erased and formatted'.format(disk_id)) else: debugger('[ERROR] ' + error.strip('\n'))
def get_disk_ids(): cmd = "diskutil list | grep '/dev/'" output, error, return_code = run_cmd(cmd) if not return_code: return output.split() else: debugger('[ERROR] ' + error.strip('\n'))
def is_installed(programs_list): cmd = 'which {}'.format(' '.join(programs_list)) output, error, return_code = run_cmd(cmd) if return_code: debugger('[ERROR] ' + error.strip('\n')) return True # if something goes wrong here, it shouldn't be catastrophic return len(output.split()) == len(programs_list)
def unmount_disk(disk_id): cmd = 'diskutil unmountDisk {}'.format(disk_id) _, error, return_code = run_cmd(cmd) if not return_code: debugger('{} successfully unmounted'.format(disk_id)) else: debugger('[ERROR: {}] {}'.format(cmd, error.strip('\n'))) raise disk_error(UNMOUNT_ERROR)
def getAriaStatus(self): self.process.poll() if not self.process.returncode: try: self.ariaStatus = self.server.aria2.tellStatus('token:'+self.secret, self.gid) if not isinstance(self.ariaStatus, dict): self.ariaStatus = {} except Exception as e: self.failed = True self.failure = e debugger('status call error {}'.format(e))
def unmount_disk(disk_id): # to unmount an entire disk, we first need to unmount all it's volumes unmount_volumes(disk_id) # now we can safely unmount the disk cmd = 'umount {}'.format(disk_id) _, error, return_code = run_cmd(cmd) if not return_code: debugger('disk {} successfully unmounted'.format(disk_id)) else: debugger('[ERROR] ' + error.strip('\n'))
def getAriaStatus(self): self.process.poll() if not self.process.returncode: try: self.ariaStatus = self.server.aria2.tellStatus( 'token:' + self.secret, self.gid) if not isinstance(self.ariaStatus, dict): self.ariaStatus = {} except Exception as e: self.failed = True self.failure = e debugger('status call error {}'.format(e))
def is_sufficient_space(required_mb): cmd = "df %s | grep -v 'Available' | awk '{print $4}'" % temp_path output, _, _ = run_cmd(cmd) try: free_space_mb = float(output.strip()) * 512 / BYTES_IN_MEGABYTE except: debugger('[ERROR] Failed parsing the line ' + output) return True debugger('Free space {0:.2f} MB in {1}'.format(free_space_mb, temp_path)) return free_space_mb > required_mb
def mount_disk(disk_id): # the following may not work if the disk has been unmounted, consider caching disk_mount = get_disk_mount(disk_id) disk_volume = get_disk_volume(disk_id, disk_mount) cmd = "mountvol {}:\\ {}".format(disk_mount, disk_volume) _, error, return_code = run_cmd_no_pipe(cmd) if not return_code: debugger('{} successfully mounted'.format(disk_mount)) else: debugger('[ERROR]: ' + error.strip('\n'))
def is_sufficient_space(path, required_mb): cmd = "df %s | grep -v 'Available' | awk '{print $4}'" % path output, _, _ = run_cmd(cmd) try: free_space_mb = float(output.strip()) * 512 / BYTES_IN_MEGABYTE except: debugger('[ERROR] Failed parsing the line ' + output) return True debugger('Free space {0:.2f} MB in {1}'.format(free_space_mb, path)) return free_space_mb > required_mb
def get_disk_ids(): cmd = "parted --list | grep 'Disk /dev/.*:' | awk '{print $2}'" output, error, return_code = run_cmd(cmd) disk_ids = [] for id in output.splitlines(): disk_ids.append(id[:-1]) if return_code: debugger('[ERROR] ' + error.strip('\n')) return disk_ids
def get_disk_names(): cmd = "parted --list | grep 'Model:'" output, error, return_code = run_cmd(cmd) disk_names = [] for name in output.splitlines(): disk_names.append(' '.join(name.split()[1:-1])) if return_code: debugger('[ERROR] ' + error.strip('\n')) # grab the first line of the output and the name is from the 4th word onwards return disk_names
def eject_disk(disk_id): ''' This method is used by the backendThread to ensure safe removal after burning finished successfully. ''' cmd = 'diskutil eject {}'.format(disk_id) _, error, return_code = run_cmd(cmd) if not return_code: debugger('{} successfully ejected'.format(disk_id)) else: debugger('[ERROR] ' + error.strip('\n'))
def eject_disk(disk_id): ''' This method is used by the backendThread to ensure safe removal after burning finished successfully. ''' cmd = 'eject {}'.format(disk_id) _, error, return_code = run_cmd(cmd) if not return_code: debugger('{} successfully ejected'.format(disk_id)) else: debugger('[ERROR] ' + error.strip('\n'))
def isFinished(self): self.process.poll() if self.process.returncode: debugger('aria returned {}'.format(self.status.returncode)) return True # aria finished; since it's not supposed to until we tell it, it probably died if self.failed: debugger('aria failed {}'.format(self.failure)) return True self.getAriaStatus() result = self.ariaStatus['status'] finished = result != 'active' and result != 'waiting' return finished
def get_disk_sizes(): cmd = "fdisk -l | grep 'Disk /dev/'" output, error, return_code = run_cmd(cmd) disk_sizes = [] for line in sorted(output.splitlines()): size = line.split()[4] disk_sizes.append(float(size) / BYTES_IN_GIGABYTE) if return_code: debugger('[ERROR] ' + error.strip('\n')) return disk_sizes
def unmount_volumes(disk_id): # all volumes on a disk have an index attached e.g. /dev/sdb1, /dev/sdb2 cmd = "fdisk -l | grep '%s[0-9][0-9]*' | awk '{print $1}'" % disk_id output, _, _ = run_cmd(cmd) # it may also happen that the disk does not have volumes # in which case the loop below won't do anything for volume in output.splitlines(): cmd = 'umount {}'.format(volume) _, error, return_code = run_cmd(cmd) if not return_code: debugger('volume {} successfully unmounted'.format(volume)) else: debugger('[ERROR] ' + error.strip('\n'))
def is_installed(programs_list): cmd = 'where.exe {}'.format(' '.join(programs_list)) output, error, return_code = run_cmd_no_pipe(cmd) if return_code: debugger('[ERROR] ' + error.strip('\n')) return True # if something goes wrong here, it shouldn't be catastrophic programs_found = 0 for line in output.splitlines(): if line and 'not find' not in line: programs_found += 1 return programs_found == len(programs_list)
def poll_burning_thread(thread): time.sleep(1) # wait for dd to start debugger('Polling burner for progress..') cmd = 'kill -INFO `pgrep ^dd`' # as long as the burning thread is running, send SIGINFO # to dd to trigger progress output while thread.is_alive(): _, error, return_code = run_cmd(cmd) if return_code: debugger('[ERROR] Sending signal to burning thread failed') return False time.sleep(0.3) return True
def get_latest_os_info(): debugger("Downloading latest OS information") # we put everything in a try block as urlopen raises URLError try: # get latest.json from download.kano.me response = urllib2.urlopen(LATEST_OS_INFO_URL) latest_json = json.load(response) latest_json['filename'] += '.gz' # the .gz will be used on all OSs # give the server some time to breathe between requests debugger('Latest Kano OS image is {}'.format(latest_json['filename'])) time.sleep(1) # use the url for the latest os version to get info about the image latest_image_json = '{base_url}{filename}.json'.format( base_url=latest_json['base_url'], filename=latest_json['filename']) debugger('Latest Kano OS image json is {}'.format(latest_image_json)) response = urllib2.urlopen(latest_image_json) os_json = json.load(response) # give the server some time to breathe between requests time.sleep(1) except: debugger('[ERROR] Downloading OS info failed') return None # merge the two jsons, add derived values and return a single info dict os_info = dict(latest_json.items() + os_json.items()) return os_info
def is_sufficient_space(required_mb): cmd = "dir {}".format(temp_path) output, _, _ = run_cmd_no_pipe(cmd) try: # grab the last line from the output free_space_line = output.splitlines()[-1] # grab the number in bytes, remove comma delimiters, and convert to MB free_space_mb = float(free_space_line.split()[2].replace(',', '')) / BYTES_IN_MEGABYTE except: debugger('[ERROR] Failed parsing the line ' + output) return True debugger('Free space {0:.2f} MB in {1}'.format(free_space_mb, temp_path)) return free_space_mb > required_mb
def get_disks_list(): ''' This method is used by the BurnerGUI when the user clicks the ComboBox. It grabs all disk ids, disk names, and disk sizes separately and then matches them by index. Sizes will be converted to GB (not GiB). NOTE: We do no return all disks that are found! Example: disk id: /dev/sda disk name: Sandisk Ultra USB disk size: 16.03 ''' disks = list() disk_ids = get_disk_ids() disk_names = get_disk_names() disk_sizes = get_disk_sizes() # check for parsing errors if len(disk_ids) != len(disk_names) or len(disk_names) != len(disk_sizes): return disks for index in range(0, len(disk_ids)): # append all data here, this would need changing if logic changes disk = { 'id': disk_ids[index], 'name': disk_names[index], 'size': disk_sizes[index] } # make sure we do not list any potential hard drive or too small SD card if disk['size'] < 3.5 or disk['size'] > 64: # GB debugger('Ignoring {}'.format(disk)) else: debugger('Listing {}'.format(disk)) disks.append(disk) return disks
def get_disk_name_size(disk_id): cmd = "parted {} unit B print".format(disk_id) output, error, return_code = run_cmd(cmd) disk_name = '' disk_size = 0 if return_code: debugger('[ERROR] ' + error.strip('\n')) for line in output.splitlines(): if 'Model:' in line: disk_name = ' '.join(line.split()[1:]) if 'Disk {}:'.format(disk_id) in line: disk_size = float(line[:-1].split()[2]) / BYTES_IN_GIGABYTE if not disk_name or not disk_size: debugger('[ERROR] Parsing disk name and size failed') return disk_name, disk_size
def format_disk(disk_id): # TODO: look into cmd = 'format {}: /Q /X'.format(disk_mount) TEMP_DIR = 'C:\\temp\\kano-burner\\' # extract the id of the physical disk, required by diskpart # e.g. \\?\Device\Harddisk[id]\Partition0 id = int(disk_id.split("Harddisk")[1][0]) # access by string index alone is dangerous! # create a diskpart script to format the given disk diskpart_format_script = 'select disk {} \nclean'.format(id) write_file_contents(TEMP_DIR + "format_disk.txt", diskpart_format_script) # run the created diskpart script cmd = 'diskpart /s {}'.format(TEMP_DIR + "format_disk.txt") _, error, return_code = run_cmd_no_pipe(cmd) if not return_code: debugger('Formatted disk {} with diskpart'.format(id)) else: debugger('[ERROR] ' + error.strip('\n'))
def get_disk_name_size(disk_id): cmd = "diskutil info {}".format(disk_id) output, error, return_code = run_cmd(cmd) disk_name = '' disk_size = 0 if return_code: debugger('[ERROR] ' + error.strip('\n')) for line in output.splitlines(): if 'Device / Media Name:' in line: disk_name = ' '.join(line.split()[4:]) if 'Total Size:' in line: disk_size = float(line.split()[4][1:]) / BYTES_IN_GIGABYTE if not disk_name or not disk_size: debugger('[ERROR] Parsing disk name and size failed') return disk_name, disk_size
def get_latest_os_info(): debugger("Downloading latest OS information") # we put everything in a try block as urlopen raises URLError try: # get latest.json from download.kano.me response = urllib2.urlopen(LATEST_OS_INFO_URL) latest_json = json.load(response) # give the server some time to breathe between requests debugger('Latest Kano OS image is {}'.format(latest_json['filename'])) time.sleep(1) # use the url for the latest os version to get info about the image response = urllib2.urlopen(latest_json['url'] + '.json') os_json = json.load(response) # give the server some time to breathe between requests time.sleep(1) except: debugger('[ERROR] Downloading OS info failed') return None # merge the two jsons and return a single info dict result os_info = {key: value for (key, value) in (latest_json.items() + os_json.items())} return os_info
def burn_kano_os(path, disk, size, return_queue, report_progress_ui): cmd = 'gzip -dc {} | dd of={} bs=4M'.format(path, disk) process = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE) failed = False unparsed_line = '' # as long as Popen is running, read it's stderr line by line # each time a USR1 signal is sent to dd, it outputs 3 lines # and we are only interested in the last one i.e. 'x bytes written in y seconds' for line in iter(process.stderr.readline, ''): if 'bytes' in line: try: parts = line.split() written_bytes = float(parts[0]) progress = int(written_bytes / size * 100) speed = float(parts[7]) eta = calculate_eta(written_bytes, size, speed * BYTES_IN_MEGABYTE) report_progress_ui( progress, 'speed {0:.2f} MB/s eta {1:s} completed {2:d}%'.format( speed, eta, progress)) except: unparsed_line = line # watch out for an error output from dd if 'error' in line.lower() or 'invalid' in line.lower(): debugger('[ERROR] ' + line) failed = True # make sure the progress bar is filled and show an appropriate message # if we failed, the UI will immediately show the error screen report_progress_ui(100, 'burning finished successfully') # making sure we log anything nasty that has happened if unparsed_line: debugger('[ERROR] Failed parsing the line: ' + unparsed_line) if failed: debugger('[ERROR] Burning Kano image failed') return_queue.put(False) else: debugger('Burning successfully finished') return_queue.put(True)
def format_disk(disk_id): # TODO: look into cmd = 'format {}: /Q /X'.format(disk_mount) # extract the id of the physical disk, required by diskpart # e.g. \\?\Device\Harddisk[id]\Partition0 id = int(disk_id['id_num']) # access by string index alone is dangerous! # create a diskpart script to format the given disk diskpart_format_script = 'select disk {} \nclean'.format(id) diskpart_script_path = os.path.join(temp_path, "format_disk.txt") write_file_contents(diskpart_format_script, diskpart_script_path) # run the created diskpart script cmd = 'diskpart /s {}'.format(diskpart_script_path) _, error, return_code = run_cmd_no_pipe(cmd) time.sleep(15) # diskpart requires a timeout between calls if not return_code: debugger('Formatted disk {} with diskpart'.format(id)) else: debugger('[ERROR] ' + error.strip('\n')) raise disk_error(FORMAT_ERROR)
def get_disks_list(): ''' This method is used by the BurnerGUI when the user clicks the ComboBox. It grabs all disk ids and then for every disk we get the name and size. Sizes will be converted to GB (not GiB). NOTE: We do no return all disks that are found! Example: disk id: /dev/rdisk2 disk name: APPLE SD Card Reader USB disk size: 16.03 ''' disks = list() for disk_id in get_disk_ids(): # change disk to raw to increase performance disk_id = disk_id[:5] + 'r' + disk_id[5:] # get the disk manufacturer and size in GB disk_name, disk_size = get_disk_name_size(disk_id) disk = { 'id': disk_id, 'name': disk_name, 'size': disk_size } # make sure we do not list any potential hard drive or too small SD card if disk['size'] < 3.5 or disk['size'] > 64: # GB debugger('Ignoring {}'.format(disk)) else: debugger('Listing {}'.format(disk)) disks.append(disk) return disks
def get_latest_os_info(): debugger("Downloading latest OS information") # we put everything in a try block as urlopen raises URLError try: # get latest.json from download.kano.me response = urllib2.urlopen(LATEST_OS_INFO_URL) latest_json = json.load(response) # the .gz archive will be used on all OSs latest_json['archive'] = latest_json['filename'] + '.gz' # give the server some time to breathe between requests debugger('Latest Kano OS image is {}'.format(latest_json['filename'])) time.sleep(1) # use the url for the latest os version to get info about the image latest_image_json = '{base_url}{filename}.json'.format( base_url=latest_json['base_url'], filename=latest_json['filename']) # aria2 supports more url types, allow option to use these without failing # backward compatibility with older burners if 'url.v2' in latest_json: latest_json['url'] = latest_json['url.v2'] debugger('Latest Kano OS image json is {}'.format(latest_image_json)) response = urllib2.urlopen(latest_image_json) os_json = json.load(response) # give the server some time to breathe between requests time.sleep(1) except: debugger('[ERROR] Downloading OS info failed') return None # merge the two jsons, add derived values and return a single info dict os_info = dict(latest_json.items() + os_json.items()) return os_info
def isSuccessful(self): if self.failed: return False self.getAriaStatus() debugger('Final aria status {}'.format(self.ariaStatus)) # Fixme: check codes present in dict # Fixme: check for hash failed code if self.ariaStatus['status'] != 'complete' or int( self.ariaStatus['errorCode']) != 0: debugger('Download status {}'.format(self.ariaStatus['status'])) debugger('Downloader returned error code {}'.format( self.ariaStatus['errorCode'])) return False return True