diff --git a/scripts/distro.py b/scripts/distro.py index 2530e8b..66fe7ba 100644 --- a/scripts/distro.py +++ b/scripts/distro.py @@ -6,14 +6,15 @@ # Licence: This file is a part of multibootusb package. You can redistribute it or modify # under the terms of GNU General Public License, v.2 or above +from functools import partial import os import platform import re -from .iso import * -from .isodump3 import ISO9660 -from .gen import * -from . import _7zip +from . import _7zip +from .gen import * +from . import iso +from .isodump3 import ISO9660 def distro(iso_cfg_ext_dir, iso_link, expose_exception=False): """ @@ -23,165 +24,177 @@ def distro(iso_cfg_ext_dir, iso_link, expose_exception=False): """ # iso9660fs = ISO9660(iso_link) # iso_file_list = iso9660fs.readDir("/") - iso_file_list = _7zip.list_iso(iso_link, expose_exception=expose_exception) + + distro = None # tenatively set to None + + iso_file_list = _7zip.list_iso( + iso_link, expose_exception=expose_exception) + iso_file_list_lower = [f.lower() for f in iso_file_list] + v_isolinux_bin_exists = iso.isolinux_bin_exist(iso_link) + + # Let's have less costly checks first. + # We'll have to make these checks as strictive as possible + # so that keyword based tests will not be skipped because + # of a false positive. + + if iso_file_list: + + distro = detect_iso_from_file_list(iso_file_list) + if distro: + return distro + else: + iso_file_list = [] + + def run_contains(keywords, filename, file_content, iso_flielist, + isolinux_bin_exists): + return any(k in file_content for k in keywords.split('|')) + + def contains(keywords): + return partial(run_contains, keywords.lower()) + + def run_file_exists(filename_sought, filename, file_content, iso_filelist, + isolinux_bin_exists): + return filename_sought in iso_filelist + def file_exists(filename_sought): + return partial(run_file_exists, filename_sought) + + def run_isolinux_bin_exists(exist_or_not, filename, file_content, + iso_filelist, isolinux_bin_exists): + return exist_or_not is isolinux_bin_exists + + def isolinux_bin_exists(exist_or_not): + return partial(run_isolinux_bin_exists, exist_or_not) + + def run_not(predicate, filename, file_content, + iso_filelist, isolinux_bin_exists): + return not predicate(filename, file_content, iso_filelist, + isolinux_bin_exists) + def not_(predicate): + return partial(run_not, predicate) + + test_vector = [ + ('ubcd', contains('ubcd')), + ('sgrubd2', contains('Super Grub Disk')), + ('hbcd', contains('hbcd')), + ('systemrescuecd', contains('systemrescuecd')), + ('parted-magic', [contains('pmagic|partedmagic'), + isolinux_bin_exists(True)]), + # mounting fat filesystem hard coded in to initrd. + # Can be modified only under linux. + ('mageialive', contains('mgalive')), + ('arch', contains('archisolabel|misolabel|parabolaisolabel')), + ('chakra', contains('chakraisolabel')), + ('kaos', contains('kdeosisolabel')), + ('debian', [contains('boot=live'), isolinux_bin_exists(True)]), + ('grml', [contains('grml'), contains('live-media-path')]), + ('debian-install', [contains('debian-installer'), + not_(file_exists('casper'))]), + ('solydx', contains('solydx')), + ('knoppix', contains('knoppix')), + ('centos', contains('root=live:CDLABEL=CentOS')), + ('fedora', contains('root=live:CDLABEL=|root=live:LABEL=')), + ('fedora', contains('redcore')), + ('redhat', contains('redhat')), + ('slitaz', contains('slitaz|dban |ophcrack|tinycore|rescue.cpi' + '|xpud|untangle|4mlinux|partition wizard' + '|android-x86.png|riplinux|lebel dummy' + '|http://pogostick.net/~pnh/ntpasswd/' + '|AVG Rescue CD|AntivirusLiveCD' + '|lkrn|Nanolinux|OSForensics|PING')), + ('slitaz', contains('minimal Slackware|Slackware-HOWTO')), + #('suse', contains('suse')), + ('opensuse-install', contains('class opensuse')), + ('ubuntu', contains('boot=casper')), + ('wifislax', contains('wifislax')), + ('slax', contains('slax')), + ('sms', [contains('sms.jpg|vector |autoexec'), + isolinux_bin_exists(True)]), + ('antix', contains('antix')), + ('porteus', contains('porteus')), + ('pclinuxos', contains('livecd=livecd|PCLinuxOS')), + ('gentoo', contains('looptype=squashfs|http://dee.su/liberte')), + ('finnix', contains('finnix')), + ('wifiway', contains('wifiway')), + ('puppy', contains('puppy|quirky|fatdog|slacko|xenialpup')), + ('ipcop', contains('ipcop')), + ('ipfire', contains('ipfire')), + ('salix-live', [contains('zenwalk|slack|salix'), + contains('live')]), + ('zenwalk', contains('zenwalk|slack|salix')), + ('ubuntu-server', contains('ubuntu server')), + ('centos-install', contains('Install CentOS')), + ('centos', contains('centos')), + ('trinity-rescue', contains('Trinity Rescue Kit')), + ('alpine', contains('alpine')), + ('kaspersky', contains('http://support.kaspersky.com')), + ('alt-linux', contains('ALT Linux')), + ('Windows', contains('Sergei Strelec')), + ('ReactOS', contains('ReactOS')), + ('fsecure', contains('fsecure')), + ('pc-unlocker', contains('default rwp')), + ('pc-tool', contains('/system/stage1')), + ('grub2only', contains('vba32rescue')), + ('rising-av', contains('BOOT_IMAGE=rising')), + ('Avira-RS', contains('Avira Rescue System')), + ('insert', contains('BOOT_IMAGE=insert')), + ] + + + # I'm not sure if this check is necessary prvious but code had skipped + # keyword based checks if the platform is unknown. if platform.system() == "Linux" or platform.system() == "Windows": for path, subdirs, files in os.walk(iso_cfg_ext_dir): for name in files: - if name.endswith(('.cfg', '.CFG', '.txt', '.TXT', '.lst')): - if name.lower()=='i18n.cfg': - # i18n.cfg in salitaz-rolling cause misdetection - # of centos by the following line. - # MENU LABEL English US (acentos) - continue - try: - # errors='ignore' is required as some files also contain non utf character - string = open(os.path.join(path, name), errors='ignore').read() - except IOError: - return "Read Error." - else: - if any("f4ubcd" in s.lower() for s in iso_file_list): - return "f4ubcd" - if re.search(r'ubcd', string, re.I): - return "ubcd" - elif re.search(r'Super Grub Disk', string, re.I): - return "sgrubd2" - elif re.search(r'hbcd', string, re.I): - return "hbcd" - elif re.search(r'systemrescuecd', string, re.I): - return "systemrescuecd" - elif re.search(r'pmagic|partedmagic', string, re.I) and isolinux_bin_exist(iso_link): - return "parted-magic" - elif re.search(r'mgalive', string, re.I): # mounting fat filesystem hard coded in to initrd. - # Can be modified only under linux. - return "mageialive" - elif re.search(r'archisolabel|misolabel|parabolaisolabel', string, re.I): - return "arch" - elif re.search(r'chakraisolabel', string, re.I): - return "chakra" - elif re.search(r'kdeosisolabel', string, re.I): - return "kaos" - elif re.search(r'boot=live', string, re.I) and isolinux_bin_exist(iso_link): - return "debian" - elif re.search(r'grml', string, re.I) and re.search(r'live-media-path=', string, re.I): - return "grml" - elif re.search(r'debian-installer', string, re.I) and not any("casper" in s.lower() for s in iso_file_list): - return "debian-install" - elif re.search(r'solydx', string, re.I): - return "solydx" - elif re.search(r'knoppix', string, re.I): - return "knoppix" - elif re.search(r'root=live:CDLABEL=CentOS', - string, re.I): - return 'centos' # centos-live - elif re.search(r'root=live:CDLABEL=', string, re.I) or re.search(r'root=live:LABEL=', string, re.I): - return "fedora" - elif re.search(r'redcore', string, re.I): - return "fedora" - elif re.search(r'redhat', string, re.I): - return "redhat" - elif re.search( - r'slitaz|dban |ophcrack|tinycore|rescue.cpi|xpud|untangle|4mlinux|partition wizard|android-x86.png|' - r'riplinux|lebel dummy|http://pogostick.net/~pnh/ntpasswd/|AVG Rescue CD|AntivirusLiveCD|' - r'lkrn|Nanolinux|OSForensics|PING', string, re.I): # acentos is a text found in slitaz distro - return "slitaz" - elif re.search(r'minimal Slackware|Slackware-HOWTO', string, re.I): - # for minimal slackware detection - return "slitaz" - # elif re.search(r'suse', string, re.I): - # return "suse" - elif re.search(r'class opensuse', string, re.I): - return "opensuse-install" - elif re.search(r'boot=casper', string, re.I): - return "ubuntu" - elif re.search(r'wifislax', string, re.I): - return "wifislax" - elif re.search(r'slax', string, re.I): - return "slax" - elif re.search(r'sms.jpg|vector |autoexec', string, re.I) and isolinux_bin_exist(iso_link): - return "sms" - elif re.search(r'antix', string, re.I): - return "antix" - elif re.search(r'porteus', string, re.I): - return "porteus" - elif re.search(r'livecd=livecd|PCLinuxOS', string, re.I): - return "pclinuxos" - elif re.search(r'looptype=squashfs|http://dee.su/liberte', string, re.I): - return "gentoo" - elif re.search(r'finnix', string, re.I): - return "finnix" - elif re.search(r'wifiway', string, re.I): - return "wifiway" - elif re.search(r'puppy|quirky|fatdog|slacko|xenialpup', string, re.I): - return "puppy" - elif re.search(r'ipcop', string, re.I): - return "ipcop" - elif re.search(r'ipfire', string, re.I): - return "ipfire" - elif re.search(r'zenwalk|slack|salix', string, re.I) and re.search(r'live', string, re.I): - return "salix-live" - elif re.search(r'zenwalk|slack|salix', string, re.I): - return "zenwalk" - elif re.search(r'ubuntu server', string, re.I): - return "ubuntu-server" - elif re.search(r'Install CentOS', string, re.I): - return "centos-install" - elif re.search(r'CentOS', string, re.I): - return "centos" - elif re.search(r'Trinity Rescue Kit', string, re.I): - return "trinity-rescue" - elif re.search(r'alpine', string, re.I): - return "alpine" - elif re.search(r'http://support.kaspersky.com', string, re.I): - return "kaspersky" - elif re.search(r'ALT Linux', string, re.I): - return "alt-linux" - elif re.search(r'Sergei Strelec', string, re.I): - return "Windows" - elif re.search(r'ReactOS', string, re.I): - return "ReactOS" - elif re.search(r'fsecure', string, re.I): - return "fsecure" - elif re.search(r'default rwp', string, re.I): - return "pc-unlocker" - elif re.search(r'/system/stage1', string, re.I): - return 'pc-tool' - elif re.search(r'vba32rescue', string, re.I): - return 'grub2only' - elif re.search(r'BOOT_IMAGE=rising', string, re.I): - return 'rising-av' - elif re.search(r'Avira Rescue System', string, re.I): - return 'Avira-RS' - elif any("alpine-release" in s.lower() for s in iso_file_list): - return 'alpine' - elif re.search(r'BOOT_IMAGE=insert', string, re.I): - return 'insert' - - distro = detect_iso_from_file_list(iso_link, iso_file_list) + name_lower = name.lower() + if not name_lower.endswith(('.cfg', '.txt', '.lst')): + continue + if name_lower=='i18n.cfg': + # i18n.cfg in salitaz-rolling cause misdetection + # of centos by the following line. + # MENU LABEL English US (acentos) + continue + try: + # errors='ignore' is required as some files also + # contain non utf character + string = open(os.path.join(path, name), + errors='ignore').read() + except IOError: + log("Read Error on %s." % name) + continue - if distro: - return distro - # FIXME: See the below comments. -# else: -# # FIXME: The idea of detecting as generic is to work like a unetbootin if other methods fails. -# # This simply extracts distro to root of the USB and install syslinux on isolinux.bin directory. -# # All works fine but unable to boot the distro successfully. Also, see the generic section from -# # syslinux, update_cfg and install_distro modules. -# if self.isolinux_bin_exist(): -# return "generic" - elif str(iso_link).lower().endswith('.iso'): - return 'memdisk_iso' - elif str(iso_link).lower().endswith('.img'): - return 'memdisk_img' - else: - return None - - -def detect_iso_from_file_list(iso_link, iso_file_list): + for distro_, predicate in test_vector: + predicates = [predicate] if callable(predicate) \ + else predicate + if all( p(name_lower, string.lower(), iso_file_list_lower, + v_isolinux_bin_exists) for p in predicates ): + return distro_ + + if distro: + return distro +# FIXME: See the below comments. +# else: +# # FIXME: The idea of detecting as generic is to work like a unetbootin if other methods fails. +# # This simply extracts distro to root of the USB and install syslinux on isolinux.bin directory. +# # All works fine but unable to boot the distro successfully. Also, see the generic section from +# # syslinux, update_cfg and install_distro modules. +# if self.isolinux_bin_exist(): +# return "generic" + elif str(iso_link).lower().endswith('.iso'): + return 'memdisk_iso' + elif str(iso_link).lower().endswith('.img'): + return 'memdisk_img' + else: + return None + + +def detect_iso_from_file_list(iso_file_list): """ Fallback detection script from the content of an ISO. :return: supported distro as string """ keys_to_distro = [ + (['f4ubcd'], 'f4ubcd'), + (['alpine-release'], 'alpine'), (['sources', 'boot.wim'], 'Windows'), (['config.isoclient'], 'opensuse'), (['dban'], 'slitaz'), @@ -190,16 +203,14 @@ def detect_iso_from_file_list(iso_link, iso_file_list): (['menu.lst'], 'grub4dos'), (['bootwiz.cfg', 'bootmenu_logo.png'], 'grub4dos_iso') ] - if os.path.exists(iso_link): - filenames = [f.lower() for f in iso_file_list] - for keys, distro in keys_to_distro: - if all(k in filenames for k in keys): - return distro - log("Examined %d %s in the iso but could not determine the distro." - % (len(filenames), len(filenames)==1 and 'filename' or 'filenames')) + filenames = [f.lower() for f in iso_file_list] + for keys, distro in keys_to_distro: + if all(k in filenames for k in keys): + return distro + #log("Examined %d %s in the iso but could not determine the distro." + # % (len(filenames), len(filenames)==1 and 'filename' or 'filenames')) return None - if __name__ == '__main__': iso_cfg_ext_dir = os.path.join(multibootusb_host_dir(), "iso_cfg_ext_dir") iso_link = 'Downloads/clonezilla-live-2.4.2-32-amd64.iso' diff --git a/tests/test-distro.py b/tests/test-distro.py index 42e1e3f..8283263 100644 --- a/tests/test-distro.py +++ b/tests/test-distro.py @@ -12,7 +12,7 @@ class DistoDetection(unittest.TestCase): mock_isobin_exists = MM(return_value=isobin_exists) mock_iso_list = MM(return_value=filelist_in_iso) mock_os_walk = MM(return_value=[('/', [], ['grub.cfg'])]) - @patch('scripts.distro.isolinux_bin_exist', mock_isobin_exists) + @patch('scripts.iso.isolinux_bin_exist', mock_isobin_exists) @patch('os.walk', mock_os_walk) @patch('scripts._7zip.list_iso', mock_iso_list) @patch('builtins.open', mock_open(read_data=input_text)) @@ -21,7 +21,6 @@ class DistoDetection(unittest.TestCase): return test_when_isolinux_bin_is_available() - def test_filebased_detection(self): test_inputs = [ ('f4ubcd', '', ['f4ubcd']), @@ -68,7 +67,6 @@ class DistoDetection(unittest.TestCase): ('kaos', 'kdeosisolabel'), ('memdisk_iso', 'grml'), ('grml', 'grml live-media-path=/dev/sda1'), - ('debian-install', 'debian-installer'), ('solydx', 'solydx'), ('knoppix', 'knoppix'), ('fedora', 'root=live:CDLABEL=|redcore'), @@ -115,7 +113,7 @@ class DistoDetection(unittest.TestCase): ] for expected_distro, input_texts in test_inputs: - for input_text in input_texts.split('|'): + for input_text in input_texts.lower().split('|'): distro = self.distro(False, [], input_text) assert distro==expected_distro, ( "From \"%s\", '%s' is expected but got '%s'" % @@ -133,18 +131,16 @@ class DistoDetection(unittest.TestCase): @patch('scripts.distro.log', log_mock) def _(): fn = distro.detect_iso_from_file_list - assert fn('fake.iso', ['BOOT.wim', 'Sources']) == 'Windows' - assert fn('fake.iso', ['BOOT.wim', 'Sause']) is None - assert fn('fake.iso', ['config.isoclient', 'foo']) == 'opensuse' - assert fn('fake.iso', ['bar', 'dban', 'foo']) == 'slitaz' - assert fn('fake.iso', ['memtest.img']) == 'memtest' - assert fn('fake.iso', ['mt86.png','isolinux']) == 'raw_iso' - assert fn('fake.iso', ['menu.lst']) == 'grub4dos' - assert fn('fake.iso', ['bootwiz.cfg', 'bootmenu_logo.png']) == \ + assert fn(['BOOT.wim', 'Sources']) == 'Windows' + assert fn(['BOOT.wim', 'Sause']) is None + assert fn(['config.isoclient', 'foo']) == 'opensuse' + assert fn(['bar', 'dban', 'foo']) == 'slitaz' + assert fn(['memtest.img']) == 'memtest' + assert fn(['mt86.png','isolinux']) == 'raw_iso' + assert fn(['menu.lst']) == 'grub4dos' + assert fn(['bootwiz.cfg', 'bootmenu_logo.png']) == \ 'grub4dos_iso' _() - log_mock.assert_called_with('Examined 2 filenames in the iso ' - 'but could not determine the distro.') if __name__ == '__main__': unittest.main()