You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
multibootusb/scripts/distro.py

268 lines
11 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Name: distro.py
# Purpose: Module to detect if distro types supported by multibootusb (by extracting specific files)
# Authors: Sundar
# 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 . import _7zip
from .gen import *
from . import iso
from .isodump3 import ISO9660
def distro(iso_cfg_ext_dir, iso_link, expose_exception=False):
"""
Detect if distro is supported by multibootusb.
:param iso_cfg_ext_dir: Directory where *.cfg files are extracted.
:return: Detected distro name as string.
"""
# iso9660fs = ISO9660(iso_link)
# iso_file_list = iso9660fs.readDir("/")
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 = perform_strict_detections(iso_cfg_ext_dir, iso_file_list)
if distro:
return distro
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)
# contains(X) predicates that X is contained in an examined text file.
# Multiple keywords can be concatenated by '|'. Predicates gets aaserted
# if anyone of the keywords is found in the text file.
# Sorry you can't include | in a keyword for now.
test_vector = [
('ubcd', contains('ubcd')),
('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')),
('sgrubd2', contains('Super Grub Disk')),
]
# I'm not sure if this platform check is necessary but I will
# avoid removal to not alter the behaviour.
if platform.system() == "Linux" or platform.system() == "Windows":
for path, subdirs, files in os.walk(iso_cfg_ext_dir):
for name in files:
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
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'),
(['memtest.img'], 'memtest'),
(['mt86.png', 'isolinux'], 'raw_iso'),
(['menu.lst'], 'grub4dos'),
(['bootwiz.cfg', 'bootmenu_logo.png'], 'grub4dos_iso') ]
filenames = [f.lower() for f in iso_file_list]
for keys, distro in keys_to_distro:
match = True
for k in keys:
if all(k not in fn for fn in filenames):
match = False
break
if match is True:
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
def perform_strict_detections(iso_cfg_ext_dir, iso_file_list):
def run_contains(filepath, keyword, cfg_dir=iso_cfg_ext_dir):
fullpath = os.path.join(cfg_dir, filepath.replace('/', os.sep))
if not os.path.exists(fullpath):
return False
try:
with open(fullpath, 'rb') as f:
data = f.read().lower()
return keyword in data
except (IOError, OSError):
log("Failed to open %s" % fullpath)
return False
def contains(relataive_filepath, keyword):
return partial(run_contains, relataive_filepath,
bytes(keyword.lower(),'us-ascii'))
# contains(P, K) predicates that file P contains the specified
# string K. The predicate get never asserted if P has not been
# extracted into the staging area (iso_cfg_ext_dir).
test_vector = [
('wifislax', contains('boot/syslinux/menu/vesamenu.cfg',
'menu label Wifislax64 Live')),
('salix-live', contains('boot/menus/mainmenu.cfg',
'MENU LABEL SALIX LIVE')),
('grml', contains('boot/isolinux/vesamenu.cfg',
'menu title Grml - Live Linux'))
]
for distro, predicate in test_vector:
predicates = [predicate] if callable(predicate) else predicate
if all(p() for p in predicates):
return distro
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'
iso_extract_file(iso_link, iso_cfg_ext_dir, 'cfg')
log(distro(iso_cfg_ext_dir))