Pull from master

pull/115/head
mbusb 7 years ago
parent f6b6d1c0e5
commit 52c66ca7cd

@ -3,6 +3,7 @@ Version - 8.5.0
* Added Solus OS
* Added Ka OS
* Added PC Unlocker
* Added Acronis True Image
* Fixed bug - Same drives appearing multiple times under imager tab
* Fixed HBCD boot issue
* Fix crash when using ISO Imager option to write ISO files (fix by bagage)

@ -0,0 +1,74 @@
# file GENERATED by distutils, do NOT edit
multibootusb
multibootusb-pkexec
org.debian.pkexec.run-multibootusb.policy
setup.py
data/multibootusb.desktop
data/version.txt
data/multibootusb/bg.png
data/multibootusb/chain.c32
data/multibootusb/extlinux.cfg
data/multibootusb/grub.exe
data/multibootusb/memdisk
data/multibootusb/menu.c32
data/multibootusb/menu.lst
data/multibootusb/syslinux.cfg
data/multibootusb/vesamenu.c32
data/tools/mbr.bin
data/tools/multibootusb.png
data/tools/dd/dd.exe
data/tools/dd/diskio.dll
data/tools/mkfs/mke2fs.exe
data/tools/syslinux/syslinux_linux.zip
data/tools/syslinux/syslinux_linux_64.zip
data/tools/syslinux/syslinux_modules.zip
data/tools/syslinux/syslinux_windows.zip
scripts/_7zip.py
scripts/__init__.py
scripts/admin.py
scripts/config.py
scripts/distro.py
scripts/gen.py
scripts/imager.py
scripts/install.py
scripts/iso.py
scripts/isodump3.py
scripts/mbusb_cli.py
scripts/mbusb_gui.py
scripts/persistence.py
scripts/qemu.py
scripts/syslinux.py
scripts/udisks.py
scripts/uninstall_distro.py
scripts/update_cfg_file.py
scripts/usb.py
scripts/gui/__init__.py
scripts/gui/ui_multibootusb.py
scripts/progressbar/__init__.py
scripts/progressbar/compat.py
scripts/progressbar/progressbar.py
scripts/progressbar/widgets.py
scripts/pyudev/__init__.py
scripts/pyudev/_compat.py
scripts/pyudev/_errors.py
scripts/pyudev/_qt_base.py
scripts/pyudev/_util.py
scripts/pyudev/core.py
scripts/pyudev/discover.py
scripts/pyudev/glib.py
scripts/pyudev/monitor.py
scripts/pyudev/pyqt4.py
scripts/pyudev/pyqt5.py
scripts/pyudev/pyside.py
scripts/pyudev/version.py
scripts/pyudev/wx.py
scripts/pyudev/_ctypeslib/__init__.py
scripts/pyudev/_ctypeslib/_errorcheckers.py
scripts/pyudev/_ctypeslib/libc.py
scripts/pyudev/_ctypeslib/libudev.py
scripts/pyudev/_ctypeslib/utils.py
scripts/pyudev/_os/__init__.py
scripts/pyudev/_os/pipe.py
scripts/pyudev/_os/poll.py
scripts/pyudev/device/__init__.py
scripts/pyudev/device/_device.py

@ -0,0 +1,10 @@
Metadata-Version: 1.0
Name: multibootusb
Version: 8.5.0
Summary: Create multi boot live Linux on a USB disk...
Home-page: http://multibootusb.org/
Author: Sundar
Author-email: feedback.multibootusb@gmail.com
License: General Public License (GPL)
Description: multibootusb is an advanced cross-platform application for installing/uninstalling Linux operating systems on to a single USB flash drives.
Platform: Linux

@ -0,0 +1,8 @@
[Desktop Entry]
Type=Application
Name=multibootusb
Comment=Install multiple Linux Operating System on USB
Icon=multibootusb
Exec=multibootusb-pkexec
Categories=System;
StartupNotify=true

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -0,0 +1,35 @@
# This file is created by MultiBootUSB.
default vesamenu.c32
prompt 0
menu title MultiBootUSB
MENU BACKGROUND /multibootusb/bg.png
TIMEOUT 300
MENU WIDTH 80
MENU MARGIN 10
MENU PASSWORDMARGIN 3
MENU ROWS 12
MENU TABMSGROW 18
MENU CMDLINEROW 18
MENU ENDROW -1
MENU PASSWORDROW 11
MENU TIMEOUTROW 20
MENU HELPMSGROW 22
MENU HELPMSGENDROW -1
MENU HIDDENROW -2
MENU HSHIFT 0
MENU VSHIFT 0
MENU COLOR border 30;44 #40ffffff #a0000000 std
MENU COLOR title 1;36;44 #9033ccff #a0000000 std
MENU COLOR sel 7;37;40 #e0ffffff #20ffffff all
MENU COLOR unsel 37;44 #50ffffff #a0000000 std
MENU COLOR help 37;40 #c0ffffff #a0000000 std
MENU COLOR timeout_msg 37;40 #80ffffff #00000000 std
MENU COLOR timeout 1;37;40 #c0ffffff #00000000 std
MENU COLOR msg07 37;40 #90ffffff #a0000000 std
MENU COLOR tabmsg 31;40 #30ffffff #00000000 std
label Boot from Hard Drive
MENU LABEL Boot from Hard Disk
KERNEL chain.c32
APPEND hd1
MENU DEFAULT

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

@ -0,0 +1,5 @@
multibootusb (8.5.0-1) unstable; urgency=low
* source package automatically created by stdeb 0.8.2
-- Sundar <feedback.multibootusb@gmail.com> Sun, 05 Feb 2017 15:32:06 +0530

@ -0,0 +1,14 @@
Source: multibootusb
Maintainer: Sundar <feedback.multibootusb@gmail.com>
Section: system
Priority: optional
Build-Depends: python3-all, debhelper (>= 9), python3-all
Standards-Version: 3.9.1
X-Python-Version: = 3.5
Package: python3-multibootusb
Architecture: all
Depends: ${misc:Depends}, ${python3:Depends}, python3-pyqt5, parted, util-linux, mtools, python3-dbus, p7zip-full, python3-six
Description: Create multi boot live Linux on a USB disk...
multibootusb is an advanced cross-platform application for installing/uninstalling Linux operating systems on to a single USB flash drives.

@ -0,0 +1,8 @@
#!/usr/bin/make -f
# This file was automatically generated by stdeb 0.8.2 at
# Sun, 05 Feb 2017 15:32:06 +0530
export PYBUILD_NAME=multibootusb
%:
dh $@ --with python3 --buildsystem=pybuild

@ -0,0 +1,105 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Name: multibootusb
# Purpose: Main file which will determine if cli or gui is to be opened
# 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
import logging
import getopt
import sys
import os
import platform
# Had trouble in importing scripts directory. Had to add few lines below to ensure it works on source as well as
# post install
try:
from scripts.mbusb_cli import *
from scripts import admin
from scripts import gen
except:
try:
from .scripts.mbusb_cli import *
from .scripts import admin
from .scripts import gen
except:
import scripts
gui = True
iso_link = None
usb_disk = None
uninstall = False
def usage():
print('\nAn advanced multi boot live usb creator using command line.')
print('\nUsage: python3 multibootusb -c -i /path/to/iso [option(s)] -t /path/to/device\n')
print('[option(s)] are :\n')
print(' -h or --help : Print this help message and exit')
print(' -i or --iso : Path to ISO file')
print(' -t or --target : Path to target USB device partition (example /dev/sdb1)\n')
print(' -c or --command : This option is must for invoking multibootusb from command line\n')
print(' -u or --uninstall : List and uninstall distro from USB disk')
print(' Command line example for making a bootable USB from command line should look like this:-\n')
print(' python3 multibootusb -c -i ../../favourite.iso -t /dev/sdb1\n')
exit(2)
def start_gui():
from scripts import mbusb_gui
gen.log('Starting multibootusb GUI...')
if platform.system() == 'Linux':
if os.getuid() != 0:
gen.log('\n\nAdmin privilege is required to run multibootusb.\n If you are running from source try '
'\'sudo python3 ./multibootusb\'\n or you can try \'multibootusb-pkexec\' (post install)\n\n',
info=False, debug=True)
mbusb_gui.main_gui()
if __name__ == '__main__':
if platform.system() == 'Windows':
if not admin.isUserAdmin():
admin.runAsAdmin()
sys.exit(0)
try:
opts, args = getopt.getopt(sys.argv[1:], 'i:t:vhcu',
['iso=', 'target=', 'version', 'help', 'command', 'uninstall'])
except getopt.GetoptError:
usage()
sys.exit(2)
for opt, arg in opts:
if opt in ('-h', '--help'):
usage()
sys.exit(2)
elif opt in ('-v', '--version'):
print_version()
sys.exit()
elif opt in ('-i', '--iso'):
config.iso_link = arg
elif opt in ('-t', '--target'):
config.usb_disk = arg
elif opt in ('-c', '--command'):
gui = False
elif opt in ('-u', '--uninstall'):
uninstall = True
else:
gui = True
#start_gui()
'''
usage()
sys.exit()
'''
if gui is False:
if uninstall is True and config.usb_disk is not None:
cli_uninstall_distro()
elif config.iso_link is None and config.usb_disk is None:
usage()
elif config.iso_link is None or config.usb_disk is None:
print('\nOptions \'-i\' and \'t\' must be supplied together. See the usage below.')
usage()
else:
cli_install_distro()
elif gui is True:
start_gui()

@ -0,0 +1,6 @@
#!/bin/bash
if [ $(which pkexec) ]; then
pkexec --disable-internal-agent "/usr/bin/multibootusb" "$@"
else
/usr/bin/multibootusb "$@"
fi

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<policyconfig>
<action id="org.debian.pkexec.run-multibootusb">
<description>Run the multibootusb program</description>
<message>Authentication is required to run the multibootusb</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/usr/bin/multibootusb</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
</action>
</policyconfig>

@ -0,0 +1,130 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Name: 7zip.py
# Purpose: Wrapper module to list and extract ISO files using 7zip
# 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
import os
import platform
import subprocess
from . import config
from . import gen
if platform.system() == 'Windows':
_7zip = gen.quote(gen.resource_path(os.path.join('data', 'tools', '7zip', '7z.exe')))
else:
_7zip = '7z'
def extract_iso(src, dst, pattern=None, suppress_out=True):
"""
Simple wrapper function to extract ISO file to destination
:param src: Path to ISO file
:param dst: Path to directory where the files are to be extracted
:param patter: The pattern to match the files to be extracted
:return:
"""
# 7z x -y -oC:\path_to_directory X:\path_to_iso_file.iso
# 7z e archive.zip -oC:\path_to_directory *.cfg *.bin -r
if platform.system() == 'Windows':
cli_option = ' -bb1' # Linux does not accept this option (may be due to version diff).
if suppress_out != '':
# suppress_out = ' 2> nul'
suppress_out = ''
else:
cli_option = ''
if suppress_out != '':
suppress_out = ' 2> /dev/null'
if not os.path.exists(src):
gen.log('ISO file could not be found on the location specified.')
return False
if not os.path.exists(dst):
os.makedirs(dst, exist_ok=True)
if pattern is None:
_cmd = _7zip + cli_option + ' x -y -o' + gen.quote(dst) + ' ' + gen.quote(src) + suppress_out
else:
_cmd = _7zip + ' -y x ' + gen.quote(src) + ' -o' + dst + ' ' + gen.quote(pattern) + ' -r' + suppress_out
# gen.log('Executing', _cmd)
_7zip_process = subprocess.Popen(_cmd, universal_newlines=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE,
stdout=subprocess.PIPE, shell=True)
config.status_text = 'Extracting ' + os.path.basename(src)
while True:
line = _7zip_process.stdout.readline()
# gen.log(line)
if line.startswith('- '):
config.status_text = 'Extracting ' + line[2:]
elif platform.system() == 'Linux': # line.startswith('Extracting'): # Under Linux it prints directly all the process (may be due to version diff).
config.status_text = line
if line == '' and _7zip_process.poll() != None:
break
def list_iso(iso_link, suppress_out=True):
if platform.system() == 'Windows':
if suppress_out is True:
suppress_out = ' 2> nul'
else:
if suppress_out is True:
suppress_out = ' 2> /dev/null'
if not os.path.exists(iso_link):
gen.log('Path to ISO link does not exist.')
return False
else:
file_list = []
_cmd = _7zip + ' l ' + gen.quote(iso_link) + suppress_out
# gen.log('Executing', _cmd)
_cmd_out = subprocess.check_output(_cmd, universal_newlines=True, stderr=subprocess.PIPE, shell=True).splitlines()
for line in _cmd_out:
line = line.split()
if '.....' in line:
file_list.append(line[-1])
return file_list
def test_iso(iso_link, suppress_out=True):
"""
Function to test if ISO file is corrupted. Relying only on 7zip.
:param iso_link: Path to ISO file
:return: True if test is positive
"""
# 7z t /path/to/iso/file.iso
# return value : 0 No error
# return value : 1 Warning (Non fatal error(s))
# return value : 2 Fatal error
# return value : 7 Command line error
# return value : 8 Not enough memory for operation
# return value : 255 User stopped the process
if platform.system() == 'Windows':
if suppress_out is True:
suppress_out = ' > nul'
else:
if suppress_out is True:
suppress_out = ' > /dev/null'
_cmd = _7zip + ' t ' + iso_link + suppress_out
# gen.log('Executing', _cmd)
rc = subprocess.call(_cmd, shell=True)
if rc == 0 or rc == 1:
return True
else:
return False
if __name__ == '__main__':
# slitaz-4.0.iso
# ubuntu-16.04-desktop-amd64.iso
# avg_arl_cdi_all_120_160420a12074.iso
# haiku-nightly.iso
# Hiren_BootCD.iso
file_list = list_iso('../../ubuntu_14_04_backup/Downloads/clonezilla-live-2.4.2-32-amd64.iso')
for f in file_list:
print(f)

@ -0,0 +1,10 @@
# Author(s) information
__authors__ = ['Sundar']
__author__ = ','.join(__authors__)
__credits__ = ['Ian Bruce', 'Lee']
__copyright__ = 'Copyright (c) 2014'
__license__ = 'GPL'
# Maintanence information
__maintainer__ = 'Sundar'
__email__ = 'feedback.multibootusb@gmail.com'

@ -0,0 +1,167 @@
#!/usr/bin/env python3
# -*- coding: utf-8; mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vim: fileencoding=utf-8 tabstop=4 expandtab shiftwidth=4
# Name: admin.py
# Purpose: Module to ask for admin rights under Linux and Windows
# Authors: Originally developed by Preston Landers (for windows) and modified for multibootusb by Sundar
# Licence: This file is a part of multibootusb package. You can redistribute it or modify
# under the terms of the same license as Python 2.6.5
##
## (C) COPYRIGHT © Preston Landers 2010
## Released under the same license as Python 2.6.5
##
"""
User Access Control for Microsoft Windows Vista and higher. This is
only for the Windows platform.
This will relaunch either the current script - with all the same command
line parameters - or else you can provide a different script/program to
run. If the current user doesn't normally have admin rights, he'll be
prompted for an admin password. Otherwise he just gets the UAC prompt.
This is meant to be used something like this::
if not pyuac.isUserAdmin():
return pyuac.runAsAdmin()
# otherwise carry on doing whatever...
See L{runAsAdmin} for the main interface.
"""
import os
import traceback
import types
import platform
import sys
import subprocess
from PyQt5 import QtWidgets
from . import gen
def isUserAdmin():
"""
@return: True if the current user is an 'Admin' whatever that means
(root on Unix), otherwise False.
Warning: The inner function fails unless you have Windows XP SP2 or
higher. The failure causes a traceback to be gen.loged and this
function to return False.
"""
if platform.system() == "Windows":
import ctypes
# WARNING: requires Windows XP SP2 or higher!
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
traceback.print_exc()
gen.log("Admin check failed, assuming not an admin.")
return False
elif platform.system() == "Linux":
return os.getuid() == 0
else:
raise RuntimeError("Unsupported operating system for this module: %s" % (os.name,))
def runAsAdmin(cmdLine=None, wait=True):
"""
Attempt to relaunch the current script as an admin using the same
command line parameters. Pass cmdLine in to override and set a new
command. It must be a list of [command, arg1, arg2...] format.
Set wait to False to avoid waiting for the sub-process to finish. You
will not be able to fetch the exit code of the process if wait is
False.
Returns the sub-process return code, unless wait is False in which
case it returns None.
@WARNING: this function only works on Windows.
"""
#if os.name == 'nt':
# raise RuntimeError, "This function is only implemented on Windows."
if platform.system() == "Windows":
import win32api, win32con, win32event, win32process
from win32com.shell.shell import ShellExecuteEx
from win32com.shell import shellcon
python_exe = sys.executable
if cmdLine is None:
cmdLine = [python_exe] + sys.argv
elif type(cmdLine) not in (types.TupleType, types.ListType):
raise ValueError("cmdLine is not a sequence.")
cmd = '"%s"' % (cmdLine[0],)
# XXX TODO: isn't there a function or something we can call to massage command line params?
params = " ".join(['"%s"' % (x,) for x in cmdLine[1:]])
cmdDir = ''
showCmd = win32con.SW_SHOWNORMAL
#showCmd = win32con.SW_HIDE
lpVerb = 'runas' # causes UAC elevation prompt.
#gen.log("Running", cmd, params)
# ShellExecute() doesn't seem to allow us to fetch the PID or handle
# of the process, so we can't get anything useful from it. Therefore
# the more complex ShellExecuteEx() must be used.
# procHandle = win32api.ShellExecute(0, lpVerb, cmd, params, cmdDir, showCmd)
procInfo = ShellExecuteEx(nShow=showCmd,
fMask=shellcon.SEE_MASK_NOCLOSEPROCESS,
lpVerb=lpVerb,
lpFile=cmd,
lpParameters=params)
if wait:
procHandle = procInfo['hProcess']
obj = win32event.WaitForSingleObject(procHandle, win32event.INFINITE)
rc = win32process.GetExitCodeProcess(procHandle)
#gen.log "Process handle %s returned code %s" % (procHandle, rc)
else:
rc = None
return rc
def adminCmd(cmd, fork=False, gui=False):
"""
This simple function checks for a sudo command and runs a command using it.
This function tries to launch given script with root access using pkexec/gksu/gksudo/kdesu/kdesudo,
if one of them is already installed.
PyQt4 is used as GUI.
Author : sundar
"""
sudo_cmd = ''
if os.getuid() == 0:
sudo_cmd = cmd
else:
if os.system('which pkexec') == 0:
if gui:
cmd = ['export DISPLAY=$DISPLAY; export XAUTHORITY=$XAUTHORITY; '] + cmd # By default, pkexec disallows X11 apps. Restore DISPLAY & XAUTHORITY to allow it. man 1 pkexec/"SECURITY NOTES" section
sudo_cmd = ['pkexec', '/bin/sh', '-c']
elif os.system('which gksudo') == 0:
sudo_cmd = ["gksudo", "--", "/bin/sh", "-c"]
elif os.system('which gksu') == 0:
sudo_cmd = ["gksu"]
elif os.system('which kdesudo') == 0:
sudo_cmd = ["kdesudo", "-t", "-c"] # http://www.unix.com/man-page/debian/1/kdesudo/
elif os.system('which kdesu') == 0:
sudo_cmd = ["kdesu", "-t", "-c"] # http://linux.die.net/man/1/kdesu
else:
QtWidgets.QMessageBox.information('No root...',
'Could not find any of: pkexec, sudo, gksu, kdesu, gksudo, or kdesudo. Please install one then restart multibootusb.')
sys.exit(0)
final_cmd = ' '.join(sudo_cmd + ['"' + ' '.join(cmd).replace('"', '\\"') + '"'])
gen.log("Executing ==> " + final_cmd)
if fork:
return subprocess.Popen(final_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1, shell=True)
else:
ret = subprocess.call(final_cmd, shell=True)
gen.log("Process returned ==> " + str(ret))
return ret

@ -0,0 +1,36 @@
#!/usr/bin/env python3
# Name: config.py
# Purpose: Module to share important variables between various modules. Mainly included so as not to call many
# functions again and again
# 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
iso_link = ""
usb_disk = ""
usb_mount = ""
usb_uuid = ""
usb_label = ""
persistence = 0
distro = ""
status_text = ""
percentage = 0
syslinux_version = ''
uninstall_distro_dir_name = ""
uninstall_distro_dir_path = ""
iso_file_list = ''
process_exist = None
imager_iso_link = ""
imager_usb_disk_selected = ""
imager_lock = ""
imager_percentage = ""
imager_status_text = ""
install_size = ""
editors_linux = ["gedit", "kate", "kwrite"]
editors_win = ["notepad++.exe", "notepad.exe"]
imager_usb_disk = []

@ -0,0 +1,176 @@
#!/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
import os
import string
import platform
import re
from .iso import *
from .isodump3 import ISO9660
from .gen import *
from . import _7zip
from . import config
def distro(iso_cfg_ext_dir, iso_link):
"""
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("/")
iso_file_list = _7zip.list_iso(iso_link)
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') or name.endswith('.CFG') or name.endswith('.txt') or name.endswith('.TXT') \
or name.endswith('.lst'):
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', 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'debian-installer', string, re.I) and isolinux_bin_exist(iso_link):
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=', string, re.I):
return "fedora"
elif re.search(r'redhat', string, re.I):
return "redhat"
# elif re.search(r'suse', string, re.I):
# return "suse"
elif re.search(r'opensuse', string, re.I):
return "opensuse"
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', string, re.I):
return "slitaz"
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|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', 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', 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'Welcome to CentOS', string, re.I):
return "centos-net-minimal"
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"
distro = detect_iso_from_file_list(iso_link)
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"
'''
else:
return None
def detect_iso_from_file_list(iso_link):
"""
Fallback detection script from the content of an ISO.
:return: supported distro as string
"""
if os.path.exists(iso_link):
iso_file_list = _7zip.list_iso(iso_link)
if any("sources" in s.lower() for s in iso_file_list) and any("boot.wim" in s.lower() for s in iso_file_list):
return "Windows"
elif any("config.isoclient" in s.lower() for s in iso_file_list):
return "opensuse"
elif any("dban" in s.lower() for s in iso_file_list):
return "slitaz"
elif any("memtest.img" in s.lower() for s in iso_file_list):
return "mentest"
elif any("menu.lst" in s.lower() for s in iso_file_list):
return "grub4dos"
elif any("bootwiz.cfg" in s.lower() for s in iso_file_list) and any("bootmenu_logo.png" in s.lower() for s in iso_file_list):
return "grub4dos_iso"
else:
log(iso_file_list)
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))

@ -0,0 +1,320 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*
# Name: log.py
# Purpose: This 'general' module contain many functions required to be called at many places
# 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
import logging
import sys
import os
import platform
import shutil
import string
import zipfile
import tempfile
def scripts_dir_path():
return os.path.dirname(os.path.realpath(__file__))
def log(message, info=True, error=False, debug=False):
"""
Dirty function to log messages to file and also print on screen.
:param message:
:param info:
:param error:
:param debug:
:return:
"""
# LOG_FILE_PATH = os.path.join(multibootusb_host_dir(), 'multibootusb.log')
LOG_FILE_PATH = mbusb_log_file()
if os.path.exists(LOG_FILE_PATH):
log_file_size = os.path.getsize(LOG_FILE_PATH) / (1024.0 * 1024.0)
if log_file_size > 1:
print('Removing log file as it crosses beyond 1mb')
os.remove(LOG_FILE_PATH)
logging.basicConfig(filename=LOG_FILE_PATH,
filemode='a',
format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
datefmt='%H:%M:%S',
level=logging.DEBUG)
print(message)
if info is True:
logging.info(message)
elif error is not False:
logging.error(message)
elif debug is not False:
logging.debug(message)
def resource_path(relativePath):
"""
Function to detect the correct path of file when working with sourcecode/install or binary.
:param relativePath: Path to file/data.
:return: Modified path to file/data.
"""
try:
basePath = sys._MEIPASS # Try if we are running as standalone executable
# log('Running stand alone executable.')
except:
basePath = '/usr/share/multibootusb' # Check if we run in installed environment
#if os.path.exists('/usr/share/multibootusb'):
#log('Running from installed machine.')
if not os.path.exists(basePath):
#basePath = os.path.abspath(".")
basePath = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
if os.path.exists(os.path.join(basePath, relativePath)):
path = os.path.join(basePath, relativePath)
return path
elif not os.path.exists(os.path.join(basePath, relativePath)):
if os.path.exists(os.path.join(os.path.abspath("."), relativePath)):
basePath = os.path.abspath(".")
elif os.path.exists(os.path.join(os.path.abspath(".."), relativePath)):
basePath = os.path.abspath("..")
path = os.path.join(basePath, relativePath)
return path
def print_version():
"""
Simple log the version number of the multibootusb application
:return:
"""
log('multibootusb version : ', mbusb_version())
def quote(text):
"""
Function to quote the input word or sentence.
:param text: Any word or sentence.
:return: Quoted text or sentence. If already quoted the same text is returned.
"""
if not is_quoted(text):
return '"' + text + '"'
else:
return text
def is_quoted(text):
"""
Function to check if word is quoted.
:param text: Any word or sentence with or without quote.
:return: True if text is quoted else False.
"""
if text.startswith("""") and text.endswith("""):
return True
else:
return False
def has_digit(word):
"""
Useful function to detect if input word contain digit.
:param word: Any alphanumeric word.
:return: True if the word has a digit else False.
"""
return any(char.isdigit() for char in word)
def sys_64bits():
"""
Detect if the host system is 64 bit.
:return: True if system is 64 bit.
"""
return sys.maxsize > 2**32
def mbusb_log_file():
"""
Function to genrate path to log file.
Under linux path is created as /tmp/multibootusb.log
Under Windows the file is created under
"""
if platform.system() == "Linux":
home_dir = os.path.expanduser('~')
# log_file = os.path.join(home_dir, "multibootusb.log")
log_file = os.path.join(tempfile.gettempdir(), "multibootusb.log")
elif platform.system() == "Windows":
# log_file = os.path.join(tempfile.gettempdir(), "multibootusb", "multibootusb.log")
log_file = os.path.join("multibootusb.log")
return log_file
def multibootusb_host_dir():
"""
Cross platform way to detect multibootusb directory on host system.
:return: Path to multibootusb directory of host system.
"""
if platform.system() == "Linux":
home_dir = os.path.expanduser('~')
mbusb_dir = os.path.join(home_dir, ".multibootusb")
elif platform.system() == "Windows":
mbusb_dir = os.path.join(tempfile.gettempdir(), "multibootusb")
return mbusb_dir
def iso_cfg_ext_dir():
"""
Function to return the path to ISO configuration file extraction directory.
:return: Path to directory where ISO config files will be extracted.
"""
return os.path.join(multibootusb_host_dir(), 'iso_cfg_ext_dir')
def clean_iso_cfg_ext_dir(iso_cfg_ext_dir):
"""
Clean old ISO config files extracted by previous use of multibootusb.
:param iso_cfg_ext_dir: Path to config extract directory.
:return:
"""
if os.path.exists(iso_cfg_ext_dir):
filelist = [f for f in os.listdir(iso_cfg_ext_dir)]
for f in filelist:
if os.path.isdir(os.path.join(iso_cfg_ext_dir, f)):
shutil.rmtree(os.path.join(iso_cfg_ext_dir, f))
else:
os.remove(os.path.join(iso_cfg_ext_dir, f))
else:
log('iso_cfg_ext_dir directory does not exist.')
def copy_mbusb_dir_usb(usb_disk):
"""
Copy the multibootusb directory to USB mount path.
:param usb_mount_path: Path to USB mount.
:return:
"""
from .iso import iso_size
from .usb import details
usb_details = details(usb_disk)
usb_mount_path = usb_details['mount_point']
if not os.path.exists(os.path.join(usb_mount_path, "multibootusb")):
try:
log('Copying multibootusb directory to ' + usb_mount_path)
shutil.copytree(resource_path(os.path.join("data", "multibootusb")), os.path.join(usb_mount_path, "multibootusb"))
return True
except:
return False
else:
log('multibootus directory already exist. Not copying.')
def read_input_yes():
"""
List option and read user input
:return: True if user selected yes or else false
"""
yes_list = ['Y', 'y', 'Yes', 'yes', 'YES']
no_list = ['N', 'n', 'No', 'no', 'NO']
response = input("Please enter the option listed above : ")
if response in yes_list:
return True
elif response in no_list:
return False
def strings(filename, min=4):
with open(filename, errors="ignore") as f:
result = ""
for c in f.read():
if c in string.printable:
result += c
continue
if len(result) >= min:
yield result
result = ""
if len(result) >= min: # catch result at EOF
yield result
def size_not_enough(iso_link, usb_disk):
from .iso import iso_size
from .usb import details
isoSize = iso_size(iso_link)
usb_details = details(usb_disk)
usb_size = usb_details['size_free']
if isoSize > usb_size:
return True
else:
return False
def mbusb_version():
version = open(resource_path(os.path.join("data", "version.txt")), 'r').read().strip()
return version
def prepare_mbusb_host_dir():
"""
Prepare multibootusb host directory and extract data files for use.
:return:
"""
home = multibootusb_host_dir()
if not os.path.exists(home):
os.makedirs(home)
else:
log("Cleaning old multibootusb directory...")
shutil.rmtree(home)
os.makedirs(home)
if not os.path.exists(os.path.join(home, "preference")):
os.makedirs(os.path.join(home, "preference"))
if not os.path.exists(os.path.join(home, "iso_cfg_ext_dir")):
os.makedirs(os.path.join(home, "iso_cfg_ext_dir"))
if os.path.exists(os.path.join(home, "syslinux", "bin", "syslinux4")):
log("Syslinux exist in multibootusb directory...")
else:
log("Extracting syslinux to multibootusb directory...")
if platform.system() == "Linux":
if sys_64bits() is True:
log('Host OS is 64 bit...')
log("Extracting syslinux 64 bit...")
# log(resource_path(os.path.join("data", "tools", "syslinux", "syslinux_linux_64.zip")))
with zipfile.ZipFile(resource_path(os.path.join("data", "tools", "syslinux", "syslinux_linux_64.zip")), "r") as z:
z.extractall(home)
else:
log("Extracting syslinux 32 bit...")
with zipfile.ZipFile(resource_path(os.path.join("data", "tools", "syslinux", "syslinux_linux.zip")), "r") as z:
z.extractall(home)
else:
with zipfile.ZipFile(resource_path(os.path.join("data", "tools", "syslinux", "syslinux_windows.zip")), "r") as z:
z.extractall(home)
log("Extracting syslinux modules to multibootusb directory...")
with zipfile.ZipFile(resource_path(os.path.join("data", "tools", "syslinux", "syslinux_modules.zip")), "r") as z:
z.extractall(os.path.join(home, "syslinux"))
if os.listdir(os.path.join(home, "iso_cfg_ext_dir")):
log(os.listdir(os.path.join(home, "iso_cfg_ext_dir")))
log("iso extract directory is not empty.")
log("Removing junk files...")
for files in os.listdir(os.path.join(home, "iso_cfg_ext_dir")):
if os.path.isdir(os.path.join(os.path.join(home, "iso_cfg_ext_dir", files))):
log (os.path.join(os.path.join(home, "iso_cfg_ext_dir", files)))
os.chmod(os.path.join(os.path.join(home, "iso_cfg_ext_dir", files)), 0o777)
shutil.rmtree(os.path.join(os.path.join(home, "iso_cfg_ext_dir", files)))
else:
try:
log (os.path.join(os.path.join(home, "iso_cfg_ext_dir", files)))
os.chmod(os.path.join(os.path.join(home, "iso_cfg_ext_dir", files)), 0o777)
os.unlink(os.path.join(os.path.join(home, "iso_cfg_ext_dir", files)))
os.remove(os.path.join(os.path.join(home, "iso_cfg_ext_dir", files)))
except OSError:
log("Can't remove the file. Skipping it.")
if __name__ == '__main__':
log(quote("""Test-string"""))
log(has_digit("test-string-with-01-digit"))
log(sys_64bits())
log(multibootusb_host_dir())
log(iso_cfg_ext_dir())
strings_test = strings('../../text-stings.bin')
log(strings_test)

@ -0,0 +1,455 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'scripts\gui\multibootusb.ui'
#
# Created by: PyQt5 UI code generator 5.6
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(644, 516)
self.horizontalLayout = QtWidgets.QHBoxLayout(Dialog)
self.horizontalLayout.setObjectName("horizontalLayout")
self.tabWidget = QtWidgets.QTabWidget(Dialog)
self.tabWidget.setObjectName("tabWidget")
self.tab_3 = QtWidgets.QWidget()
self.tab_3.setObjectName("tab_3")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.tab_3)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.label_persistence_value = QtWidgets.QLabel(self.tab_3)
self.label_persistence_value.setObjectName("label_persistence_value")
self.gridLayout.addWidget(self.label_persistence_value, 5, 2, 1, 1)
self.verticalLayout_11 = QtWidgets.QVBoxLayout()
self.verticalLayout_11.setObjectName("verticalLayout_11")
self.groupBox_11 = QtWidgets.QGroupBox(self.tab_3)
self.groupBox_11.setObjectName("groupBox_11")
self.horizontalLayout_8 = QtWidgets.QHBoxLayout(self.groupBox_11)
self.horizontalLayout_8.setObjectName("horizontalLayout_8")
self.detect_usb = QtWidgets.QPushButton(self.groupBox_11)
self.detect_usb.setObjectName("detect_usb")
self.horizontalLayout_8.addWidget(self.detect_usb)
self.verticalLayout_11.addWidget(self.groupBox_11)
self.gridLayout.addLayout(self.verticalLayout_11, 1, 3, 1, 2)
self.listWidget = QtWidgets.QListWidget(self.tab_3)
self.listWidget.setObjectName("listWidget")
self.gridLayout.addWidget(self.listWidget, 0, 0, 4, 3)
self.progressBar = QtWidgets.QProgressBar(self.tab_3)
self.progressBar.setProperty("value", 0)
self.progressBar.setObjectName("progressBar")
self.gridLayout.addWidget(self.progressBar, 7, 0, 1, 6)
self.create = QtWidgets.QPushButton(self.tab_3)
self.create.setObjectName("create")
self.gridLayout.addWidget(self.create, 5, 4, 1, 1)
self.comboBox = QtWidgets.QComboBox(self.tab_3)
self.comboBox.setObjectName("comboBox")
self.gridLayout.addWidget(self.comboBox, 0, 3, 1, 1)
self.labelstep1 = QtWidgets.QLabel(self.tab_3)
self.labelstep1.setObjectName("labelstep1")
self.gridLayout.addWidget(self.labelstep1, 0, 5, 1, 1)
self.labelstep2 = QtWidgets.QLabel(self.tab_3)
self.labelstep2.setObjectName("labelstep2")
self.gridLayout.addWidget(self.labelstep2, 4, 5, 1, 1)
self.close = QtWidgets.QPushButton(self.tab_3)
self.close.setObjectName("close")
self.gridLayout.addWidget(self.close, 5, 3, 1, 1)
self.labelstep3 = QtWidgets.QLabel(self.tab_3)
self.labelstep3.setObjectName("labelstep3")
self.gridLayout.addWidget(self.labelstep3, 5, 5, 1, 1)
self.slider_persistence = QtWidgets.QSlider(self.tab_3)
self.slider_persistence.setEnabled(False)
self.slider_persistence.setAutoFillBackground(False)
self.slider_persistence.setOrientation(QtCore.Qt.Horizontal)
self.slider_persistence.setTickPosition(QtWidgets.QSlider.TicksBothSides)
self.slider_persistence.setTickInterval(0)
self.slider_persistence.setObjectName("slider_persistence")
self.gridLayout.addWidget(self.slider_persistence, 5, 1, 1, 1)
self.groupBox = QtWidgets.QGroupBox(self.tab_3)
self.groupBox.setObjectName("groupBox")
self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox)
self.verticalLayout.setObjectName("verticalLayout")
self.gridLayout_5 = QtWidgets.QGridLayout()
self.gridLayout_5.setObjectName("gridLayout_5")
self.uninstall = QtWidgets.QPushButton(self.groupBox)
self.uninstall.setObjectName("uninstall")
self.gridLayout_5.addWidget(self.uninstall, 0, 0, 1, 1)
self.verticalLayout.addLayout(self.gridLayout_5)
self.gridLayout.addWidget(self.groupBox, 3, 3, 1, 2)
self.lineEdit = QtWidgets.QLineEdit(self.tab_3)
self.lineEdit.setObjectName("lineEdit")
self.gridLayout.addWidget(self.lineEdit, 4, 0, 1, 4)
self.groupBox_6 = QtWidgets.QGroupBox(self.tab_3)
self.groupBox_6.setObjectName("groupBox_6")
self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.groupBox_6)
self.verticalLayout_5.setObjectName("verticalLayout_5")
self.usb_dev = QtWidgets.QLabel(self.groupBox_6)
self.usb_dev.setObjectName("usb_dev")
self.verticalLayout_5.addWidget(self.usb_dev)
self.usb_vendor = QtWidgets.QLabel(self.groupBox_6)
self.usb_vendor.setObjectName("usb_vendor")
self.verticalLayout_5.addWidget(self.usb_vendor)
self.usb_model = QtWidgets.QLabel(self.groupBox_6)
self.usb_model.setObjectName("usb_model")
self.verticalLayout_5.addWidget(self.usb_model)
self.usb_size = QtWidgets.QLabel(self.groupBox_6)
self.usb_size.setObjectName("usb_size")
self.verticalLayout_5.addWidget(self.usb_size)
self.usb_mount = QtWidgets.QLabel(self.groupBox_6)
self.usb_mount.setObjectName("usb_mount")
self.verticalLayout_5.addWidget(self.usb_mount)
self.gridLayout.addWidget(self.groupBox_6, 2, 3, 1, 3)
self.browse_iso = QtWidgets.QPushButton(self.tab_3)
self.browse_iso.setObjectName("browse_iso")
self.gridLayout.addWidget(self.browse_iso, 4, 4, 1, 1)
self.label_persistence = QtWidgets.QLabel(self.tab_3)
self.label_persistence.setObjectName("label_persistence")
self.gridLayout.addWidget(self.label_persistence, 5, 0, 1, 1)
self.checkBox_all_drives = QtWidgets.QCheckBox(self.tab_3)
self.checkBox_all_drives.setObjectName("checkBox_all_drives")
self.gridLayout.addWidget(self.checkBox_all_drives, 0, 4, 1, 1)
self.status = QtWidgets.QLabel(self.tab_3)
self.status.setMinimumSize(QtCore.QSize(0, 0))
self.status.setAcceptDrops(False)
self.status.setAutoFillBackground(False)
self.status.setFrameShadow(QtWidgets.QFrame.Plain)
self.status.setText("")
self.status.setTextFormat(QtCore.Qt.AutoText)
self.status.setScaledContents(False)
self.status.setObjectName("status")
self.gridLayout.addWidget(self.status, 6, 0, 1, 6)
self.horizontalLayout_2.addLayout(self.gridLayout)
self.tabWidget.addTab(self.tab_3, "")
self.imager = QtWidgets.QWidget()
self.imager.setObjectName("imager")
self.horizontalLayout_7 = QtWidgets.QHBoxLayout(self.imager)
self.horizontalLayout_7.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.gridLayout_9 = QtWidgets.QGridLayout()
self.gridLayout_9.setObjectName("gridLayout_9")
self.groupBox_7 = QtWidgets.QGroupBox(self.imager)
self.groupBox_7.setObjectName("groupBox_7")
self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.groupBox_7)
self.verticalLayout_6.setObjectName("verticalLayout_6")
self.gridLayout_11 = QtWidgets.QGridLayout()
self.gridLayout_11.setObjectName("gridLayout_11")
self.groupBox_9 = QtWidgets.QGroupBox(self.groupBox_7)
self.groupBox_9.setObjectName("groupBox_9")
self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.groupBox_9)
self.verticalLayout_8.setObjectName("verticalLayout_8")
self.comboBox_2 = QtWidgets.QComboBox(self.groupBox_9)
self.comboBox_2.setObjectName("comboBox_2")
self.verticalLayout_8.addWidget(self.comboBox_2)
self.pushbtn_imager_refreshusb = QtWidgets.QPushButton(self.groupBox_9)
self.pushbtn_imager_refreshusb.setObjectName("pushbtn_imager_refreshusb")
self.verticalLayout_8.addWidget(self.pushbtn_imager_refreshusb)
self.imager_disk_label = QtWidgets.QLabel(self.groupBox_9)
self.imager_disk_label.setObjectName("imager_disk_label")
self.verticalLayout_8.addWidget(self.imager_disk_label)
self.imager_total_size = QtWidgets.QLabel(self.groupBox_9)
self.imager_total_size.setObjectName("imager_total_size")
self.verticalLayout_8.addWidget(self.imager_total_size)
self.imager_uuid = QtWidgets.QLabel(self.groupBox_9)
self.imager_uuid.setObjectName("imager_uuid")
self.verticalLayout_8.addWidget(self.imager_uuid)
self.gridLayout_11.addWidget(self.groupBox_9, 0, 0, 1, 1)
self.groupBox_10 = QtWidgets.QGroupBox(self.groupBox_7)
self.groupBox_10.setObjectName("groupBox_10")
self.verticalLayout_9 = QtWidgets.QVBoxLayout(self.groupBox_10)
self.verticalLayout_9.setObjectName("verticalLayout_9")
self.pushButton = QtWidgets.QPushButton(self.groupBox_10)
self.pushButton.setObjectName("pushButton")
self.verticalLayout_9.addWidget(self.pushButton)
self.lineEdit_3 = QtWidgets.QLineEdit(self.groupBox_10)
self.lineEdit_3.setObjectName("lineEdit_3")
self.verticalLayout_9.addWidget(self.lineEdit_3)
self.imager_bootable = QtWidgets.QLabel(self.groupBox_10)
self.imager_bootable.setObjectName("imager_bootable")
self.verticalLayout_9.addWidget(self.imager_bootable)
self.imager_iso_size = QtWidgets.QLabel(self.groupBox_10)
self.imager_iso_size.setObjectName("imager_iso_size")
self.verticalLayout_9.addWidget(self.imager_iso_size)
self.gridLayout_11.addWidget(self.groupBox_10, 0, 1, 1, 1)
self.verticalLayout_6.addLayout(self.gridLayout_11)
self.gridLayout_9.addWidget(self.groupBox_7, 1, 0, 1, 1)
self.groupBox_8 = QtWidgets.QGroupBox(self.imager)
self.groupBox_8.setObjectName("groupBox_8")
self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.groupBox_8)
self.verticalLayout_7.setObjectName("verticalLayout_7")
self.imager_label_status = QtWidgets.QLabel(self.groupBox_8)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.imager_label_status.sizePolicy().hasHeightForWidth())
self.imager_label_status.setSizePolicy(sizePolicy)
self.imager_label_status.setText("")
self.imager_label_status.setObjectName("imager_label_status")
self.verticalLayout_7.addWidget(self.imager_label_status)
self.gridLayout_12 = QtWidgets.QGridLayout()
self.gridLayout_12.setObjectName("gridLayout_12")
self.imager_write = QtWidgets.QPushButton(self.groupBox_8)
self.imager_write.setObjectName("imager_write")
self.gridLayout_12.addWidget(self.imager_write, 2, 2, 1, 1)
self.imager_close = QtWidgets.QPushButton(self.groupBox_8)
self.imager_close.setObjectName("imager_close")
self.gridLayout_12.addWidget(self.imager_close, 2, 1, 1, 1)
self.label_10 = QtWidgets.QLabel(self.groupBox_8)
self.label_10.setObjectName("label_10")
self.gridLayout_12.addWidget(self.label_10, 1, 0, 1, 3)
self.imager_progressbar = QtWidgets.QProgressBar(self.groupBox_8)
self.imager_progressbar.setProperty("value", 0)
self.imager_progressbar.setObjectName("imager_progressbar")
self.gridLayout_12.addWidget(self.imager_progressbar, 0, 0, 1, 3)
self.verticalLayout_7.addLayout(self.gridLayout_12)
self.gridLayout_9.addWidget(self.groupBox_8, 2, 0, 1, 1)
self.horizontalLayout_7.addLayout(self.gridLayout_9)
self.tabWidget.addTab(self.imager, "")
self.syslinux_ab = QtWidgets.QWidget()
self.syslinux_ab.setObjectName("syslinux_ab")
self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.syslinux_ab)
self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.gridLayout_2 = QtWidgets.QGridLayout()
self.gridLayout_2.setObjectName("gridLayout_2")
self.groupBox_2 = QtWidgets.QGroupBox(self.syslinux_ab)
self.groupBox_2.setAutoFillBackground(False)
self.groupBox_2.setObjectName("groupBox_2")
self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.groupBox_2)
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.gridLayout_3 = QtWidgets.QGridLayout()
self.gridLayout_3.setObjectName("gridLayout_3")
self.install_syslinux = QtWidgets.QPushButton(self.groupBox_2)
self.install_syslinux.setObjectName("install_syslinux")
self.gridLayout_3.addWidget(self.install_syslinux, 2, 1, 1, 1)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.gridLayout_3.addItem(spacerItem, 2, 0, 1, 1)
self.install_sys_all = QtWidgets.QRadioButton(self.groupBox_2)
self.install_sys_all.setObjectName("install_sys_all")
self.gridLayout_3.addWidget(self.install_sys_all, 1, 0, 1, 2)
self.install_sys_only = QtWidgets.QRadioButton(self.groupBox_2)
self.install_sys_only.setObjectName("install_sys_only")
self.gridLayout_3.addWidget(self.install_sys_only, 0, 0, 1, 2)
self.horizontalLayout_4.addLayout(self.gridLayout_3)
self.gridLayout_2.addWidget(self.groupBox_2, 0, 0, 1, 1)
self.groupBox_3 = QtWidgets.QGroupBox(self.syslinux_ab)
self.groupBox_3.setObjectName("groupBox_3")
self.horizontalLayout_5 = QtWidgets.QHBoxLayout(self.groupBox_3)
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.gridLayout_4 = QtWidgets.QGridLayout()
self.gridLayout_4.setObjectName("gridLayout_4")
self.edit_syslinux = QtWidgets.QPushButton(self.groupBox_3)
self.edit_syslinux.setObjectName("edit_syslinux")
self.gridLayout_4.addWidget(self.edit_syslinux, 1, 1, 1, 1)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.gridLayout_4.addItem(spacerItem1, 1, 0, 1, 1)
self.label_2 = QtWidgets.QLabel(self.groupBox_3)
self.label_2.setObjectName("label_2")
self.gridLayout_4.addWidget(self.label_2, 0, 0, 1, 2)
self.horizontalLayout_5.addLayout(self.gridLayout_4)
self.gridLayout_2.addWidget(self.groupBox_3, 1, 0, 1, 1)
self.horizontalLayout_3.addLayout(self.gridLayout_2)
self.tabWidget.addTab(self.syslinux_ab, "")
self.tab = QtWidgets.QWidget()
self.tab.setObjectName("tab")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.tab)
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.groupBox_5 = QtWidgets.QGroupBox(self.tab)
self.groupBox_5.setObjectName("groupBox_5")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox_5)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.gridLayout_7 = QtWidgets.QGridLayout()
self.gridLayout_7.setObjectName("gridLayout_7")
self.ram_iso_2048 = QtWidgets.QRadioButton(self.groupBox_5)
self.ram_iso_2048.setObjectName("ram_iso_2048")
self.gridLayout_7.addWidget(self.ram_iso_2048, 4, 4, 1, 1)
self.ram_iso_1024 = QtWidgets.QRadioButton(self.groupBox_5)
self.ram_iso_1024.setObjectName("ram_iso_1024")
self.gridLayout_7.addWidget(self.ram_iso_1024, 4, 3, 1, 1)
self.ram_iso_256 = QtWidgets.QRadioButton(self.groupBox_5)
self.ram_iso_256.setObjectName("ram_iso_256")
self.gridLayout_7.addWidget(self.ram_iso_256, 4, 0, 1, 1)
self.browse_iso_qemu = QtWidgets.QPushButton(self.groupBox_5)
self.browse_iso_qemu.setObjectName("browse_iso_qemu")
self.gridLayout_7.addWidget(self.browse_iso_qemu, 2, 4, 1, 1)
self.label_7 = QtWidgets.QLabel(self.groupBox_5)
self.label_7.setObjectName("label_7")
self.gridLayout_7.addWidget(self.label_7, 0, 0, 1, 5)
self.ram_iso_512 = QtWidgets.QRadioButton(self.groupBox_5)
self.ram_iso_512.setObjectName("ram_iso_512")
self.gridLayout_7.addWidget(self.ram_iso_512, 4, 1, 1, 1)
self.boot_iso_qemu = QtWidgets.QPushButton(self.groupBox_5)
self.boot_iso_qemu.setObjectName("boot_iso_qemu")
self.gridLayout_7.addWidget(self.boot_iso_qemu, 6, 4, 1, 1)
self.ram_iso_768 = QtWidgets.QRadioButton(self.groupBox_5)
self.ram_iso_768.setObjectName("ram_iso_768")
self.gridLayout_7.addWidget(self.ram_iso_768, 4, 2, 1, 1)
self.lineEdit_2 = QtWidgets.QLineEdit(self.groupBox_5)
self.lineEdit_2.setObjectName("lineEdit_2")
self.gridLayout_7.addWidget(self.lineEdit_2, 2, 0, 1, 4)
spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_7.addItem(spacerItem2, 3, 1, 1, 1)
self.label_3 = QtWidgets.QLabel(self.groupBox_5)
self.label_3.setObjectName("label_3")
self.gridLayout_7.addWidget(self.label_3, 6, 0, 1, 4)
spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_7.addItem(spacerItem3, 5, 0, 1, 1)
spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_7.addItem(spacerItem4, 1, 0, 1, 1)
self.verticalLayout_3.addLayout(self.gridLayout_7)
self.verticalLayout_2.addWidget(self.groupBox_5)
spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_2.addItem(spacerItem5)
self.gridLayout_6 = QtWidgets.QGridLayout()
self.gridLayout_6.setObjectName("gridLayout_6")
self.groupBox_4 = QtWidgets.QGroupBox(self.tab)
self.groupBox_4.setObjectName("groupBox_4")
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.groupBox_4)
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.gridLayout_8 = QtWidgets.QGridLayout()
self.gridLayout_8.setObjectName("gridLayout_8")
self.ram_usb_768 = QtWidgets.QRadioButton(self.groupBox_4)
self.ram_usb_768.setObjectName("ram_usb_768")
self.gridLayout_8.addWidget(self.ram_usb_768, 2, 2, 1, 1)
self.ram_usb_256 = QtWidgets.QRadioButton(self.groupBox_4)
self.ram_usb_256.setObjectName("ram_usb_256")
self.gridLayout_8.addWidget(self.ram_usb_256, 2, 0, 1, 1)
self.label_6 = QtWidgets.QLabel(self.groupBox_4)
self.label_6.setObjectName("label_6")
self.gridLayout_8.addWidget(self.label_6, 0, 0, 1, 5)
self.ram_usb_1024 = QtWidgets.QRadioButton(self.groupBox_4)
self.ram_usb_1024.setObjectName("ram_usb_1024")
self.gridLayout_8.addWidget(self.ram_usb_1024, 2, 3, 1, 1)
self.label_4 = QtWidgets.QLabel(self.groupBox_4)
self.label_4.setObjectName("label_4")
self.gridLayout_8.addWidget(self.label_4, 4, 0, 1, 4)
self.ram_usb_512 = QtWidgets.QRadioButton(self.groupBox_4)
self.ram_usb_512.setObjectName("ram_usb_512")
self.gridLayout_8.addWidget(self.ram_usb_512, 2, 1, 1, 1)
self.boot_usb_qemu = QtWidgets.QPushButton(self.groupBox_4)
self.boot_usb_qemu.setObjectName("boot_usb_qemu")
self.gridLayout_8.addWidget(self.boot_usb_qemu, 4, 4, 1, 1)
self.ram_usb_2048 = QtWidgets.QRadioButton(self.groupBox_4)
self.ram_usb_2048.setObjectName("ram_usb_2048")
self.gridLayout_8.addWidget(self.ram_usb_2048, 2, 4, 1, 1)
spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_8.addItem(spacerItem6, 1, 1, 1, 1)
spacerItem7 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_8.addItem(spacerItem7, 3, 2, 1, 1)
spacerItem8 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_8.addItem(spacerItem8, 5, 0, 1, 1)
self.verticalLayout_4.addLayout(self.gridLayout_8)
self.gridLayout_6.addWidget(self.groupBox_4, 0, 0, 1, 1)
self.verticalLayout_2.addLayout(self.gridLayout_6)
self.tabWidget.addTab(self.tab, "")
self.tab_2 = QtWidgets.QWidget()
self.tab_2.setObjectName("tab_2")
self.horizontalLayout_6 = QtWidgets.QHBoxLayout(self.tab_2)
self.horizontalLayout_6.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_6.setObjectName("horizontalLayout_6")
self.gridLayout_10 = QtWidgets.QGridLayout()
self.gridLayout_10.setObjectName("gridLayout_10")
spacerItem9 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
self.gridLayout_10.addItem(spacerItem9, 0, 1, 1, 1)
spacerItem10 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.gridLayout_10.addItem(spacerItem10, 1, 0, 1, 1)
spacerItem11 = QtWidgets.QSpacerItem(20, 30, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
self.gridLayout_10.addItem(spacerItem11, 2, 1, 1, 1)
self.label_5 = QtWidgets.QLabel(self.tab_2)
self.label_5.setObjectName("label_5")
self.gridLayout_10.addWidget(self.label_5, 1, 1, 1, 1)
spacerItem12 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.gridLayout_10.addItem(spacerItem12, 1, 2, 1, 1)
self.horizontalLayout_6.addLayout(self.gridLayout_10)
self.tabWidget.addTab(self.tab_2, "")
self.horizontalLayout.addWidget(self.tabWidget)
self.retranslateUi(Dialog)
self.tabWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "multibootusb"))
self.label_persistence_value.setText(_translate("Dialog", "0 MB"))
self.groupBox_11.setTitle(_translate("Dialog", "Detect"))
self.detect_usb.setText(_translate("Dialog", "Detect Drives"))
self.create.setText(_translate("Dialog", "Create"))
self.labelstep1.setText(_translate("Dialog", "<html><head/><body><p align=\"center\"><span style=\" font-weight:600;\">Step 1</span></p></body></html>"))
self.labelstep2.setText(_translate("Dialog", "<html><head/><body><p align=\"center\"><span style=\" font-weight:600;\">Step 2</span></p></body></html>"))
self.close.setText(_translate("Dialog", "Close"))
self.labelstep3.setText(_translate("Dialog", "<html><head/><body><p align=\"center\"><span style=\" font-weight:600;\">Step 3</span></p></body></html>"))
self.slider_persistence.setToolTip(_translate("Dialog", "Choose Persistence size. Not all distros supports persistence..."))
self.groupBox.setTitle(_translate("Dialog", "Uninstall (Optional)"))
self.uninstall.setText(_translate("Dialog", "Uninstall Distro"))
self.groupBox_6.setTitle(_translate("Dialog", "USB Details"))
self.usb_dev.setText(_translate("Dialog", "Drive ::"))
self.usb_vendor.setText(_translate("Dialog", "Vendor ::"))
self.usb_model.setText(_translate("Dialog", "Model::"))
self.usb_size.setText(_translate("Dialog", "Size ::"))
self.usb_mount.setText(_translate("Dialog", "Mount ::"))
self.browse_iso.setText(_translate("Dialog", "Browse ISO"))
self.label_persistence.setText(_translate("Dialog", "<html><head/><body><p><span style=\" font-weight:600;\">Persistence</span></p></body></html>"))
self.checkBox_all_drives.setText(_translate("Dialog", "All Drives"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("Dialog", "MultiBootUSB"))
self.groupBox_7.setTitle(_translate("Dialog", "Imager"))
self.groupBox_9.setTitle(_translate("Dialog", "-------------- USB details -------------------"))
self.pushbtn_imager_refreshusb.setText(_translate("Dialog", "Refresh USB"))
self.imager_disk_label.setText(_translate("Dialog", "Disk Label ::"))
self.imager_total_size.setText(_translate("Dialog", "Disk Size"))
self.imager_uuid.setText(_translate("Dialog", "Disk Label ::"))
self.groupBox_10.setTitle(_translate("Dialog", "------------------------------ ISO details ----------------------------------"))
self.pushButton.setText(_translate("Dialog", "Browse ISO"))
self.imager_bootable.setText(_translate("Dialog", "Bootable ISO"))
self.imager_iso_size.setText(_translate("Dialog", "ISO Size"))
self.groupBox_8.setTitle(_translate("Dialog", "Imager Progress"))
self.imager_write.setText(_translate("Dialog", "Write/Create"))
self.imager_close.setText(_translate("Dialog", "Close"))
self.label_10.setText(_translate("Dialog", "<html><head/><body><p><span style=\" font-weight:600; color:#ff0000;\">WARNING</span> : Any bootable USB made using<span style=\" font-weight:600;\"> ISO Imager will destroy all data </span>on the selected USB disk. </p><p>Use it at your own risk. Developers are not responsile for loss of any data.</p></body></html>"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.imager), _translate("Dialog", "ISO Imager"))
self.groupBox_2.setTitle(_translate("Dialog", "Install Syslinux"))
self.install_syslinux.setText(_translate("Dialog", "Install"))
self.install_sys_all.setText(_translate("Dialog", "Install syslinux and copy all required files."))
self.install_sys_only.setText(_translate("Dialog", "Install only syslinux (existing configurations will not be altred)."))
self.groupBox_3.setTitle(_translate("Dialog", "Edit syslinux.cfg"))
self.edit_syslinux.setText(_translate("Dialog", "Edit"))
self.label_2.setText(_translate("Dialog", "<html><head/><body><p align=\"justify\">Using this option user can edit syslinux.cfg file directly. It directly uses </p><p align=\"justify\">default editor of host system. Be careful while editing syslinux.cfg file.</p></body></html>"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.syslinux_ab), _translate("Dialog", "Syslinux"))
self.groupBox_5.setTitle(_translate("Dialog", "Boot ISO"))
self.ram_iso_2048.setText(_translate("Dialog", "2048 MB"))
self.ram_iso_1024.setText(_translate("Dialog", "1024 MB"))
self.ram_iso_256.setText(_translate("Dialog", "256 MB"))
self.browse_iso_qemu.setText(_translate("Dialog", "Browse ISO"))
self.label_7.setText(_translate("Dialog", "<html><head/><body><p>Best way to test your downloaded ISOs. </p></body></html>"))
self.ram_iso_512.setText(_translate("Dialog", "512 MB"))
self.boot_iso_qemu.setText(_translate("Dialog", "Boot ISO"))
self.ram_iso_768.setText(_translate("Dialog", "768 MB"))
self.label_3.setText(_translate("Dialog", "Choose desired RAM and click on Boot ISO button."))
self.groupBox_4.setTitle(_translate("Dialog", "Boot USB"))
self.ram_usb_768.setText(_translate("Dialog", "768 MB"))
self.ram_usb_256.setText(_translate("Dialog", "256 MB"))
self.label_6.setText(_translate("Dialog", "<html><head/><body><p align=\"justify\">Use this option if you want to check USB installation without reboot.</p></body></html>"))
self.ram_usb_1024.setText(_translate("Dialog", "1024 MB"))
self.label_4.setText(_translate("Dialog", "Choose desired RAM and click on Boot USB button."))
self.ram_usb_512.setText(_translate("Dialog", "512 MB"))
self.boot_usb_qemu.setText(_translate("Dialog", "Boot USB"))
self.ram_usb_2048.setText(_translate("Dialog", "2048 MB"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("Dialog", "QEMU"))
self.label_5.setText(_translate("Dialog", "<html><head/><body><p align=\"center\">An advanced bootable usb creator with option to install/uninstall </p><p align=\"center\">multiple distros. This software is written in python and pyqt. </p><p align=\"center\">Copyright 2010-2016 Sundar</p><p align=\"center\"><span style=\" font-weight:600; text-decoration: underline;\">Author(s)</span>: Sundar, Ian Bruce, Lee</p><p align=\"center\"><span style=\" font-weight:600; text-decoration: underline;\">Licence:</span> GPL version 2 or later</p><p align=\"center\"><span style=\" font-weight:600; text-decoration: underline;\">Home page</span>: <a href=\" http://multibootusb.org\"><span style=\" text-decoration: underline; color:#0000ff;\">http://multibootusb.org</span></a></p><p align=\"center\"><span style=\" font-weight:600; text-decoration: underline;\">Help/Email:</span> feedback.multibootusb@gmail.com</p><p align=\"center\"><span style=\" font-weight:600; text-decoration: underline;\">Source Code:</span><span style=\" font-weight:600;\"/><a href=\"https://github.com/mbusb/multibootusb\"><span style=\" text-decoration: underline; color:#0000ff;\">https://github.com/mbusb/multibootusb</span></a></p><p><br/></p></body></html>"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("Dialog", "About"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())

@ -0,0 +1,226 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Name: imager.py
# Purpose: Module to write ISO image to selected USB disk. Uses dd as backend.
# 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
# WARNING : Any boot-able USB made using this module will destroy data stored on USB disk.
import os
import subprocess
import collections
import platform
import signal
from PyQt5 import QtGui
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from .gui.ui_multibootusb import Ui_Dialog
from .gen import *
from . import iso
from . import usb
from . import config
from . import progressbar
if platform.system() == "Windows":
import win32com.client
def dd_linux():
import time
input = "if=" + config.imager_iso_link
in_file_size = float(os.path.getsize(config.imager_iso_link))
output = "of=" + config.imager_usb_disk
os.system("umount " + config.imager_usb_disk + "1")
command = ['dd', input, output, "bs=1M"]
log("Executing ==> " + " ".join(command))
dd_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
pbar = progressbar.ProgressBar(maxval=100).start() # bar = progressbar.ProgressBar(redirect_stdout=True)
while dd_process.poll() is None:
time.sleep(.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:
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("Executing ==> sync")
os.system("sync")
log("ISO has been written to USB disk...")
return
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.imager_iso_link
in_file_size = float(os.path.getsize(config.imager_iso_link) / 1024 / 1024)
output = "of=\\\.\\" + config.imager_usb_disk
command = [windd, input, output, "bs=1M", "--progress"]
log("Executing ==> " + " ".join(command))
dd_process = subprocess.Popen(command, universal_newlines=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE,
shell=False)
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...")
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...")
return
class Imager(QtWidgets.QDialog, Ui_Dialog):
"""
Raw write to USB disk using dd.
"""
def __init__(self):
QtWidgets.QDialog.__init__(self)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
def on_Imager_Browse_iso_Click(self):
"""
Browse and choose an ISO.
:return:
"""
self.ui.lineEdit_3.clear()
config.imager_iso_link = QtWidgets.QFileDialog.getOpenFileName(self, 'Select an iso...', "", "ISO Files (*.iso)")[0]
if config.imager_iso_link:
if platform.system() == "Windows":
if "/" in config.imager_iso_link:
config.imager_iso_link = config.imager_iso_link.strip().replace("/", "\\")
self.ui.lineEdit_3.insert(str(config.imager_iso_link))
self.add_iso_gui_label_text()
else:
log("File not selected...")
def add_iso_gui_label_text(self):
"""
Simple function to add text label to GUI widgets.
:return:
"""
log("Testing ISO...")
if iso.is_bootable(config.imager_iso_link) is True:
self.ui.imager_bootable.setText("Bootable ISO :: Yes")
log("ISO is bootable.")
else:
self.ui.imager_bootable.setText("Bootable ISO :: No")
log("ISO is not bootable.")
if os.path.exists(config.imager_iso_link):
log("Path " + config.imager_iso_link + " is exist...")
self.iso_size = str(round(os.path.getsize(config.imager_iso_link) / 1024 / 1024))
self.ui.imager_iso_size.setText("ISO Size :: " + self.iso_size + " MB")
log("ISO Size is " + self.iso_size + " MB")
def onImagerComboChange(self):
config.imager_usb_disk = str(self.ui.comboBox_2.currentText())
if bool(config.imager_usb_disk):
self.ui.imager_disk_label.setText("Disk Type :: " + self.imager_usb_detail(config.imager_usb_disk,
partition=0).usb_type)
self.ui.imager_total_size.setText("Disk Size :: " + usb.bytes2human(int(self.imager_usb_detail
(config.imager_usb_disk,
partition=0).total_size)))
if platform.system() == "Linux":
self.ui.imager_uuid.setText("Disk Model :: " + str(self.imager_usb_detail(config.imager_usb_disk,
partition=0).model))
else:
self.ui.imager_uuid.setText("Disk Label :: " + self.imager_usb_detail(config.imager_usb_disk,
partition=0).model)
def imager_list_usb(self, partition=1):
"""
Function to detect whole USB disk. It uses lsblk package on Linux.
:param partition: What to return. By default partition is set.
:return: USB disk/partition as list
"""
disk = []
if platform.system() == "Linux":
output = subprocess.check_output("lsblk -i", shell=True)
if not partition == 1:
for line in output.splitlines():
line = line.split()
if (line[2].strip()) == b'1' and (line[5].strip()) == b'disk':
disk.append(str("/dev/" + str(line[0].strip().decode())))
elif partition == 1:
for line in output.splitlines():
line = line.split()
if (line[2].strip()) == b'1' and line[5].strip() == b'part':
disk.append(str("/dev/" + str(line[0].strip()[2:])))
else:
if partition == 1 or not partition == 1:
oFS = win32com.client.Dispatch("Scripting.FileSystemObject")
oDrives = oFS.Drives
for drive in oDrives:
if drive.DriveType == 1 and drive.IsReady:
disk.append(drive)
return disk
def imager_usb_detail(self, usb_disk, partition=1):
"""
Function to detect details of USB disk using lsblk
:param usb_disk: path to usb disk
:param partition: by default partition is set (but yet to code for it)
:return: details of size, type and model as tuples
"""
_ntuple_diskusage = collections.namedtuple('usage', 'total_size usb_type model')
if platform.system() == "Linux":
output = subprocess.check_output("lsblk -ib " + usb_disk, shell=True)
for line in output.splitlines():
line = line.split()
if not partition == 1:
if line[2].strip() == b'1' and line[5].strip() == b'disk':
total_size = line[3]
if not total_size:
total_size = "Unknown"
usb_type = "Removable"
model = subprocess.check_output("lsblk -in -f -o MODEL " + usb_disk, shell=True).decode()
if not model:
model = "Unknown"
else:
try:
selected_usb_part = str(usb_disk)
oFS = win32com.client.Dispatch("Scripting.FileSystemObject")
d = oFS.GetDrive(oFS.GetDriveName(oFS.GetAbsolutePathName(selected_usb_part)))
selected_usb_device = d.DriveLetter
label = (d.VolumeName).strip()
if not label.strip():
label = "No label."
total_size = d.TotalSize
usb_type = "Removable"
model = label
except:
log("Error detecting USB details.")
return _ntuple_diskusage(total_size, usb_type, model)
def get_usb_size(self, usb_disk):
"""
Function to detect USB disk space. Useful but not used in multibootusb as of now.
:param usb_disk: USB disk like "/dev/sdb"
:return: Size of the disk as integer
"""
if platform.system() == "Linux":
cat_output = subprocess.check_output("cat /proc/partitions | grep " + usb_disk[5:], shell=True)
usb_size = int(cat_output.split()[2]) * 1024
# log(usb_size)
return usb_size
else:
usb_size = self.usb.disk_usage(self.usb.get_usb(usb_disk).mount).total
return usb_size

@ -0,0 +1,192 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Name: install.py
# Purpose: This module contain functions to install ISO files to USB disk non destructively.
# 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
import os
import shutil
import sys
import platform
import threading
import subprocess
from .usb import *
from .gen import *
# from .iso import *
from . import iso
from scripts.update_cfg_file import *
from . import config
from . import persistence
def install_distro():
"""
Install selected ISO to USB disk.
:return:
"""
usb_mount = config.usb_mount
install_dir = os.path.join(usb_mount, "multibootusb", iso_basename(config.iso_link))
_iso_file_list = iso.iso_file_list(config.iso_link)
if not os.path.exists(os.path.join(usb_mount, "multibootusb")):
log("Copying multibootusb directory to " + usb_mount)
shutil.copytree(resource_path(os.path.join("data", "tools", "multibootusb")),
os.path.join(config.usb_mount, "multibootusb"))
if not os.path.exists(install_dir):
os.makedirs(install_dir)
with open(os.path.join(install_dir, "multibootusb.cfg"), "w") as f:
f.write(config.distro)
with open(os.path.join(install_dir, "iso_file_list.cfg"), 'w') as f:
for file_path in _iso_file_list:
f.write(file_path + "\n")
log("Installing " + iso_name(config.iso_link) + " on " + install_dir)
if config.distro == "opensuse":
iso.iso_extract_file(config.iso_link, install_dir, 'boot')
status_text = "Copying ISO..."
if platform.system() == "Windows":
subprocess.call(["xcopy", config.iso_link, usb_mount], shell=True) # Have to use xcopy as python file copy is dead slow.
elif platform.system() == "Linux":
log("Copying " + config.iso_link + " to " + usb_mount)
shutil.copy(config.iso_link, usb_mount)
elif config.distro == "Windows" or config.distro == "alpine" or config.distro == 'pc-unlocker':
log("Extracting iso to " + usb_mount)
iso_extract_full(config.iso_link, usb_mount)
elif config.distro == "trinity-rescue":
iso.iso_extract_file(config.iso_link, usb_mount, '*trk3')
elif config.distro == "ipfire":
iso.iso_extract_file(config.iso_link, usb_mount, '*.tlz')
iso.iso_extract_file(config.iso_link, usb_mount, 'distro.img')
iso.iso_extract_file(config.iso_link, install_dir, 'boot')
elif config.distro == "zenwalk":
config.status_text = "Copying ISO..."
iso.iso_extract_file(config.iso_link, install_dir, "kernel")
copy_iso(config.iso_link, install_dir)
elif config.distro == "salix-live":
# iso.iso_extract_file(config.iso_link, install_dir, "boot")
iso.iso_extract_file(config.iso_link, install_dir, '*syslinux')
iso.iso_extract_file(config.iso_link, install_dir, '*menus')
iso.iso_extract_file(config.iso_link, install_dir, '*vmlinuz')
iso.iso_extract_file(config.iso_link, install_dir, '*initrd*')
iso.iso_extract_file(config.iso_link, usb_mount, '*modules')
iso.iso_extract_file(config.iso_link, usb_mount, '*packages')
iso.iso_extract_file(config.iso_link, usb_mount, '*optional')
iso.iso_extract_file(config.iso_link, usb_mount, '*liveboot')
#iso.iso_extract_full(config.iso_link, usb_mount)
# config.status_text = "Copying ISO..."
# copy_iso(config.iso_link, install_dir)
elif config.distro == 'sgrubd2':
copy_iso(config.iso_link, install_dir)
elif config.distro == 'alt-linux':
iso.iso_extract_file(config.iso_link, install_dir, '-xr!*rescue')
iso.iso_extract_file(config.iso_link, config.usb_mount, 'rescue')
elif config.distro == "generic":
#with open(os.path.join(install_dir, "generic.cfg"), "w") as f:
# f.write(os.path.join(isolinux_bin_dir(config.iso_link), "generic") + ".bs")
iso_extract_full(config.iso_link, usb_mount)
elif config.distro == 'grub4dos':
iso_extract_full(config.iso_link, usb_mount)
elif config.distro == 'ReactOS':
iso_extract_full(config.iso_link, usb_mount)
elif config.distro == 'grub4dos_iso':
copy_iso(config.iso_link, install_dir)
else:
iso.iso_extract_full(config.iso_link, install_dir)
if platform.system() == 'Linux':
log('ISO extracted successfully. Sync is in progress...')
os.system('sync')
if config.persistence != 0:
log('Creating Persistence...')
config.status_text = 'Creating Persistence...'
persistence.create_persistence()
install_patch()
def copy_iso(src, dst):
"""
A simple wrapper for copying larger files. This is necessary as
shutil copy files is much slower under Windows platform
:param src: Path to source file
:param dst: Destination directory
:return:
"""
if platform.system() == "Windows":
subprocess.call("xcopy " + src + " " + dst, shell=True)
elif platform.system() == "Linux":
shutil.copy(src, dst)
def install_progress():
"""
Function to calculate progress percentage of install.
:return:
"""
from . import progressbar
usb_details = details(config.usb_disk)
usb_mount = usb_details['mount_point']
usb_size_used = usb_details['size_used']
thrd = threading.Thread(target=install_distro, name="install_progress")
# thrd.daemon()
# install_size = usb_size_used / 1024
install_size = iso_size(config.iso_link) / 1024
final_size = (usb_size_used + iso_size(config.iso_link)) + config.persistence
thrd.start()
pbar = progressbar.ProgressBar(maxval=100).start() # bar = progressbar.ProgressBar(redirect_stdout=True)
while thrd.is_alive():
current_size = details(config.usb_disk)['size_used']
percentage = int((current_size / final_size) * 100)
if percentage > 100:
percentage = 100
config.percentage = percentage
pbar.update(percentage)
def install_patch():
"""
Function to certain distros which uses makeboot.sh script for making bootable usb disk.
This is required to make sure that same version (32/64 bit) of modules present is the isolinux directory
:return:
"""
if config.distro == 'debian':
if platform.system() == 'Linux': # Need to syn under Linux. Otherwise, USB disk becomes random read only.
os.system('sync')
iso_cfg_ext_dir = os.path.join(multibootusb_host_dir(), "iso_cfg_ext_dir")
isolinux_path = os.path.join(iso_cfg_ext_dir, isolinux_bin_path(config.iso_link))
iso_linux_bin_dir = isolinux_bin_dir(config.iso_link)
config.syslinux_version = isolinux_version(isolinux_path)
iso_file_list = iso.iso_file_list(config.iso_link)
os.path.join(config.usb_mount, "multibootusb", iso_basename(config.iso_link), isolinux_bin_dir(config.iso_link))
if any("makeboot.sh" in s.lower() for s in iso_file_list):
for module in os.listdir(os.path.join(config.usb_mount, "multibootusb", iso_basename(config.iso_link),
isolinux_bin_dir(config.iso_link))):
if module.endswith(".c32"):
if os.path.exists(os.path.join(config.usb_mount, "multibootusb", iso_basename(config.iso_link),
isolinux_bin_dir(config.iso_link), module)):
try:
os.remove(os.path.join(config.usb_mount, "multibootusb",
iso_basename(config.iso_link), isolinux_bin_dir(config.iso_link), module))
log("Copying " + module)
log((resource_path(
os.path.join(multibootusb_host_dir(), "syslinux", "modules", config.syslinux_version, module)),
os.path.join(config.usb_mount, "multibootusb", iso_basename(config.iso_link),
isolinux_bin_dir(config.iso_link), module)))
shutil.copy(resource_path(
os.path.join(multibootusb_host_dir(), "syslinux", "modules", config.syslinux_version, module)),
os.path.join(config.usb_mount, "multibootusb", iso_basename(config.iso_link),
isolinux_bin_dir(config.iso_link), module))
except Exception as err:
log(err)
log("Could not copy " + module)
else:
log('Patch not required...')
if __name__ == '__main__':
config.iso_link = '../../../DISTROS/2016/slitaz-4.0.iso'
install_distro()

@ -0,0 +1,214 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*
# Name: iso.py
# Purpose: Module to manupulate ISO file
# Authors: Sundar
# Depends: isodump3.py (Authored by Johni Lee for MultiBootUSB)
# 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
import sys
import os
import string
import platform
import re
from .gen import *
from .isodump3 import ISO9660
from . import _7zip
_iso_cfg_ext_dir = iso_cfg_ext_dir()
def iso_name(iso_link):
"""
Find the name of an ISO.
:return: Name of an ISO (with extension) as string. Returns If not returns None.
"""
if os.path.exists(iso_link):
try:
name = os.path.basename(str(iso_link))
except:
name = None
else:
name = None
return name
def iso_basename(iso_link):
"""
Find the base name of an ISO.
:return: Base name (without extension) of a selected ISO as string. If not returns None.
"""
try:
dir_name = str(os.path.splitext(os.path.basename(str(iso_link)))[0])
except:
dir_name = None
return dir_name
def isolinux_bin_exist(iso_link):
"""
Check if an "isolinux.bin" file exist.
:return: True if "isolinux.bin" file exist of False if not.
"""
if os.path.exists(iso_link):
iso_file_list = _7zip.list_iso(iso_link)
if any("isolinux.bin" in s.lower() for s in iso_file_list):
return True
else:
return False
def iso_size(iso_link):
return os.path.getsize(iso_link)
def is_bootable(iso_link):
"""
Check if an ISO has the ability to boot.
:return: True if ISO is bootable and False if not.
"""
iso9660fs = ISO9660(iso_link)
isBootable = iso9660fs.checkISOBootable()
if isBootable:
return True
else:
return False
def isolinux_bin_dir(iso_link):
"""
Detects "isolinux.bin" directory.
:return: path of "isolinux.bin" directory as string.
"""
if os.path.exists(iso_link):
bin_dir = False
iso_file_list = _7zip.list_iso(iso_link)
if any("isolinux.bin" in s.lower() for s in iso_file_list):
for f in iso_file_list:
if 'isolinux.bin' in f.lower():
if 'efi' not in f.lower(): # Certain distros place their isolinux.bin in to /EFI/BOOT director and we don't want to include them
bin_dir = os.path.dirname(f)
break
return bin_dir
def isolinux_bin_path(iso_link):
"""
Detects pat to "isolinux.bin".
:return: path of "isolinux.bin" as a string.
"""
iso_bin_path = False
if isolinux_bin_exist(iso_link) is not False:
iso_file_list = _7zip.list_iso(iso_link)
for f in iso_file_list:
if 'isolinux.bin' in f.lower():
iso_bin_path = f
break
return iso_bin_path
def iso_menu_lst_path(iso_link):
"""
Detects pat to "menu.lst" of grub4dos.
:return: path of "menu.lst" as a string.
"""
menu_lst_path = False
iso_file_list = _7zip.list_iso(iso_link)
for f in iso_file_list:
if 'menu.lst' in f.lower():
menu_lst_path = f
break
return menu_lst_path
def integrity(iso_link):
"""
Check the integrity of an ISO.
:return: True if integrity passes or False if it fails.
"""
return _7zip.test_iso(iso_link)
def iso_file_list(iso_link):
"""
Function to return the content of an ISO.
:return: List of files of an ISO as list.
"""
return _7zip.list_iso(iso_link)
def isolinux_version(isolinux_bin_path):
"""
Detect isolinux version shipped by distros.
:param isolinux_path: Path to "isolinux.bin"
:return: Version number as string.
"""
version = ["3", "4", "5", "6"]
if isolinux_bin_path is not None:
sl = list(strings(isolinux_bin_path))
for strin in sl:
if re.search(r'isolinux ', strin, re.I):
for number in version:
if re.search(r'isolinux ' + number, strin, re.I):
log("\n\nFound syslinux version " + number + "\n\n")
return str(number)
def iso_extract_file(iso_link, dest_dir, filter):
"""
Extract the specific file(s) from an ISO
:param dest_dir: Path to destination directory.
:param filter: Filter to extract particular file(s)
:return: Extract file(s) to destination.
"""
_7zip.extract_iso(iso_link, dest_dir, filter)
def extract_cfg_file(iso_link):
"""
Function to extract certain files for auto detecting supported distros
:param iso_link: Path to ISO file
:return:
"""
_pattern = ['.cfg', '.CFG', '.txt', '.TXT', 'isolinux.bin', 'ISOLINUX.BIN', '.lst']
# file_list = iso_file_list(iso_link)
for ext in _pattern:
_7zip.extract_iso(iso_link, _iso_cfg_ext_dir, pattern='*' + ext)
def iso_extract_full(iso_link, dest_dir):
"""
Extract an ISO to destination directory
:param dest_dir: Destination path as string.
:return: False if it fails or extract ISO files to destination directory.
"""
_7zip.extract_iso(iso_link, dest_dir)
if __name__ == '__main__':
#iso_path = '../../../DISTROS/2016/debian-live-8.3.0-amd64-lxde-desktop.iso'
iso_path = '../../../DISTROS/2015/super_grub2_disk_hybrid_2.02s3.iso'
test_iso_bin_path = os.path.join('test', 'isolinux', 'isolinux.bin')
log('iso_name(iso_path) : ', iso_name(iso_path))
log('iso_basename(iso_path) : ', iso_basename(iso_path))
log('Integrity of ISO is : ', integrity(iso_path))
f_list = (iso_file_list(iso_path))
if f_list:
for f in f_list:
log(f)
log('isolinux_bin_exist(iso_path) : ', isolinux_bin_exist(iso_path))
#log('is_bootable : ', is_bootable(iso_path))
log('isolinux_bin_dir() : ', isolinux_bin_dir(iso_path))
log('isolinux_bin_path(iso_path) : ', isolinux_bin_path(iso_path))
iso_extract_full(iso_path, 'test')
iso_extract_file(iso_path, 'test', 'isolinux.bin')
log(isolinux_version(test_iso_bin_path))

@ -0,0 +1,806 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*
# Name: isodump3.py
# Purpose: Module to list and extract iso files.
# Authors: LiQiong Lee (written exclusively for multibootusb)
# Licence: This file is a part of multibootusb package. You can redistribute it or modify
# under the terms of GNU General Public License version 3
# Credit : I am grateful to LiQiong Lee. He not only wrote this module for multibootusb, but also extended the same
# to python3 within short time after request.
""" ISO9660fs
Dump raw meta data of iso9660 file system.
Extract directories and files.
"""
##
## Extract directory or file from iso.
## Support RRIP.
##
# Author : joni <joni.kartore.lee@gmail.com>
# version : 1.0
import sys
import struct
import os
import re
import stat
from ctypes import *
from . import config
from . import gen
BLOCK_SIZE = 2048
S_IFSOCKET = 0o140000
S_IFLINK = 0o120000
S_IFREG = 0o100000
S_IFBLK = 0o060000
S_IFCHR = 0o020000
S_IFDIR = 0o040000
S_IFIFO = 0o010000
E_SUCCESS = 0
E_FAILURE = -1
E_DEVICEFILE = -2 # can't write device file
class PrimaryVolume(Structure):
def __init__(self):
self.sysIdentifier = ""
self.volIdentifier = ""
self.volSize = 0
self.volSeq = 0
self.blockSize = 0
self.ptSize = 0
self.ptLRd = 0
self.fsVer = 0
self.rootLoc = 0
self.rootTotal = 0
class Rrip(Structure):
def __init__(self):
self.offset = -1
self.altname = ""
self.devH = 0
self.devL = 0
self.fMode = 0
class DirRecord(Structure):
def __init__(self):
self.lenDr = 0
self.lenEattr = 0
self.locExtent= 0
self.lenData = 0
self.dtYear = 0
self.dtMonth = 0
self.dtHour = 0
self.dtMinute = 0
self.dtSecond = 0
self.dtOffset = 0
self.fFlag = 0
self.fUnitSize= 0
self.gapSize = 0
self.volSeqNr = 0
self.lenFi = 0
self.fIdentifier = ""
self.sysUseStar = 0
self.suspBuf = ""
self.rrip = None
class PathTabelItem(Structure):
def __init__(self):
self.lenDi = 0
self.lenEattr = 0
self.locExtenti = 0
self.pdirNr = 0
self.fIdentifier = ""
class ISO9660:
"""
This class can dump iso9660 file system meta data and extract files.
Support:
RRIP extension.
"""
def __init__(self, isofile):
try:
f = open(isofile, 'rb')
except(IOError):
sys.stderr.write("can't open {0}".format(isofile))
sys.exit(-1)
self.isoFile = f
self.priVol = None
self.rootDir = None
self.rripOffset = -1
desc_nr = 0
while True:
desc_nr = desc_nr + 1
try:
self.isoFile.seek(BLOCK_SIZE*(15+desc_nr))
volume_dsc = self.isoFile.read(BLOCK_SIZE)
flag = struct.unpack('B',volume_dsc[0:1])[0]
if flag == 1:
self.__readPrimaryVolume__(volume_dsc)
continue
if flag == 255:
break
except Exception as e:
gen.log("Got exception when init iso file:", sys.exc_info()[0])
self.priVol = None
self.rootDir = None
break
def __del__(self):
self.isoFile.close()
def __readPrimaryVolume__(self, volume_dsc):
""" Dump primary volume descriptor """
global BLOCK_SIZE
priVol = PrimaryVolume()
priVol.sysIdentifier = volume_dsc[8:40]
priVol.volIdentifier = volume_dsc[40:72]
priVol.volSize = struct.unpack('<L',volume_dsc[80:84])[0]
priVol.volSeq = struct.unpack('<H',volume_dsc[124:126])[0]
priVol.blockSize = struct.unpack('<H',volume_dsc[128:130])[0]
priVol.ptSize = struct.unpack('<L',volume_dsc[132:136])[0]
priVol.ptLRd = struct.unpack('<L',volume_dsc[140:144])[0]
priVol.fsVer = struct.unpack('B', volume_dsc[881:882])[0]
dirRec = self.readDirrecord(volume_dsc[156:190])
priVol.rootLoc = dirRec.locExtent
priVol.rootTotal = dirRec.lenData
BLOCK_SIZE = priVol.blockSize
# Check RRIP
#gen.log ("loc extent(%d)"%(dirRec.locExtent))
self.priVol = priVol # readDirItems will use self.priVol
root_dir = self.readDirItems(dirRec.locExtent, priVol.rootTotal)[0]
rripNode = self.__rripLoop__(root_dir.suspBuf, root_dir.lenDr-root_dir.sysUseStar)
if rripNode.offset != -1:
self.rripOffset = rripNode.offset
#gen.log ("RRIP: rrip_offset %d"%(self.rripOffset))
else:
gen.log (" This disc don't support RRIP")
self.rootDir = root_dir
# Rrip extension
def __rripLoop__(self, desc_buf, len_buf):
if self.rripOffset > 0:
entry_buf = desc_buf[self.rripOffset:]
gen.log ("__rripLoop__ offset:%d"%(self.rripOffset))
else:
entry_buf = desc_buf
rr = Rrip()
while True:
ce_blk = 0
ce_len = 0
ce_off = 0
head = 0
len_entry = 0
while True:
#gen.log (("\n%d, %d\n")%(len_buf, head))
head += len_entry
if len_buf - head < 4: # less than one entry
break
entry_buf = entry_buf[len_entry:]
sig1 = struct.unpack("B", entry_buf[0:1])[0]
sig2 = struct.unpack("B", entry_buf[1:2])[0]
len_entry = struct.unpack("B", entry_buf[2:3])[0]
ver = struct.unpack("B", entry_buf[3:4])[0]
#if len_entry == 0:
# gen.log "Got a entry in __rripLoop__ (%c,%c) of SUSP with length:(%d),version:(%d)-->"%(sig1,sig2,len_entry, ver),
if len_entry == 0:
break;
if sig1 == ord('S') and sig2 == ord('P'):
ck1 = struct.unpack("B", entry_buf[4:5])[0]
ck2 = struct.unpack("B", entry_buf[5:6])[0]
skip = struct.unpack("B", entry_buf[6:7])[0]
#gen.log "-->(0x%x==0xBE,0x%x==EF,%d)" %(ck1, ck2, skip)
if ck1 == 0xBE and ck2 == 0xEF:
rr.offset = skip
continue
if sig1 == ord('C') and sig2 == ord('E'):
ce_blk = struct.unpack("<L", entry_buf[4:8])[0]
ce_off = struct.unpack("<L", entry_buf[12:16])[0]
ce_len = struct.unpack("<L", entry_buf[20:24])[0]
#gen.log "-->(%d,%d,%d)" %(ce_blk, ce_off, ce_len)
continue
if sig1 == ord('N') and sig2 == ord('M'):
flag = struct.unpack("B", entry_buf[4:5])[0]
#gen.log "-->(flag:(0x%x), name:(%s))" %(flag, entry_buf[5:len_entry])
if flag == 0x02: # FLAG_CURRENT
rr.altname += "."
elif flag == 0x04: # FLAG_PARENT
rr.altname += ".."
elif flag == 0x01 or flag ==0: # 1:FLAG_CONTINUE
rr.altname += entry_buf[5:len_entry].decode()
continue
if sig1 == ord('P') and sig2 == ord('N'):
rr.devH = struct.unpack("<L", entry_buf[4:8])[0]
rr.devL = struct.unpack("<L", entry_buf[12:16])[0]
continue
if sig1 == ord('E') and sig2 == ord('R'):
len_id = struct.unpack("B", entry_buf[4:5])[0]
len_des = struct.unpack("B", entry_buf[5:6])[0]
len_src = struct.unpack("B", entry_buf[6:7])[0]
ext_ver = struct.unpack("B", entry_buf[7:8])[0]
continue
if sig1 == ord('P') and sig2 == ord('X'):
rr.fMode = struct.unpack("<L", entry_buf[4:8])[0]
s_link = struct.unpack("<L", entry_buf[12:16])[0]
uid = struct.unpack("<L", entry_buf[20:24])[0]
gid = struct.unpack("<L", entry_buf[28:32])[0]
continue
if sig1 == ord('S') and sig2 == ord('T'):
return rr
#gen.log "\n"
# while (True) end #
if ce_len > 0:
#gen.log " Read CE block, (%d, %d, %d)"%(ce_blk, ce_len, ce_off)
self.isoFile.seek(ce_blk*BLOCK_SIZE + ce_off)
entry_buf = self.isoFile.read(ce_len)
len_buf = ce_len
else:
break
# while (True) end #
return rr
def checkISOBootable(self):
""" Struct of a classical generic MBR.
0x0000 Bootstrap Code area
-----------------------------------------
0x01BE
.. Partition table
0x01EE
------------------------------------------
0x01FE 55h
Boot signature
0x01FF AAh
"""
self.isoFile.seek(0x01FE)
h = self.isoFile.read(2)
s1 = struct.unpack('B', h[0:1])[0]
s2 = struct.unpack('B', h[1:2])[0]
#gen.log "-->(0x%x,0x%x)" %(s1, s2)
if (s1 == 0x55) and (s2 == 0xAA):
result = True # "Bootable"
else:
result = False # "Not bootable"
return result
def searchDir(self, path):
# /root/abc/ - ['', 'root', 'abc', '']
# /root/abc - ['', 'root', 'abc']
# / - ['', '']
dircomps = path.split('/')
if dircomps[-1] == '':
dircomps.pop()
if dircomps == []:
return
if self.priVol == None:
return
if len(dircomps) == 1:
return self.rootDir
pdir_loc = self.priVol.rootLoc
pdir_len = self.priVol.rootTotal
i_dircomp = 1
while True:
found = False
dirs = self.readDirItems(pdir_loc, pdir_len)
for item in dirs:
if item.fIdentifier == dircomps[i_dircomp]:
pdir_loc = item.locExtent
pdir_len = item.lenData
found = True
#gen.log "found (%s)"%(dircomps[i_dircomp])
break
if found: # advacne
if i_dircomp < len(dircomps)-1:
i_dircomp = i_dircomp + 1
else:
return item
else:
gen.log ("can't find " + dircomps[i_dircomp])
return None
def readDirrecord(self, desc_buf):
""" Dump file dirctory record
Return a directory record reading from a Directory Descriptors.
"""
dirRec = DirRecord()
try:
dirRec.lenDr = struct.unpack("B", desc_buf[0:1])[0]
if dirRec.lenDr == 0:
return None
except:
return None
dirRec.lenEattr = struct.unpack("B", desc_buf[1:2])[0]
dirRec.locExtent = struct.unpack("<L", desc_buf[2:6])[0]
dirRec.lenData = struct.unpack("<L", desc_buf[10:14])[0]
dirRec.fFlag = struct.unpack("B", desc_buf[25:26])[0]
dirRec.fUnitSize = struct.unpack("B", desc_buf[26:27])[0]
dirRec.gapSize = struct.unpack("B", desc_buf[27:28])[0]
dirRec.volSeqNr = struct.unpack("<H", desc_buf[28:30])[0]
dirRec.lenFi = struct.unpack("B", desc_buf[32:33])[0]
dirRec.fIdentifier = ""
if dirRec.lenFi == 1:
dirRec.fIdentifier = struct.unpack("B", desc_buf[33:34])[0]
if dirRec.fIdentifier == 0:
dirRec.fIdentifier = "."
elif dirRec.fIdentifier == 1:
dirRec.fIdentifier = ".."
else:
dirRec.fIdentifier = desc_buf[33:33+dirRec.lenFi].decode()
idx = dirRec.fIdentifier.rfind(";")
if idx != -1:
dirRec.fIdentifier = dirRec.fIdentifier[0:idx]
dirRec.suspBuf = ""
dirRec.sysUseStar = 34 + dirRec.lenFi -1
if dirRec.lenFi % 2 == 0:
dirRec.sysUseStar += 1
# Extension Attribute
if dirRec.lenDr > dirRec.sysUseStar+4:
if dirRec.locExtent == self.priVol.rootLoc:
dirRec.suspBuf = desc_buf[dirRec.sysUseStar:dirRec.lenDr]
suspBuf = desc_buf[dirRec.sysUseStar:dirRec.lenDr]
if self.rripOffset != -1:
rripNode = self.__rripLoop__(suspBuf, dirRec.lenDr-dirRec.sysUseStar)
dirRec.rrip = rripNode
if rripNode != None:
if rripNode.altname != "":
dirRec.fIdentifier = rripNode.altname
dirRec.lenFi = len(rripNode.altname)
#gen.log "rrip_altname: %s"%(dirRec.fIdentifier)
# if rripNode end #
# if self.rripOffset != -1 end #
# if dirRec.lenDr > .. end #
return dirRec
def readDirItems(self, block_nr=None, total=None):
""" Read file dirctory records
Read dirctory records from 'block_nr' with a length of 'total'.
Return a list containing directory records(DirRecord).
"""
dirs = []
total_blk = (total+BLOCK_SIZE-1)//BLOCK_SIZE
i_blk = 0
while i_blk < total_blk:
self.isoFile.seek((block_nr+i_blk)*BLOCK_SIZE)
desc_buf = self.isoFile.read(BLOCK_SIZE)
i_blk = i_blk + 1
while True:
dirItem = self.readDirrecord(desc_buf)
if dirItem == None:
break
dirs.append(dirItem)
if desc_buf.__len__() > dirItem.lenDr:
desc_buf = desc_buf[dirItem.lenDr:]
else:
break
return dirs
def readPathtableL(self):
""" Read path table of L typde """
if self.priVol == None:
return
block_nr = self.priVol.ptLRd
total = self.priVol.ptSize
path_table = []
self.isoFile.seek(block_nr*BLOCK_SIZE)
ptbuf = self.isoFile.read((BLOCK_SIZE * ((total+BLOCK_SIZE-1)//BLOCK_SIZE)))
i = 0
r_size = 0
while True :
i = i+1
t = PathTabelItem()
t.lenDi = struct.unpack('B', ptbuf[0:1])[0]
t.lenEattr = struct.unpack('B', ptbuf[1:2])[0]
t.locExtent = struct.unpack('<L', ptbuf[2:6])[0]
t.pdirNr = struct.unpack('<H', ptbuf[6:8])[0]
t.fIdentifier = ptbuf[8:8+t.lenDi].decode()
path_table.append(t)
if t.lenDi % 2 :
len_pd = 1
else:
len_pd = 0
r_size += 9+t.lenDi-1+len_pd
if r_size >= total:
break
ptbuf = ptbuf[9+t.lenDi-1+len_pd:]
# while True
return path_table
# @path -- path within iso file system.
# @output -- what local path you want write to.
# @pattern -- regular expression.
# @r -- recursion flag, write the whole sub-directories or not.
# @all_type -- which file type should be writed.
# False: Write regular type files only.
# True: Wirte all types files (regular, device file, link, socket, etc)
def writeDir(self, path, output, pattern="", r=True, all_type=False):
""" Extract a directory
Return 0 means success otherwise failure.
"""
d = self.searchDir(path)
if d != None:
if output.endswith("/"):
output = output[0:-1]
# Try to make target directory.
if not os.path.exists(output):
try:
os.makedirs(output)
except(OSError):
sys.stderr.write("can't make dirs({0})\n".format(output))
return E_FAILURE
pp = None
if pattern != "":
p = r'{0}'.format(pattern)
pp = re.compile(p)
#gen.log "writeDir: flag(%x)"%(d.fFlag)
if d.fFlag & 0x02 == 0x02:
# Check if a clean directory.
#try:
# if len(os.listdir(output)) > 0:
# sys.stderr.write("The target directory is not empty\n")
# return E_FAILURE
#except(OSError):
# sys.stderr.write("can't access dirs({0})\n".format(p))
# return E_FAILURE
self.writeDir_r(output, d, pp, r, all_type)
return E_SUCCESS
else:
return self.writeFile(d, output+path, all_type)
else:
return E_FAILURE
def writeDir_r(self, det_dir, dire, pp, r, all_type):
#gen.log "writeDir_r:(%s)"%(det_dir)
dirs = self.readDirItems(dire.locExtent, dire.lenData)
for d in dirs:
if not d.fIdentifier in [".", ".."]:
if (pp != None) and (pp.search(d.fIdentifier) == None):
match = False
else:
match = True
#gen.log "mathing %s, %s, (%x)"%(match, d.fIdentifier, d.fFlag)
p = det_dir + "/" + d.fIdentifier
if d.fFlag & 0x02 == 0x02:
if not os.path.exists(p):
os.makedirs(p, 0o777)
if r:
if match:
self.writeDir_r(p, d, None, r, all_type) # Don't need to match subdirectory.
else:
self.writeDir_r(p, d, pp, r, all_type)
elif match:
self.writeFile(d, p, all_type)
# if not d.fIdentifier end #
# for d in dirs end #
def writeFile(self, dirRec, detFile, all_type):
""" Write a file to detFile
Return 0 means success otherwise failure.
"""
global file_out
if detFile == "" or dirRec == None:
sys.stderr.write("can't write file\n")
return E_FAILURE
#gen.log "write file (%s)"%(detFile)
config.status_text = detFile
dirname = os.path.dirname(detFile)
if not os.path.exists(dirname):
try:
os.makedirs(dirname, 0o777)
except(OSError):
sys.stderr.write("can't makedirs\n")
return E_FAILURE
if all_type == True:
# device file
if dirRec.rrip != None and (dirRec.rrip.devH != 0 or dirRec.rrip.devL != 0):
#fFlag == 0
high = dirRec.rrip.devH
low = dirRec.rrip.devL
if high == 0:
device = os.makedev(os.major(low), os.minor(low))
else:
device = os.makedev(high, os.minor(low))
try:
mode = dirRec.rrip.fMode & 0o770000
if mode == S_IFCHR:
os.mknod(detFile, 0o777|stat.S_IFCHR, device)
elif mode == S_IFBLK:
os.mknod(detFile, 0o777|stat.S_IFBLK, device)
except(OSError):
sys.stderr.write("can't mknode, maybe no permission\n")
return E_DEVICEFILE
return E_SUCCESS
loc = dirRec.locExtent
length = dirRec.lenData
self.isoFile.seek(BLOCK_SIZE * loc)
#gen.log "file length(%d)"%(length)
r_size = BLOCK_SIZE*1024*50 #100M cache
try:
f_output = open(detFile, 'wb', r_size)
except(IOError):
sys.stderr.write("can't open{0} for write\n".format(detFile))
return E_FAILURE
while True:
if length == 0:
break
elif length <= r_size:
r_size = length
length = 0
else:
length = length - r_size
buf = self.isoFile.read(r_size)
f_output.write(buf)
f_output.flush()
# while True end.
f_output.close()
return E_SUCCESS
def readDir(self, dir_path, r=True):
file_list = []
d = self.searchDir(dir_path)
if d != None:
if (d.fFlag & 0x02) == 0x02:
#gen.log "readDir (%x, %x)"%(d.locExtent, d.lenData)
if dir_path.endswith("/"):
dir_path = dir_path[0:-1]
self.readDir_r(file_list, dir_path, d, r)
# if (d.fFlag & 0x02) == 0x02: #
# if d != None:
return file_list
def readDir_r(self, file_list, dir_path, dire, r):
if (dire.fFlag & 0x02) != 0x02:
return
dirs = self.readDirItems(dire.locExtent, dire.lenData)
for d in dirs:
if not d.fIdentifier in [".", ".."]:
p = dir_path + "/" + d.fIdentifier
file_list.append(p)
if r:
self.readDir_r(file_list, p, d, r)
# if not d.fIdentifier #
# for d in dirs: #
def checkIntegrity(self):
if self.priVol == None: # no primary volume
return False
if self.priVol.ptSize == 0: # empty ?
return True
path_table = self.readPathtableL()
if path_table == []: # pathtable record is broken.
return False
# find last file item to check
for dr in reversed(path_table):
#gen.log dr.fIdentifier
dirs = self.readDirItems(dr.locExtent, BLOCK_SIZE)
if len(dirs) > 2:
dot = dirs[0]
dirs2 = self.readDirItems(dot.locExtent, dot.lenData) # get the whole items.
for dr2 in reversed(dirs2): # search last file item.
if dr2.fFlag == 0:
#gen.log "get last file(%s)"%(dr2.fIdentifier)
try:
#self.isoFile.seek(BLOCK_SIZE * dr2.locExtent+dr2.lenData)
lastfile_end = BLOCK_SIZE * dr2.locExtent + dr2.lenData
self.isoFile.seek(0, os.SEEK_END)
iso_end = self.isoFile.tell()
#gen.log ("%d-->%d")%(lastfile_end, iso_end)
if iso_end >= lastfile_end:
return True
else:
return False
except(IOError):
#gen.log "exception when seek. iso is broken"
return False
elif len(dirs) < 2: # Dir record is broken. At least, should have two entries.
return False
return True
###########################################################################
def dump_dir_record(dirs):
""" Dump all the file dirctory records contained in desc_buf """
gen.log("Dump file/deirectory record")
gen.log("===========================", end="\n")
if dirs != None:
for f in dirs:
gen.log("length of directory record:(0x%x), length of extend attribute:(%d), \
location of record:(%d)BLOCK->(0x%x), data length(%d) size of file unit:(%d), \
interleave gap size:(%d), file flag:(0x%x),name length:(%d) identify:(%s)\n" \
%(f.lenDr, f.lenEattr, f.locExtent, f.locExtent*BLOCK_SIZE,f.lenData, \
f.fUnitSize, f.gapSize, f.fFlag, f.lenFi, f.fIdentifier))
def dump_pathtable_L(path_table):
""" Dump path table of L typde """
gen.log("Dump path table")
gen.log("================", end="\n")
#path_table = readPathtableL()
i = 0
for t in path_table:
i = i + 1
if t.lenDi == 1:
if t.fIdentifier in [0, 1]:
gen.log("is a root directory(%d)" %(is_root))
gen.log("%d->length of identify:(%d), length of extend attribute:(%d), \
local:(%d)->(0x%x), parent dir number:(%d), identify:(%s)\n" \
%(i, t.lenDi, t.lenEattr, t.locExtent, t.locExtent*BLOCK_SIZE, t.pdirNr, t.fIdentifier))
def dump_primary_volume(privol=None):
""" Dump primary volume descriptor """
if privol == None:
gen.log("Can't dump, maybe iso is broken")
return
gen.log("===== Dump primary volume descriptor ==")
gen.log("System Identifier:(%s)" %(privol.sysIdentifier.decode()))
gen.log("Volume Identifier:(%s)" %privol.volIdentifier.decode())
gen.log("Volume Space size:(0x%x)BLOCKS(2kB)" %privol.volSize)
gen.log("Volume sequence number:(%d)" %(privol.volSeq))
gen.log("logic block size:(0x%x)" %(privol.blockSize))
gen.log("Volume path talbe L's BLOCK number is :(0x%x-->0x%x), size(%d)" %(privol.ptLRd, privol.ptLRd*BLOCK_SIZE, privol.ptSize))
# gen.log "Abstract File Identifier: (%s)" %(volume_dsc[739:776])
# gen.log "Bibliographic File Identifier: (%s)" %(volume_dsc[776:813])
gen.log("pathtable locate (%d)" %(privol.ptLRd))
gen.log("File Structure Version:(%d)" %(privol.fsVer))
gen.log("Root directory is at (%d)block, have(0x%x)bytes" %(privol.rootLoc, privol.rootTotal))
# dump_dir_record(None, 23, 1)
def dump_boot_record(volume_dsc):
""" Dump boot record """
gen.log("===== Dump boot record ==")
std_identifier = volume_dsc[1:6]
gen.log("Standard Identifier:(%s)" %std_identifier)
vol_ver = struct.unpack('B', volume_dsc[6])
gen.log("Volume descriptor version:(%d)" %vol_ver)
bootsys_identifier = volume_dsc[7:39]
gen.log("boot system identifier(%s)" %bootsys_identifier)
boot_identifier = volume_dsc[39:71]
gen.log("boot identifier(%s)" %boot_identifier)
def usage():
""" Prompt user how to use """
gen.log("""
Usage: isodump dump-what [options] iso-file
[dump-what]
-----------
boot - Dump boot record.
primary-volume - Dump primary volume.
pathtable - Dump path table.
dir-record [block number] [length] - Dump a raw data of a Directory Record
iso:/dir [-r] [-o output] [-p pattern] - Dump a dirctory or file to [output]
-r recursively visit directory.
-p spcify a Regular expression pattern for re.search(pattern,).
isodump xx.iso - Dump the root directory
isodump pathtable xx.iso - Dump the path table record.
isodump iso:/ -r xx.iso
-- Dump the root directory of xx.iso recursively.
isodump iso:/ -r -o /tmp/iso xx.iso
-- Extract the iso to /tmp/iso/.
isodump iso:/boot -o /tmp/iso/boot xx.iso
-- Extract the /boot directory of xx.iso to /tmp/iso/boot.
isodump iso:/boot/grup.cfg -o /tmp/grub.cfg xx.iso
-- Extract the file "grup.cfg" to "/tmp/grub.cfg"
isodump iso:/boot -r -o /tmp/iso -p "*.cfg" xx.iso
-- Extract any files or directories under /boot maching "*.cfg" to /tmp/iso/.
""")
sys.exit(-1)
if __name__ == '__main__':
argv = sys.argv
if len(argv) < 3:
usage()
iso9660fs = ISO9660(argv[-1])
integrity = iso9660fs.checkIntegrity()
if integrity == False:
gen.log("iso file is broken")
sys.exit(-1)
dump_what = argv[1]
if dump_what == "primary-volume":
dump_primary_volume(iso9660fs.priVol)
elif dump_what == "pathtable":
path_table = iso9660fs.readPathtableL()
dump_pathtable_L(path_table)
if dump_what == "dir-record":
if len(argv) == 5:
gen.log("dump dir-record (%s, %s)"%(argv[2], argv[3]))
dirs = iso9660fs.readDirItems(int(argv[2]), int(argv[3]))
dump_dir_record(dirs)
else:
usage()
elif dump_what.startswith("iso:"):
o_path = ""
r = False
o = False
p = False
pattern = ""
for arg in argv[2:-1]:
if arg == "-r":
r = True
o = False
p = False
elif arg == "-o":
o = True
p = False
elif arg == "-p":
o = False
p = True
elif o == True:
o_path = arg
o = False
elif p == True:
pattern = arg
p = False
isodir = dump_what[4:]
if o_path == "":
gen.log("dump_dir(%s)"%(isodir))
filelist = iso9660fs.readDir(isodir, r)
if filelist == []:
gen.log("can't read any file from (%s)"%(isodir))
else:
for f in filelist:
gen.log(f)
else:
gen.log("writeDir(%s)->(%s) with pattern(%s)"%(isodir, o_path, pattern))
sys.exit(iso9660fs.writeDir(isodir, o_path, pattern, r, True))

@ -0,0 +1,85 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Name: mbusb_cli.py
# Purpose: Module to handle command line options of multibootusb
# 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
import os
import re
import shutil
from . import usb
from . import gen
from .iso import *
from .uninstall_distro import *
from .distro import *
from .syslinux import *
from .install import *
def read_input_uninstall():
response = False
try:
response = int(input("Please enter the number against the distro you need to uninstall: "))
except ValueError:
log('\nPlease provide valid integer from the above list.\n')
return response
def cli_install_distro():
'''
if platform.system() == 'Linux':
if os.getuid() != 0:
exit("You need to have root privileges to run this script.\nPlease try again using 'sudo'. Exiting.")
elif platform.system() == 'Windows':
if admin.isUserAdmin():
admin.elevate()
'''
log('Starting multibootusb from Command line...')
if usb.is_block(config.usb_disk) is False:
log(config.usb_disk, 'is not a valid device partition...')
exit(1)
elif integrity(config.iso_link) is not True:
log(config.iso_link, ' failed to pass integrity check...')
exit(1)
elif size_not_enough(config.iso_link, config.usb_disk) is True:
log(config.usb_disk, 'does not have enough space...')
else:
prepare_mbusb_host_dir()
extract_cfg_file(config.iso_link)
_distro = distro(iso_cfg_ext_dir(), config.iso_link)
log('Detected distro type is', _distro)
if _distro is not None:
log('\nSelected ISO is :', quote(iso_name(config.iso_link)))
log('Selected target device is:', quote(config.usb_disk), '\n')
log('Please confirm the option.')
log('Y/y/Yes/yes/YES or N/n/No/no/NO')
if read_input_yes() is True:
config.distro = _distro
copy_mbusb_dir_usb(config.usb_disk)
install_progress()
syslinux_distro_dir(config.usb_disk, config.iso_link, _distro)
syslinux_default(config.usb_disk)
update_distro_cfg_files(config.iso_link, config.usb_disk, _distro)
else:
log('Sorry', iso_name(config.iso_link), 'is not supported at the moment\n'
'Please report tissue at https://github.com/mbusb/multibootusb/issues')
def cli_uninstall_distro():
distro_list = install_distro_list()
if distro_list is not None:
for index, _distro_dir in enumerate(distro_list):
log(index, '--->>', _distro_dir)
user_input = read_input_uninstall()
if user_input is not False:
for index, _distro_dir in enumerate(distro_list):
if index == user_input:
config.uninstall_distro_dir_name = _distro_dir
unin_distro()
else:
log('No distro installed on', config.usb_disk)

@ -0,0 +1,640 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Name: mbusb_gui.py
# Purpose: Module to handle multibootusb through gui
# 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
import os
import platform
import sys
import signal
from PyQt5 import QtCore, QtGui, QtWidgets
import subprocess
import time
from scripts.gui.ui_multibootusb import Ui_Dialog
from . import usb
from .gen import *
from .install import *
from .uninstall_distro import *
from .syslinux import *
from .distro import *
from .iso import *
from .imager import Imager, dd_linux, dd_win
from . import persistence
from . import config
from . import admin
from . import qemu
from .update_cfg_file import update_distro_cfg_files
class AppGui(qemu.Qemu, Imager, QtWidgets.QDialog, Ui_Dialog):
"""
Main multibootusb GUI manipulation class.
"""
def __init__(self):
QtWidgets.QDialog.__init__(self)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
# Main Tab
self.ui.checkBox_all_drives.clicked.connect(self.add_device)
self.ui.detect_usb.clicked.connect(self.onRefereshClick)
self.ui.close.clicked.connect(self.on_close_Click)
self.ui.browse_iso.clicked.connect(self.browse_iso)
self.ui.comboBox.activated[str].connect(self.onComboChange)
# self.ui.create.clicked.connect(self.update_progress)
self.ui.create.clicked.connect(self.onCreateClick)
self.ui.slider_persistence.valueChanged.connect(self.update_slider_text)
self.ui.uninstall.clicked.connect(self.OnUninstallClick)
# ISO Imager Tab
self.ui.pushButton.clicked.connect(self.on_Imager_Browse_iso_Click)
self.ui.comboBox_2.activated[str].connect(self.onImagerComboChange)
self.ui.pushbtn_imager_refreshusb.clicked.connect(self.onRefereshClick)
self.ui.imager_close.clicked.connect(self.on_close_Click)
self.ui.imager_write.clicked.connect(self.dd_write)
# Syslinux Tab
self.ui.install_syslinux.clicked.connect(self.onInstall_syslinuxClick)
self.ui.edit_syslinux.clicked.connect(self.onedit_syslinux)
# QEMU Tab
self.ui.browse_iso_qemu.clicked.connect(self.on_Qemu_Browse_iso_Click)
self.ui.boot_iso_qemu.clicked.connect(self.on_Qemu_Boot_iso_Click)
self.ui.boot_usb_qemu.clicked.connect(lambda: self.on_Qemu_Boot_usb_Click(str(self.ui.comboBox.currentText())))
# self.ui.tabWidget.removeTab(3)
# Update progressbar and status (Main ISO install)
self.progress_thread_install = GuiInstallProgress()
self.progress_thread_install.finished.connect(self.install_syslinux)
self.progress_thread_install.update.connect(self.ui.progressBar.setValue)
self.progress_thread_install.status.connect(self.ui.status.setText)
# Update progressbar and status (Uninstall from previous install)
self.progress_thread_uninstall = GuiUninstallProgress()
self.progress_thread_uninstall.finished.connect(self.uninstall_sys_file_update)
self.progress_thread_uninstall.update.connect(self.ui.progressBar.setValue)
self.progress_thread_uninstall.status.connect(self.ui.status.setText)
# Update progressbar and status (dd ISO)
self.progress_thread_dd = DD_Progress()
self.progress_thread_dd.update.connect(self.ui.imager_progressbar.setValue)
self.progress_thread_dd.finished.connect(self.dd_finished)
self.progress_thread_dd.status.connect(self.ui.imager_label_status.setText)
self.add_device()
prepare_mbusb_host_dir()
def add_device(self):
"""
Adds list of available USB devices to GUI combobox.
:return:
"""
self.ui.comboBox.clear()
self.ui.comboBox_2.clear()
if self.ui.checkBox_all_drives.isChecked():
detected_device = usb.list(partition=1, fixed=True)
else:
detected_device = usb.list()
if bool(detected_device):
for device in detected_device:
self.ui.comboBox.addItem(str(device))
if self.ui.comboBox.currentText():
self.onComboChange()
imager_detected_device = self.imager_list_usb(partition=0)
if bool(imager_detected_device):
for disk in imager_detected_device:
self.ui.comboBox_2.addItem(str(disk))
self.onImagerComboChange()
def onComboChange(self):
"""
Detects and updates GUI with populated USB device details.
:return:
"""
self.ui.listWidget.clear()
config.usb_disk = str(self.ui.comboBox.currentText())
config.imager_usb_disk = str(self.ui.comboBox_2.currentText())
if bool(config.usb_disk):
self.update_gui_oncombobox(config.usb_disk)
else:
log("No USB disk found...")
def onRefereshClick(self):
"""
Calls function to detect USB devices.
:return:
"""
self.ui.comboBox.clear()
self.ui.comboBox_2.clear()
self.add_device()
def update_gui_oncombobox(self, usb_disk):
self.usb_details = usb.details(usb_disk)
config.usb_mount = self.usb_details['mount_point']
self.ui.usb_dev.setText("Drive :: " + usb_disk)
# self.label.setFont(QtGui.QFont("Times",weight=QtGui.QFont.Bold))
if platform.system() == 'Windows':
self.ui.usb_vendor.setText("FileSystem :: " + self.usb_details['file_system'])
self.ui.usb_model.setText("Label :: " + self.usb_details['label'])
else:
self.ui.usb_vendor.setText("Vendor :: " + self.usb_details['vendor'])
self.ui.usb_model.setText("Model :: " + self.usb_details['model'])
self.ui.usb_size.setText("Total Size :: " + str(usb.bytes2human(self.usb_details['size_total'])))
self.ui.usb_mount.setText("Mount :: " + self.usb_details['mount_point'])
self.update_list_box(usb_disk)
def update_list_box(self, usb_disk):
"""
Updates listbox with installed distros on selected USB disk.
:param usb_mount: Selected USB disk from combobox.
:return:
"""
distro_list = install_distro_list()
#sys_cfg_file = os.path.join(str(usb_mount), "multibootusb", "syslinux.cfg")
if distro_list is not None:
self.ui.listWidget.clear()
for name in distro_list:
self.ui.listWidget.addItem(name)
else:
if config.usb_mount == 'No_Mount':
log("UBS disk is not mounted and can't update list widget...")
#QtWidgets.QMessageBox.information(self, 'No Install...',
# 'syslinux.cfg does not exist for updating list widget.')
def browse_iso(self):
if str(self.ui.lineEdit.text()):
self.ui.lineEdit.clear()
config.iso_link = QtWidgets.QFileDialog.getOpenFileName(self, 'Select an iso...', '', 'ISO Files (*.iso)')[0]
if config.iso_link:
if platform.system() == "Windows":
if "/" in config.iso_link:
config.iso_link = config.iso_link.strip().replace("/", "\\")
self.ui.lineEdit.insert(str(config.iso_link))
if os.path.exists(config.iso_link):
clean_iso_cfg_ext_dir(os.path.join(multibootusb_host_dir(), "iso_cfg_ext_dir")) # Need to be cleaned.
extract_cfg_file(config.iso_link)
config.distro = distro(iso_cfg_ext_dir(), config.iso_link) # Detect supported distro
if config.distro:
per_availability = persistence.persistence_distro(config.distro, config.usb_disk, config.iso_link)[0]
per_max_size = persistence.persistence_distro(config.distro, config.usb_disk, config.iso_link)[1]
if per_availability is not None:
self.ui.slider_persistence.setEnabled(True)
self.ui.slider_persistence.setTickInterval(10)
self.ui.slider_persistence.setSingleStep(10)
ui_per_max_size = per_max_size / 1024 / 1024
# config.persistence = per_max_size
self.ui.slider_persistence.setMaximum(ui_per_max_size)
log('Persistence Max Size: ' + str(bytes2human(per_max_size)))
else:
log('Persistence is not available for ' + iso_name(config.iso_link))
else:
log("File not selected...")
def update_slider_text(self):
slide_value = self.ui.slider_persistence.value() * 1024 * 1024
self.ui.label_persistence_value.setText(bytes2human(slide_value))
config.persistence = slide_value
def install_syslinux(self):
"""
Function to install syslinux on distro directory and on selected USB disks.
:return:
"""
self.ui.status.setText(str("Installing Syslinux..."))
syslinux_distro_dir(config.usb_disk, config.iso_link, config.distro)
syslinux_default(config.usb_disk)
update_distro_cfg_files(config.iso_link, config.usb_disk, config.distro, config.persistence)
self.update_list_box(config.usb_disk)
if sys.platform.startswith("linux"):
self.ui.status.setText("Sync is in progress...")
os.system('sync')
self.ui.status.clear()
QtWidgets.QMessageBox.information(self, 'Finished...', iso_name(config.iso_link) + ' has been successfully installed.')
config.process_exist = None
def onInstall_syslinuxClick(self):
"""
Function to install syslinux/extlinux on selected USB disk.
:return:
"""
if platform.system() == "Linux" or platform.system() == "Windows":
if self.ui.install_sys_all.isChecked() or self.ui.install_sys_only.isChecked():
log("Installing default syslinux on ", config.usb_disk)
ret = syslinux_default(config.usb_disk)
if ret is True:
if self.ui.install_sys_all.isChecked():
log("Copying multibootusb directory to " + config.usb_mount)
for dirpath, dirnames, filenames in os.walk(resource_path(os.path.join("tools", "multibootusb"))):
for f in filenames:
log("Copying " + f)
shutil.copy(resource_path(os.path.join(dirpath, f)), os.path.join(self.usb.get_usb(config.usb_disk).mount, "multibootusb"))
QtWidgets.QMessageBox.information(self, 'Install Success...',
'Syslinux installed successfully on ' + config.usb_disk)
elif ret is False:
QtWidgets.QMessageBox.information(self, 'Install error...',
'Sorry. Syslinux failed to install on ' + config.usb_disk)
else:
QtWidgets.QMessageBox.information(self, 'No selection...',
'Please select one of the option from above.')
def onedit_syslinux(self):
"""
Function to edit main syslinux.cfg file.
:return:
"""
# Function to edit syslinux.cfg file on editors like gedit, notepad etc.
# Suggest me more editor which can be included in to this function.
sys_cfg_file = os.path.join(config.usb_mount, "multibootusb", "syslinux.cfg")
log("Locating " + sys_cfg_file)
editor = ''
if not os.path.exists(sys_cfg_file):
log("syslinux.cfg file not found...")
QtWidgets.QMessageBox.information(self, 'File not found...', 'Sorry. Unable to locate syslinux.cfg file.\n'
'You can only edit syslinux.cfg file generated by multibootusb.')
else:
if platform.system() == "Linux":
for e in config.editors_linux:
if subprocess.call('which ' + e, shell=True) == 0:
log("Editor found is " + e)
editor = e
break
elif platform.system() == "Windows":
for e in config.editors_win:
if not shutil.which(e) is None:
log("Editor found is " + e)
editor = e
break
if not editor:
QtWidgets.QMessageBox.information(self, 'Editor not found...',
'Sorry. Installed editor is not supported by multibootusb\n'
'Edit ' + sys_cfg_file + ' manually.\n')
else:
try:
subprocess.Popen(editor + " '" + sys_cfg_file + "'", shell=True).pid
except OSError:
QtWidgets.QMessageBox.warning(self, 'Error...',
'Failed to open syslinux.cfg file.\n'
'Edit syslinux.cfg file manually.\n')
def OnUninstallClick(self):
"""
Triggers a function to uninstall a selected distro.
:return:
"""
if self.ui.listWidget.currentItem() is None:
log("Please select a distro from the list.")
QtWidgets.QMessageBox.information(self, 'No selection.', 'Please select a distro from the list.')
else:
config.uninstall_distro_dir_name = str(self.ui.listWidget.currentItem().text()).strip()
reply = QtWidgets.QMessageBox.question(self, "Review selection...",
"Are you sure to uninstall " + config.uninstall_distro_dir_name,
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
if not os.path.exists(os.path.join(config.usb_mount, 'multibootusb', config.uninstall_distro_dir_name)):
log("Distro install directory not found. Just updating syslinux.cfg file.")
update_sys_cfg_file()
#self.uninstall.update_sys_cfg_file()
else:
self.progress_thread_uninstall.start()
def uninstall_sys_file_update(self):
"""
Function to remove and update uninstall distro text.
:return:
"""
update_sys_cfg_file()
self.update_list_box(config.usb_mount)
if sys.platform.startswith("linux"):
self.ui.status.setText("Sync is in progress...")
os.system('sync')
self.ui.status.clear()
QtWidgets.QMessageBox.information(self, 'Uninstall Complete...',
config.uninstall_distro_dir_name + ' has been successfully removed.')
def onCreateClick(self):
"""
Main function to create bootable USB disk.
:param usb_disk: ComboBox text as detected USB disk.
:param iso_link: LineEdit text as selected ISO link.
:return:
"""
if not config.usb_disk:
log("No USB device found.\n\nInsert USB and use Refresh USB button to detect USB.")
QtWidgets.QMessageBox.information(self, "No Device...",
"No USB device found.\n\nInsert USB and use Refresh USB button to detect USB.")
elif not config.iso_link:
log("No ISO found.\n\nPlease use step 2 to choose an ISO.")
QtWidgets.QMessageBox.information(self, "No ISO...", "No ISO found.\n\nPlease use step 2 to choose an ISO.")
elif usb.details(config.usb_disk)['mount_point'] == 'No_Mount':
log("USB disk is not mounted.\nPlease mount USB disk and press refresh USB button.")
QtWidgets.QMessageBox.information(self, "No Mount...", "USB disk is not mounted.\n"
"Please mount USB disk and press refresh USB button.")
else:
# clean_iso_cfg_ext_dir(os.path.join(multibootusb_host_dir(), "iso_cfg_ext_dir")) # Need to be cleaned.
# extract_cfg_file(config.iso_link) # Extract files from ISO
# config.distro = distro(iso_cfg_ext_dir(), config.iso_link) # Detect supported distro
usb_details = usb.details(config.usb_disk)
log("USB Disk is " + config.usb_disk)
log("USB Label is " + config.usb_label)
log("USB UUID is " + config.usb_uuid)
log("USB Mount path is " + config.usb_mount)
log("Total size of the disk is " + str(usb.bytes2human(usb_details['size_total'])))
log("Total used size is " + str(usb.bytes2human(usb_details['size_used'])))
log("Total size left on the disk is " + str(usb.bytes2human(usb_details['size_free'])))
log("FileSystem is " + usb_details['file_system'])
log("Vendor is " + usb_details['vendor'])
log("Model is " + usb_details['model'])
log("Name of the ISO file is " + iso_name(config.iso_link))
if os.path.exists(config.iso_link):
self.ui.lineEdit.clear()
if config.distro:
log("Distro type detected is " + config.distro)
copy_mbusb_dir_usb(config.usb_disk)
if not os.path.exists(os.path.join(config.usb_mount, "multibootusb", iso_basename(config.iso_link))):
config.persistence = self.ui.slider_persistence.value() * 1024 * 1024
install_size = iso_size(config.iso_link) + config.persistence
log("Persistence choosen is " + str(config.persistence) + " MB")
if install_size >= disk_usage(config.usb_mount).free:
QtWidgets.QMessageBox.information(self, "No Space.", "No space available on " +
config.usb_disk)
else:
reply = QtWidgets.QMessageBox.question(self, 'Review selection...',
'Selected USB disk:: %s\n' % config.usb_disk +
'USB mount point:: %s\n' % config.usb_mount +
'Selected distro:: %s\n\n' % iso_name(config.iso_link) +
'Would you like to proceed for installation?',
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
self.ui.slider_persistence.setEnabled(False)
config.process_exist = True
self.progress_thread_install.start()
else:
QtWidgets.QMessageBox.information(self, 'Already Exist...',
os.path.basename(config.iso_link) + ' is already installed.')
else:
QtWidgets.QMessageBox.information(self, 'No support...',
'Sorry.\n' + os.path.basename(config.iso_link) +
' is not supported at the moment.\n'
'Please email this issue to feedback.multibootusb@gmail.com')
# Added to refresh usb disk remaining size after distro installation
# self.update_gui_usb_info()
def dd_finished(self):
"""
Re-enable the blocked widgets for newer use.
:return:
"""
self.ui.imager_progressbar.setValue(0)
self.ui.imager_label_status.clear()
self.ui.comboBox_2.setEnabled(True)
self.ui.pushButton.setEnabled(True)
self.ui.imager_bootable.setText("Bootable ISO :: ")
self.ui.imager_iso_size.setText("ISO Size :: ")
config.process_exist = None
QtWidgets.QMessageBox.information(self, 'Finished...', 'ISO has been written to USB disk.\nPlease reboot your '
'system to boot from USB.')
def dd_start(self):
"""
Function to block the widgets under ISO Imager tab...
:return:
"""
self.ui.imager_progressbar.setValue(0)
self.ui.imager_label_status.clear()
self.ui.lineEdit_3.clear()
self.ui.pushButton.setEnabled(False)
self.ui.comboBox_2.setEnabled(False)
self.ui.pushbtn_imager_refreshusb.setEnabled(False)
status_text = ("<b>Writing " + os.path.basename(config.imager_iso_link) + "</b>" + " to " + "<b>" +
config.imager_usb_disk_selected + "</b>")
self.ui.imager_label_status.setText(status_text)
def dd_quit(self):
self.ui.imager_progressbar.setValue(0)
self.ui.imager_label_status.clear()
self.ui.comboBox_2.setEnabled(True)
self.ui.pushButton.setEnabled(True)
QtWidgets.QMessageBox.information(self, 'Failed!', 'Writing ISO failed.')
def dd_write(self):
if not config.imager_usb_disk:
QtWidgets.QMessageBox.information(self, 'No USB...', 'Please Insert USB disk and rerun multibootusb.')
elif not config.imager_iso_link:
QtWidgets.QMessageBox.information(self, 'No ISO...', 'Please select an ISO.')
else:
usb_disk_size = int(self.imager_usb_detail(config.imager_usb_disk, partition=0).total_size)
self.iso_size = os.path.getsize(config.imager_iso_link)
if self.iso_size >= usb_disk_size:
QtWidgets.QMessageBox.information(self, "No Space.", os.path.basename(config.imager_iso_link) +
" size is larger than the size of " + config.imager_usb_disk)
else:
reply = QtWidgets.QMessageBox.question \
(self, 'Review selection...',
'Selected USB disk:: %s\n' % config.imager_usb_disk +
'Selected distro:: %s\n\n' % os.path.basename(config.imager_iso_link) +
'Would you like to proceed for installation?',
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
self.dd_start()
config.process_exist = True
self.progress_thread_dd.start()
def on_close_Click(self):
"""
Closes main GUI.
:return:
"""
self.close()
def closeEvent(self, event):
"""
To capture the main close event.
:param event: Close event.
:return:
"""
if config.process_exist == None:
event.accept()
else:
reply = QtWidgets.QMessageBox.question(self, 'Exit MultiBootUSB...',
"A process is still running.\n"
"Do you really want to quit multibootusb?", QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
log("Closing multibootusb...")
event.accept()
sys.exit(0)
else:
log("Close event cancelled.")
event.ignore()
class GuiInstallProgress(QtCore.QThread):
"""
Update GUI thread during install.
"""
update = QtCore.pyqtSignal(int)
status = QtCore.pyqtSignal(str)
finished = QtCore.pyqtSignal()
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
install_dir = os.path.join(config.usb_mount, "multibootusb", iso_basename(config.iso_link))
self.thread = GenericThread(install_progress)
status_text = ""
self.thread.start()
while self.thread.isRunning():
if config.status_text.strip():
config.status_text = config.status_text.replace(install_dir + "/", "Extracting ")
self.update.emit(config.percentage)
self.status.emit(config.status_text)
if not self.thread.isFinished() and config.percentage == 100:
config.status_text = ""
self.status.emit("Please wait...")
self.update.emit(100)
self.update.emit(0)
self.status.emit("Installing boot loader...")
if self.thread.isFinished():
config.status_text = ""
self.finished.emit()
log("Distro extraction completed...")
return
class GuiUninstallProgress(QtCore.QThread):
"""
Update GUI thread during uninstall.
"""
update = QtCore.pyqtSignal(int)
status = QtCore.pyqtSignal(str)
finished = QtCore.pyqtSignal()
def __init__(self):
QtCore.QThread.__init__(self)
self.thread = GenericThread(uninstall_progress)
def __del__(self):
self.wait()
def run(self):
self.thread.start()
while self.thread.isRunning():
self.update.emit(config.percentage)
self.status.emit(config.status_text)
if not self.thread.isFinished() and config.percentage == 100:
config.status_text = "Please wait..."
self.update.emit(100)
self.update.emit(0)
config.percentage = 0
self.status.emit("Updating syslinux.cfg file...")
if self.thread.isFinished():
config.status_text = ""
self.finished.emit()
log("Distro uninstall is complete...")
return
class DD_Progress(QtCore.QThread):
"""
Update GUI progress bar without blocking rest of GUI element when dd process is in progress.
"""
update = QtCore.pyqtSignal(int)
status = QtCore.pyqtSignal(str)
finished = QtCore.pyqtSignal()
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)
def __del__(self):
self.wait()
def run(self):
self.thread.start()
while self.thread.isRunning():
if config.imager_percentage:
self.update.emit(config.imager_percentage)
if not self.thread.isFinished() and config.percentage == 100:
config.imager_status_text = ""
self.status.emit("Please wait...")
self.update.emit(100)
self.update.emit(0)
if self.thread.isFinished():
config.status_text = ""
self.finished.emit()
return
class GenericThread(QtCore.QThread):
def __init__(self, function, *args, **kwargs):
QtCore.QThread.__init__(self)
self.function = function
self.args = args
self.kwargs = kwargs
def __del__(self):
self.wait()
def run(self):
self.function(*self.args, **self.kwargs)
return
def show_admin_info():
"""
Show simple information box reminding user to run the software with admin/root privilege.
Only required under Linux as the windows executable always will start with admin privilege.
:return:
"""
msg = QtWidgets.QMessageBox()
msg.setIcon(QtWidgets.QMessageBox.Information)
msg.setText('Admin privilege is required to run multibootusb.\n If you are running from source try '
'\'sudo python3 ./multibootusb\'\n or you can try \'multibootusb-pkexec\' (post install)')
msg.exec_()
def main_gui():
app = QtWidgets.QApplication(sys.argv)
window = AppGui()
ui = Ui_Dialog()
window.show()
window.setWindowTitle("MultiBootUSB - " + mbusb_version())
window.setWindowIcon(QtGui.QIcon(resource_path(os.path.join("data", "tools", "multibootusb.png"))))
if platform.system() == 'Linux':
if os.getuid() != 0:
show_admin_info()
sys.exit(app.exec_())

@ -0,0 +1,111 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Name: persistence.py
# Purpose: Module to deal with persistence of a selected distro.
# 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
import sys
import os
import platform
import tarfile
import subprocess
from . import usb
from . import iso
from . import gen
from . import config
def persistence_distro(distro, usb_disk, iso_link):
"""
Function to detect if distro can have persistence option.
:param distro: Detected distro name.
:return: Distro name as string or None otherwise.
"""
iso_size = iso.iso_size(iso_link)
fat_max_size = (4096 * 1024 * 1024)
usb_details = usb.details(usb_disk)
usb_sf = usb_details['file_system']
usb_free_size = usb_details['size_free']
config.usb_uuid = usb_details['uuid']
config.usb_label = usb_details['label']
if usb_sf == 'vfat' or 'FAT32':
if usb_free_size > fat_max_size:
_max_size = fat_max_size
else:
_max_size = usb_free_size
else:
_max_size = usb_free_size
if distro == "ubuntu":
gen.log("Persistence option is available.")
return "ubuntu", _max_size
# FIXME to get debian persistence workable...
# Able to add successfully but unable to keep persistence data.
elif distro == "debian" or distro == "debian-install":
gen.log("Persistence option is available.")
return "debian", _max_size
elif distro == "fedora":
gen.log("Persistence option is available.")
return "fedora", _max_size
else:
return None, None
def create_persistence():
if config.distro == "ubuntu":
fs_name = 'casper-rw'
elif config.distro == 'debian' or config.distro == "debian-install":
fs_name = 'live-rw'
elif config.distro == 'fedora':
fs_name = 'overlay-' + config.usb_label + '-' + config.usb_uuid
persistence = config.persistence / 1024 / 1024
if platform.system() == 'Linux':
mkfs = 'mkfs.ext3'
dd = 'dd'
persistence_mkfs_cmd = mkfs + ' -F ' + os.path.join(config.usb_mount, 'multibootusb',
iso.iso_basename(config.iso_link),
fs_name)
elif platform.system() == 'Windows':
mkfs = gen.quote(gen.resource_path(os.path.join("data", "tools", "mkfs", "mke2fs.exe")))
dd = gen.quote(gen.resource_path(os.path.join("data", "tools", "dd", "dd.exe")))
persistence_mkfs_cmd = 'echo y|' + mkfs + ' -b 1024 -L ' + fs_name + ' ' + os.path.join(config.usb_mount, 'multibootusb',
iso.iso_basename(config.iso_link), fs_name)
if config.distro == 'fedora':
persistence_dd_cmd = dd + ' if=/dev/zero ' \
'of=' + os.path.join(config.usb_mount, 'multibootusb',
iso.iso_basename(config.iso_link), 'LiveOS', fs_name) + \
' bs=1M count=' + str(int(persistence))
else:
persistence_dd_cmd = dd + ' if=/dev/zero of=' + os.path.join(config.usb_mount, 'multibootusb',
iso.iso_basename(config.iso_link), fs_name) +\
' bs=1M count=' + str(int(persistence))
gen.log('Executing ==>' + persistence_dd_cmd)
config.status_text = 'Creating persistence file...'
if subprocess.call(persistence_dd_cmd, shell=True) == 0:
gen.log("\nSuccessfully created persistence file...\n")
if not config.distro == 'fedora':
gen.log('Applying filesystem to persistence file...')
gen.log('Executing ==> ' + persistence_mkfs_cmd)
config.status_text = 'Applying filesystem to persistence file...'
if subprocess.call(persistence_mkfs_cmd, shell=True) == 0:
gen.log("\nSuccessfully applied filesystem...\n")
def extract_file(file_path, install_dir):
"""
Function to extract persistence files to distro install directory.
:param file_path: Path to persistence file.
:param install_dir: Path to distro install directory.
:return:
"""
tar = tarfile.open(file_path, "r:bz2")
tar.extractall(install_dir)
tar.close()

@ -0,0 +1,49 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# progressbar - Text progress bar library for Python.
# Copyright (c) 2005 Nilton Volpato
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Text progress bar library for Python.
A text progress bar is typically used to display the progress of a long
running operation, providing a visual cue that processing is underway.
The ProgressBar class manages the current progress, and the format of the line
is given by a number of widgets. A widget is an object that may display
differently depending on the state of the progress bar. There are three types
of widgets:
- a string, which always shows itself
- a ProgressBarWidget, which may return a different value every time its
update method is called
- a ProgressBarWidgetHFill, which is like ProgressBarWidget, except it
expands to fill the remaining width of the line.
The progressbar module is very easy to use, yet very powerful. It will also
automatically enable features like auto-resizing when the system supports it.
"""
__author__ = 'Nilton Volpato'
__author_email__ = 'first-name dot last-name @ gmail.com'
__date__ = '2011-05-14'
__version__ = '2.3'
from .compat import *
from .widgets import *
from .progressbar import *

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
#
# progressbar - Text progress bar library for Python.
# Copyright (c) 2005 Nilton Volpato
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Compatibility methods and classes for the progressbar module."""
# Python 3.x (and backports) use a modified iterator syntax
# This will allow 2.x to behave with 3.x iterators
try:
next
except NameError:
def next(iter):
try:
# Try new style iterators
return iter.__next__()
except AttributeError:
# Fallback in case of a "native" iterator
return iter.next()
# Python < 2.5 does not have "any"
try:
any
except NameError:
def any(iterator):
for item in iterator:
if item: return True
return False

@ -0,0 +1,306 @@
# -*- coding: utf-8 -*-
#
# progressbar - Text progress bar library for Python.
# Copyright (c) 2005 Nilton Volpato
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Main ProgressBar class."""
from __future__ import division
import math
import os
import signal
import sys
import time
try:
from fcntl import ioctl
from array import array
import termios
except ImportError:
pass
from .compat import * # for: any, next
from . import widgets
class UnknownLength: pass
class ProgressBar(object):
"""The ProgressBar class which updates and prints the bar.
A common way of using it is like:
>>> pbar = ProgressBar().start()
>>> for i in range(100):
... # do something
... pbar.update(i+1)
...
>>> pbar.finish()
You can also use a ProgressBar as an iterator:
>>> progress = ProgressBar()
>>> for i in progress(some_iterable):
... # do something
...
Since the progress bar is incredibly customizable you can specify
different widgets of any type in any order. You can even write your own
widgets! However, since there are already a good number of widgets you
should probably play around with them before moving on to create your own
widgets.
The term_width parameter represents the current terminal width. If the
parameter is set to an integer then the progress bar will use that,
otherwise it will attempt to determine the terminal width falling back to
80 columns if the width cannot be determined.
When implementing a widget's update method you are passed a reference to
the current progress bar. As a result, you have access to the
ProgressBar's methods and attributes. Although there is nothing preventing
you from changing the ProgressBar you should treat it as read only.
Useful methods and attributes include (Public API):
- currval: current progress (0 <= currval <= maxval)
- maxval: maximum (and final) value
- finished: True if the bar has finished (reached 100%)
- start_time: the time when start() method of ProgressBar was called
- seconds_elapsed: seconds elapsed since start_time and last call to
update
- percentage(): progress in percent [0..100]
"""
__slots__ = ('currval', 'fd', 'finished', 'last_update_time',
'left_justify', 'maxval', 'next_update', 'num_intervals',
'poll', 'seconds_elapsed', 'signal_set', 'start_time',
'term_width', 'update_interval', 'widgets', '_time_sensitive',
'__iterable')
_DEFAULT_MAXVAL = 100
_DEFAULT_TERMSIZE = 80
_DEFAULT_WIDGETS = [widgets.Percentage(), ' ', widgets.Bar()]
def __init__(self, maxval=None, widgets=None, term_width=None, poll=1,
left_justify=True, fd=sys.stderr):
"""Initializes a progress bar with sane defaults."""
# Don't share a reference with any other progress bars
if widgets is None:
widgets = list(self._DEFAULT_WIDGETS)
self.maxval = maxval
self.widgets = widgets
self.fd = fd
self.left_justify = left_justify
self.signal_set = False
if term_width is not None:
self.term_width = term_width
else:
try:
self._handle_resize()
signal.signal(signal.SIGWINCH, self._handle_resize)
self.signal_set = True
except (SystemExit, KeyboardInterrupt): raise
except:
self.term_width = self._env_size()
self.__iterable = None
self._update_widgets()
self.currval = 0
self.finished = False
self.last_update_time = None
self.poll = poll
self.seconds_elapsed = 0
self.start_time = None
self.update_interval = 1
self.next_update = 0
def __call__(self, iterable):
"""Use a ProgressBar to iterate through an iterable."""
try:
self.maxval = len(iterable)
except:
if self.maxval is None:
self.maxval = UnknownLength
self.__iterable = iter(iterable)
return self
def __iter__(self):
return self
def __next__(self):
try:
value = next(self.__iterable)
if self.start_time is None:
self.start()
else:
self.update(self.currval + 1)
return value
except StopIteration:
if self.start_time is None:
self.start()
self.finish()
raise
# Create an alias so that Python 2.x won't complain about not being
# an iterator.
next = __next__
def _env_size(self):
"""Tries to find the term_width from the environment."""
return int(os.environ.get('COLUMNS', self._DEFAULT_TERMSIZE)) - 1
def _handle_resize(self, signum=None, frame=None):
"""Tries to catch resize signals sent from the terminal."""
h, w = array('h', ioctl(self.fd, termios.TIOCGWINSZ, '\0' * 8))[:2]
self.term_width = w
def percentage(self):
"""Returns the progress as a percentage."""
if self.currval >= self.maxval:
return 100.0
return (self.currval * 100.0 / self.maxval) if self.maxval else 100.00
percent = property(percentage)
def _format_widgets(self):
result = []
expanding = []
width = self.term_width
for index, widget in enumerate(self.widgets):
if isinstance(widget, widgets.WidgetHFill):
result.append(widget)
expanding.insert(0, index)
else:
widget = widgets.format_updatable(widget, self)
result.append(widget)
width -= len(widget)
count = len(expanding)
while count:
portion = max(int(math.ceil(width * 1. / count)), 0)
index = expanding.pop()
count -= 1
widget = result[index].update(self, portion)
width -= len(widget)
result[index] = widget
return result
def _format_line(self):
"""Joins the widgets and justifies the line."""
widgets = ''.join(self._format_widgets())
if self.left_justify: return widgets.ljust(self.term_width)
else: return widgets.rjust(self.term_width)
def _need_update(self):
"""Returns whether the ProgressBar should redraw the line."""
if self.currval >= self.next_update or self.finished: return True
delta = time.time() - self.last_update_time
return self._time_sensitive and delta > self.poll
def _update_widgets(self):
"""Checks all widgets for the time sensitive bit."""
self._time_sensitive = any(getattr(w, 'TIME_SENSITIVE', False)
for w in self.widgets)
def update(self, value=None):
"""Updates the ProgressBar to a new value."""
if value is not None and value is not UnknownLength:
if (self.maxval is not UnknownLength
and not 0 <= value <= self.maxval):
raise ValueError('Value out of range')
self.currval = value
if not self._need_update(): return
if self.start_time is None:
raise RuntimeError('You must call "start" before calling "update"')
now = time.time()
self.seconds_elapsed = now - self.start_time
self.next_update = self.currval + self.update_interval
self.fd.write(self._format_line() + '\r')
self.fd.flush()
self.last_update_time = now
def start(self):
"""Starts measuring time, and prints the bar at 0%.
It returns self so you can use it like this:
>>> pbar = ProgressBar().start()
>>> for i in range(100):
... # do something
... pbar.update(i+1)
...
>>> pbar.finish()
"""
if self.maxval is None:
self.maxval = self._DEFAULT_MAXVAL
self.num_intervals = max(100, self.term_width)
self.next_update = 0
if self.maxval is not UnknownLength:
if self.maxval < 0: raise ValueError('Value out of range')
self.update_interval = self.maxval / self.num_intervals
self.start_time = self.last_update_time = time.time()
self.update(0)
return self
def finish(self):
"""Puts the ProgressBar bar in the finished state."""
if self.finished:
return
self.finished = True
self.update(self.maxval)
self.fd.write('\n')
if self.signal_set:
signal.signal(signal.SIGWINCH, signal.SIG_DFL)

@ -0,0 +1,355 @@
# -*- coding: utf-8 -*-
#
# progressbar - Text progress bar library for Python.
# Copyright (c) 2005 Nilton Volpato
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Default ProgressBar widgets."""
from __future__ import division
import datetime
import math
try:
from abc import ABCMeta, abstractmethod
except ImportError:
AbstractWidget = object
abstractmethod = lambda fn: fn
else:
AbstractWidget = ABCMeta('AbstractWidget', (object,), {})
def format_updatable(updatable, pbar):
if hasattr(updatable, 'update'): return updatable.update(pbar)
else: return updatable
class Widget(AbstractWidget):
"""The base class for all widgets.
The ProgressBar will call the widget's update value when the widget should
be updated. The widget's size may change between calls, but the widget may
display incorrectly if the size changes drastically and repeatedly.
The boolean TIME_SENSITIVE informs the ProgressBar that it should be
updated more often because it is time sensitive.
"""
TIME_SENSITIVE = False
__slots__ = ()
@abstractmethod
def update(self, pbar):
"""Updates the widget.
pbar - a reference to the calling ProgressBar
"""
class WidgetHFill(Widget):
"""The base class for all variable width widgets.
This widget is much like the \\hfill command in TeX, it will expand to
fill the line. You can use more than one in the same line, and they will
all have the same width, and together will fill the line.
"""
@abstractmethod
def update(self, pbar, width):
"""Updates the widget providing the total width the widget must fill.
pbar - a reference to the calling ProgressBar
width - The total width the widget must fill
"""
class Timer(Widget):
"""Widget which displays the elapsed seconds."""
__slots__ = ('format_string',)
TIME_SENSITIVE = True
def __init__(self, format='Elapsed Time: %s'):
self.format_string = format
@staticmethod
def format_time(seconds):
"""Formats time as the string "HH:MM:SS"."""
return str(datetime.timedelta(seconds=int(seconds)))
def update(self, pbar):
"""Updates the widget to show the elapsed time."""
return self.format_string % self.format_time(pbar.seconds_elapsed)
class ETA(Timer):
"""Widget which attempts to estimate the time of arrival."""
TIME_SENSITIVE = True
def update(self, pbar):
"""Updates the widget to show the ETA or total time when finished."""
if pbar.currval == 0:
return 'ETA: --:--:--'
elif pbar.finished:
return 'Time: %s' % self.format_time(pbar.seconds_elapsed)
else:
elapsed = pbar.seconds_elapsed
eta = elapsed * pbar.maxval / pbar.currval - elapsed
return 'ETA: %s' % self.format_time(eta)
class AdaptiveETA(Timer):
"""Widget which attempts to estimate the time of arrival.
Uses a weighted average of two estimates:
1) ETA based on the total progress and time elapsed so far
2) ETA based on the progress as per the last 10 update reports
The weight depends on the current progress so that to begin with the
total progress is used and at the end only the most recent progress is
used.
"""
TIME_SENSITIVE = True
NUM_SAMPLES = 10
def _update_samples(self, currval, elapsed):
sample = (currval, elapsed)
if not hasattr(self, 'samples'):
self.samples = [sample] * (self.NUM_SAMPLES + 1)
else:
self.samples.append(sample)
return self.samples.pop(0)
def _eta(self, maxval, currval, elapsed):
return elapsed * maxval / float(currval) - elapsed
def update(self, pbar):
"""Updates the widget to show the ETA or total time when finished."""
if pbar.currval == 0:
return 'ETA: --:--:--'
elif pbar.finished:
return 'Time: %s' % self.format_time(pbar.seconds_elapsed)
else:
elapsed = pbar.seconds_elapsed
currval1, elapsed1 = self._update_samples(pbar.currval, elapsed)
eta = self._eta(pbar.maxval, pbar.currval, elapsed)
if pbar.currval > currval1:
etasamp = self._eta(pbar.maxval - currval1,
pbar.currval - currval1,
elapsed - elapsed1)
weight = (pbar.currval / float(pbar.maxval)) ** 0.5
eta = (1 - weight) * eta + weight * etasamp
return 'ETA: %s' % self.format_time(eta)
class FileTransferSpeed(Widget):
"""Widget for showing the transfer speed (useful for file transfers)."""
FORMAT = '%6.2f %s%s/s'
PREFIXES = ' kMGTPEZY'
__slots__ = ('unit',)
def __init__(self, unit='B'):
self.unit = unit
def update(self, pbar):
"""Updates the widget with the current SI prefixed speed."""
if pbar.seconds_elapsed < 2e-6 or pbar.currval < 2e-6: # =~ 0
scaled = power = 0
else:
speed = pbar.currval / pbar.seconds_elapsed
power = int(math.log(speed, 1000))
scaled = speed / 1000.**power
return self.FORMAT % (scaled, self.PREFIXES[power], self.unit)
class AnimatedMarker(Widget):
"""An animated marker for the progress bar which defaults to appear as if
it were rotating.
"""
__slots__ = ('markers', 'curmark')
def __init__(self, markers='|/-\\'):
self.markers = markers
self.curmark = -1
def update(self, pbar):
"""Updates the widget to show the next marker or the first marker when
finished"""
if pbar.finished: return self.markers[0]
self.curmark = (self.curmark + 1) % len(self.markers)
return self.markers[self.curmark]
# Alias for backwards compatibility
RotatingMarker = AnimatedMarker
class Counter(Widget):
"""Displays the current count."""
__slots__ = ('format_string',)
def __init__(self, format='%d'):
self.format_string = format
def update(self, pbar):
return self.format_string % pbar.currval
class Percentage(Widget):
"""Displays the current percentage as a number with a percent sign."""
def update(self, pbar):
return '%3d%%' % pbar.percentage()
class FormatLabel(Timer):
"""Displays a formatted label."""
mapping = {
'elapsed': ('seconds_elapsed', Timer.format_time),
'finished': ('finished', None),
'last_update': ('last_update_time', None),
'max': ('maxval', None),
'seconds': ('seconds_elapsed', None),
'start': ('start_time', None),
'value': ('currval', None)
}
__slots__ = ('format_string',)
def __init__(self, format):
self.format_string = format
def update(self, pbar):
context = {}
for name, (key, transform) in self.mapping.items():
try:
value = getattr(pbar, key)
if transform is None:
context[name] = value
else:
context[name] = transform(value)
except: pass
return self.format_string % context
class SimpleProgress(Widget):
"""Returns progress as a count of the total (e.g.: "5 of 47")."""
__slots__ = ('sep',)
def __init__(self, sep=' of '):
self.sep = sep
def update(self, pbar):
return '%d%s%d' % (pbar.currval, self.sep, pbar.maxval)
class Bar(WidgetHFill):
"""A progress bar which stretches to fill the line."""
__slots__ = ('marker', 'left', 'right', 'fill', 'fill_left')
def __init__(self, marker='#', left='|', right='|', fill=' ',
fill_left=True):
"""Creates a customizable progress bar.
marker - string or updatable object to use as a marker
left - string or updatable object to use as a left border
right - string or updatable object to use as a right border
fill - character to use for the empty part of the progress bar
fill_left - whether to fill from the left or the right
"""
self.marker = marker
self.left = left
self.right = right
self.fill = fill
self.fill_left = fill_left
def update(self, pbar, width):
"""Updates the progress bar and its subcomponents."""
left, marked, right = (format_updatable(i, pbar) for i in
(self.left, self.marker, self.right))
width -= len(left) + len(right)
# Marked must *always* have length of 1
if pbar.maxval:
marked *= int(pbar.currval / pbar.maxval * width)
else:
marked = ''
if self.fill_left:
return '%s%s%s' % (left, marked.ljust(width, self.fill), right)
else:
return '%s%s%s' % (left, marked.rjust(width, self.fill), right)
class ReverseBar(Bar):
"""A bar which has a marker which bounces from side to side."""
def __init__(self, marker='#', left='|', right='|', fill=' ',
fill_left=False):
"""Creates a customizable progress bar.
marker - string or updatable object to use as a marker
left - string or updatable object to use as a left border
right - string or updatable object to use as a right border
fill - character to use for the empty part of the progress bar
fill_left - whether to fill from the left or the right
"""
self.marker = marker
self.left = left
self.right = right
self.fill = fill
self.fill_left = fill_left
class BouncingBar(Bar):
def update(self, pbar, width):
"""Updates the progress bar and its subcomponents."""
left, marker, right = (format_updatable(i, pbar) for i in
(self.left, self.marker, self.right))
width -= len(left) + len(right)
if pbar.finished: return '%s%s%s' % (left, width * marker, right)
position = int(pbar.currval % (width * 2 - 1))
if position > width: position = width * 2 - position
lpad = self.fill * (position - 1)
rpad = self.fill * (width - len(marker) - len(lpad))
# Swap if we want to bounce the other way
if not self.fill_left: rpad, lpad = lpad, rpad
return '%s%s%s%s%s' % (left, lpad, marker, rpad, right)

@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012 Sebastian Wiesner <lunaryorn@gmail.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev
======
A binding to libudev.
The :class:`Context` provides the connection to the udev device database
and enumerates devices. Individual devices are represented by the
:class:`Device` class.
Device monitoring is provided by :class:`Monitor` and
:class:`MonitorObserver`. With :mod:`pyudev.pyqt4`, :mod:`pyudev.pyside`,
:mod:`pyudev.glib` and :mod:`pyudev.wx` device monitoring can be integrated
into the event loop of various GUI toolkits.
.. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from ._errors import DeviceNotFoundAtPathError
from ._errors import DeviceNotFoundByFileError
from ._errors import DeviceNotFoundByNameError
from ._errors import DeviceNotFoundByNumberError
from ._errors import DeviceNotFoundError
from ._errors import DeviceNotFoundInEnvironmentError
from .device import Attributes
from .device import Device
from .device import Devices
from .device import Tags
from .discover import DeviceFileHypothesis
from .discover import DeviceNameHypothesis
from .discover import DeviceNumberHypothesis
from .discover import DevicePathHypothesis
from .discover import Discovery
from .core import Context
from .core import Enumerator
from .monitor import Monitor
from .monitor import MonitorObserver
from .version import __version__
from .version import __version_info__
from ._util import udev_version

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2011 Sebastian Wiesner <lunaryorn@gmail.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._compat
==============
Compatibility for Python versions, that lack certain functions.
.. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
from subprocess import Popen, CalledProcessError, PIPE
def check_output(command):
"""
Compatibility with :func:`subprocess.check_output` from Python 2.7 and
upwards.
"""
proc = Popen(command, stdout=PIPE)
output = proc.communicate()[0]
if proc.returncode != 0:
raise CalledProcessError(proc.returncode, command)
return output

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern <amulhern@redhat.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._ctypeslib
=================
Wrappers for libraries.
.. moduleauthor:: mulhern <amulhern@redhat.com>
"""
from . import libc
from . import libudev

@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2013 Sebastian Wiesner <lunaryorn@gmail.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._ctypeslib._errorcheckers
================================
Error checkers for ctypes wrappers.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
import errno
from ctypes import get_errno
ERRNO_EXCEPTIONS = {
errno.ENOMEM: MemoryError,
errno.EOVERFLOW: OverflowError,
errno.EINVAL: ValueError
}
def exception_from_errno(errnum):
"""Create an exception from ``errnum``.
``errnum`` is an integral error number.
Return an exception object appropriate to ``errnum``.
"""
exception = ERRNO_EXCEPTIONS.get(errnum)
errorstr = os.strerror(errnum)
if exception is not None:
return exception(errorstr)
else:
return EnvironmentError(errnum, errorstr)
def check_negative_errorcode(result, func, *args):
"""Error checker for funtions, which return negative error codes.
If ``result`` is smaller than ``0``, it is interpreted as negative error
code, and an appropriate exception is raised:
- ``-ENOMEM`` raises a :exc:`~exceptions.MemoryError`
- ``-EOVERFLOW`` raises a :exc:`~exceptions.OverflowError`
- all other error codes raise :exc:`~exceptions.EnvironmentError`
If result is greater or equal to ``0``, it is returned unchanged.
"""
if result < 0:
# udev returns the *negative* errno code at this point
errnum = -result
raise exception_from_errno(errnum)
else:
return result
def check_errno_on_nonzero_return(result, func, *args):
"""Error checker to check the system ``errno`` as returned by
:func:`ctypes.get_errno()`.
If ``result`` is not ``0``, an exception according to this errno is raised.
Otherwise nothing happens.
"""
if result != 0:
errnum = get_errno()
if errnum != 0:
raise exception_from_errno(errnum)
return result
def check_errno_on_null_pointer_return(result, func, *args):
"""Error checker to check the system ``errno`` as returned by
:func:`ctypes.get_errno()`.
If ``result`` is a null pointer, an exception according to this errno is
raised. Otherwise nothing happens.
"""
# pylint: disable=invalid-name
if not result:
errnum = get_errno()
if errnum != 0:
raise exception_from_errno(errnum)
return result

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2013 Sebastian Wiesner <lunaryorn@gmail.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._ctypeslib.libc
======================
Wrappers for libc.
.. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from ctypes import c_int
from ._errorcheckers import check_errno_on_nonzero_return
fd_pair = c_int * 2
SIGNATURES = dict(
pipe2=([fd_pair, c_int], c_int),
)
ERROR_CHECKERS = dict(
pipe2=check_errno_on_nonzero_return,
)

@ -0,0 +1,283 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner <lunaryorn@gmail.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._ctypeslib.libudev
=========================
Wrapper types for libudev. Use ``libudev`` attribute to access libudev
functions.
.. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from ctypes import c_char
from ctypes import c_char_p
from ctypes import c_int
from ctypes import c_uint
from ctypes import c_ulonglong
from ctypes import CDLL
from ctypes import Structure
from ctypes import POINTER
from ctypes.util import find_library
from ._errorcheckers import check_errno_on_nonzero_return
from ._errorcheckers import check_errno_on_null_pointer_return
from ._errorcheckers import check_negative_errorcode
class udev(Structure): # pylint: disable=invalid-name
"""
Dummy for ``udev`` structure.
"""
# pylint: disable=too-few-public-methods
pass
udev_p = POINTER(udev) # pylint: disable=invalid-name
class udev_enumerate(Structure): # pylint: disable=invalid-name
"""
Dummy for ``udev_enumerate`` structure.
"""
# pylint: disable=too-few-public-methods
pass
udev_enumerate_p = POINTER(udev_enumerate) # pylint: disable=invalid-name
class udev_list_entry(Structure): # pylint: disable=invalid-name
"""
Dummy for ``udev_list_entry`` structure.
"""
# pylint: disable=too-few-public-methods
pass
udev_list_entry_p = POINTER(udev_list_entry) # pylint: disable=invalid-name
class udev_device(Structure): # pylint: disable=invalid-name
"""
Dummy for ``udev_device`` structure.
"""
# pylint: disable=too-few-public-methods
pass
udev_device_p = POINTER(udev_device) # pylint: disable=invalid-name
class udev_monitor(Structure): # pylint: disable=invalid-name
"""
Dummy for ``udev_device`` structure.
"""
# pylint: disable=too-few-public-methods
pass
udev_monitor_p = POINTER(udev_monitor) # pylint: disable=invalid-name
class udev_hwdb(Structure): # pylint: disable=invalid-name
"""
Dummy for ``udev_hwdb`` structure.
"""
# pylint: disable=too-few-public-methods
pass
udev_hwdb_p = POINTER(udev_hwdb) # pylint: disable=invalid-name
dev_t = c_ulonglong # pylint: disable=invalid-name
SIGNATURES = dict(
# context
udev_new=([], udev_p),
udev_unref=([udev_p], None),
udev_ref=([udev_p], udev_p),
udev_get_sys_path=([udev_p], c_char_p),
udev_get_dev_path=([udev_p], c_char_p),
udev_get_run_path=([udev_p], c_char_p),
udev_get_log_priority=([udev_p], c_int),
udev_set_log_priority=([udev_p, c_int], None),
udev_enumerate_new=([udev_p], udev_enumerate_p),
udev_enumerate_ref=([udev_enumerate_p], udev_enumerate_p),
udev_enumerate_unref=([udev_enumerate_p], None),
udev_enumerate_add_match_subsystem=([udev_enumerate_p, c_char_p], c_int),
udev_enumerate_add_nomatch_subsystem=([udev_enumerate_p, c_char_p], c_int),
udev_enumerate_add_match_property=(
[udev_enumerate_p, c_char_p, c_char_p],
c_int
),
udev_enumerate_add_match_sysattr=(
[udev_enumerate_p, c_char_p, c_char_p],
c_int
),
udev_enumerate_add_nomatch_sysattr=(
[udev_enumerate_p, c_char_p, c_char_p],
c_int
),
udev_enumerate_add_match_tag=([udev_enumerate_p, c_char_p], c_int),
udev_enumerate_add_match_sysname=([udev_enumerate_p, c_char_p], c_int),
udev_enumerate_add_match_parent=([udev_enumerate_p, udev_device_p], c_int),
udev_enumerate_add_match_is_initialized=([udev_enumerate_p], c_int),
udev_enumerate_scan_devices=([udev_enumerate_p], c_int),
udev_enumerate_get_list_entry=([udev_enumerate_p], udev_list_entry_p),
# list entries
udev_list_entry_get_next=([udev_list_entry_p], udev_list_entry_p),
udev_list_entry_get_name=([udev_list_entry_p], c_char_p),
udev_list_entry_get_value=([udev_list_entry_p], c_char_p),
# devices
udev_device_ref=([udev_device_p], udev_device_p),
udev_device_unref=([udev_device_p], None),
udev_device_new_from_syspath=([udev_p, c_char_p], udev_device_p),
udev_device_new_from_subsystem_sysname=(
[udev_p, c_char_p, c_char_p],
udev_device_p
),
udev_device_new_from_devnum=([udev_p, c_char, dev_t], udev_device_p),
udev_device_new_from_device_id=([udev_p, c_char_p], udev_device_p),
udev_device_new_from_environment=([udev_p], udev_device_p),
udev_device_get_parent=([udev_device_p], udev_device_p),
udev_device_get_parent_with_subsystem_devtype=(
[udev_device_p, c_char_p, c_char_p],
udev_device_p
),
udev_device_get_devpath=([udev_device_p], c_char_p),
udev_device_get_subsystem=([udev_device_p], c_char_p),
udev_device_get_syspath=([udev_device_p], c_char_p),
udev_device_get_sysnum=([udev_device_p], c_char_p),
udev_device_get_sysname=([udev_device_p], c_char_p),
udev_device_get_driver=([udev_device_p], c_char_p),
udev_device_get_devtype=([udev_device_p], c_char_p),
udev_device_get_devnode=([udev_device_p], c_char_p),
udev_device_get_property_value=([udev_device_p, c_char_p], c_char_p),
udev_device_get_sysattr_value=([udev_device_p, c_char_p], c_char_p),
udev_device_get_devnum=([udev_device_p], dev_t),
udev_device_get_action=([udev_device_p], c_char_p),
udev_device_get_seqnum=([udev_device_p], c_ulonglong),
udev_device_get_is_initialized=([udev_device_p], c_int),
udev_device_get_usec_since_initialized=([udev_device_p], c_ulonglong),
udev_device_get_devlinks_list_entry=([udev_device_p], udev_list_entry_p),
udev_device_get_tags_list_entry=([udev_device_p], udev_list_entry_p),
udev_device_get_properties_list_entry=([udev_device_p], udev_list_entry_p),
udev_device_get_sysattr_list_entry=([udev_device_p], udev_list_entry_p),
udev_device_set_sysattr_value=([udev_device_p, c_char_p, c_char_p], c_int),
udev_device_has_tag=([udev_device_p, c_char_p], c_int),
# monitoring
udev_monitor_ref=([udev_monitor_p], udev_monitor_p),
udev_monitor_unref=([udev_monitor_p], None),
udev_monitor_new_from_netlink=([udev_p, c_char_p], udev_monitor_p),
udev_monitor_enable_receiving=([udev_monitor_p], c_int),
udev_monitor_set_receive_buffer_size=([udev_monitor_p, c_int], c_int),
udev_monitor_get_fd=([udev_monitor_p], c_int),
udev_monitor_receive_device=([udev_monitor_p], udev_device_p),
udev_monitor_filter_add_match_subsystem_devtype=(
[udev_monitor_p, c_char_p, c_char_p], c_int),
udev_monitor_filter_add_match_tag=([udev_monitor_p, c_char_p], c_int),
udev_monitor_filter_update=([udev_monitor_p], c_int),
udev_monitor_filter_remove=([udev_monitor_p], c_int),
# hwdb
udev_hwdb_ref=([udev_hwdb_p], udev_hwdb_p),
udev_hwdb_unref=([udev_hwdb_p], None),
udev_hwdb_new=([udev_p], udev_hwdb_p),
udev_hwdb_get_properties_list_entry=(
[udev_hwdb_p, c_char_p, c_uint],
udev_list_entry_p
)
)
ERROR_CHECKERS = dict(
udev_device_get_action=None,
udev_device_get_devlinks_list_entry=None,
udev_device_get_devnode=None,
udev_device_get_devnum=None,
udev_device_get_devpath=None,
udev_device_get_devtype=None,
udev_device_get_driver=None,
udev_device_get_is_initialized=None,
udev_device_get_parent=None,
udev_device_get_parent_with_subsystem_devtype=None,
udev_device_get_properties_list_entry=None,
udev_device_get_property_value=None,
udev_device_get_seqnum=None,
udev_device_get_subsystem=None,
udev_device_get_sysattr_list_entry=None,
udev_device_get_sysattr_value=None,
udev_device_get_sysname=None,
udev_device_get_sysnum=None,
udev_device_get_syspath=None,
udev_device_get_tags_list_entry=None,
udev_device_get_usec_since_initialized=None,
udev_device_has_tag=None,
udev_device_new_from_device_id=None,
udev_device_new_from_devnum=None,
udev_device_new_from_environment=None,
udev_device_new_from_subsystem_sysname=None,
udev_device_new_from_syspath=None,
udev_device_ref=None,
udev_device_unref=None,
udev_device_set_sysattr_value=check_negative_errorcode,
udev_enumerate_add_match_parent=check_negative_errorcode,
udev_enumerate_add_match_subsystem=check_negative_errorcode,
udev_enumerate_add_nomatch_subsystem=check_negative_errorcode,
udev_enumerate_add_match_property=check_negative_errorcode,
udev_enumerate_add_match_sysattr=check_negative_errorcode,
udev_enumerate_add_nomatch_sysattr=check_negative_errorcode,
udev_enumerate_add_match_tag=check_negative_errorcode,
udev_enumerate_add_match_sysname=check_negative_errorcode,
udev_enumerate_add_match_is_initialized=check_negative_errorcode,
udev_enumerate_get_list_entry=None,
udev_enumerate_new=None,
udev_enumerate_ref=None,
udev_enumerate_scan_devices=None,
udev_enumerate_unref=None,
udev_get_dev_path=None,
udev_get_log_priority=None,
udev_get_run_path=None,
udev_get_sys_path=None,
udev_hwdb_get_properties_list_entry=None,
udev_hwdb_new=None,
udev_hwdb_ref=None,
udev_hwdb_unref=None,
udev_list_entry_get_name=None,
udev_list_entry_get_next=None,
udev_list_entry_get_value=None,
udev_monitor_set_receive_buffer_size=check_errno_on_nonzero_return,
# libudev doc says, enable_receiving returns a negative errno, but tests
# show that this is not reliable, so query the real error code
udev_monitor_enable_receiving=check_errno_on_nonzero_return,
udev_monitor_receive_device=check_errno_on_null_pointer_return,
udev_monitor_ref=None,
udev_monitor_filter_add_match_subsystem_devtype=check_negative_errorcode,
udev_monitor_filter_add_match_tag=check_negative_errorcode,
udev_monitor_filter_update=check_errno_on_nonzero_return,
udev_monitor_filter_remove=check_errno_on_nonzero_return,
udev_monitor_get_fd=None,
udev_monitor_new_from_netlink=None,
udev_monitor_unref=None,
udev_new=None,
udev_ref=None,
udev_set_log_priority=None,
udev_unref=None
)

@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2013 Sebastian Wiesner <lunaryorn@gmail.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._ctypeslib.utils
=======================
Utilities for loading ctypeslib.
.. moduleauthor:: Anne Mulhern <amulhern@redhat.com>
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from ctypes import CDLL
from ctypes.util import find_library
def load_ctypes_library(name, signatures, error_checkers):
"""
Load library ``name`` and return a :class:`ctypes.CDLL` object for it.
:param str name: the library name
:param signatures: signatures of methods
:type signatures: dict of str * (tuple of (list of type) * type)
:param error_checkers: error checkers for methods
:type error_checkers: dict of str * ((int * ptr * arglist) -> int)
The library has errno handling enabled.
Important functions are given proper signatures and return types to support
type checking and argument conversion.
:returns: a loaded library
:rtype: ctypes.CDLL
:raises ImportError: if the library is not found
"""
library_name = find_library(name)
if not library_name:
raise ImportError('No library named %s' % name)
lib = CDLL(library_name, use_errno=True)
# Add function signatures
for funcname, signature in signatures.items():
function = getattr(lib, funcname, None)
if function:
argtypes, restype = signature
function.argtypes = argtypes
function.restype = restype
errorchecker = error_checkers.get(funcname)
if errorchecker:
function.errcheck = errorchecker
return lib

@ -0,0 +1,187 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern <amulhern@redhat.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.device._errors
=====================
Errors raised by Device methods.
.. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import abc
from six import add_metaclass
@add_metaclass(abc.ABCMeta)
class DeviceError(Exception):
"""
Any error raised when messing around w/ or trying to discover devices.
"""
@add_metaclass(abc.ABCMeta)
class DeviceNotFoundError(DeviceError):
"""
An exception indicating that no :class:`Device` was found.
.. versionchanged:: 0.5
Rename from ``NoSuchDeviceError`` to its current name.
"""
class DeviceNotFoundAtPathError(DeviceNotFoundError):
"""
A :exc:`DeviceNotFoundError` indicating that no :class:`Device` was
found at a given path.
"""
def __init__(self, sys_path):
DeviceNotFoundError.__init__(self, sys_path)
@property
def sys_path(self):
"""
The path that caused this error as string.
"""
return self.args[0]
def __str__(self):
return 'No device at {0!r}'.format(self.sys_path)
class DeviceNotFoundByFileError(DeviceNotFoundError):
"""
A :exc:`DeviceNotFoundError` indicating that no :class:`Device` was
found from the given filename.
"""
class DeviceNotFoundByInterfaceIndexError(DeviceNotFoundError):
"""
A :exc:`DeviceNotFoundError` indicating that no :class:`Device` was found
from the given interface index.
"""
class DeviceNotFoundByKernelDeviceError(DeviceNotFoundError):
"""
A :exc:`DeviceNotFoundError` indicating that no :class:`Device` was found
from the given kernel device string.
The format of the kernel device string is defined in the
systemd.journal-fields man pages.
"""
class DeviceNotFoundByNameError(DeviceNotFoundError):
"""
A :exc:`DeviceNotFoundError` indicating that no :class:`Device` was
found with a given name.
"""
def __init__(self, subsystem, sys_name):
DeviceNotFoundError.__init__(self, subsystem, sys_name)
@property
def subsystem(self):
"""
The subsystem that caused this error as string.
"""
return self.args[0]
@property
def sys_name(self):
"""
The sys name that caused this error as string.
"""
return self.args[1]
def __str__(self):
return 'No device {0.sys_name!r} in {0.subsystem!r}'.format(self)
class DeviceNotFoundByNumberError(DeviceNotFoundError):
"""
A :exc:`DeviceNotFoundError` indicating, that no :class:`Device` was found
for a given device number.
"""
def __init__(self, typ, number):
DeviceNotFoundError.__init__(self, typ, number)
@property
def device_type(self):
"""
The device type causing this error as string. Either ``'char'`` or
``'block'``.
"""
return self.args[0]
@property
def device_number(self):
"""
The device number causing this error as integer.
"""
return self.args[1]
def __str__(self):
return ('No {0.device_type} device with number '
'{0.device_number}'.format(self))
class DeviceNotFoundInEnvironmentError(DeviceNotFoundError):
"""
A :exc:`DeviceNotFoundError` indicating, that no :class:`Device` could
be constructed from the process environment.
"""
def __str__(self):
return 'No device found in environment'
class DeviceValueError(DeviceError):
"""
Raised when a parameter has an unacceptable value.
May also be raised when the parameter has an unacceptable type.
"""
_FMT_STR = "value '%s' for parameter %s is unacceptable"
def __init__(self, value, param, msg=None):
""" Initializer.
:param object value: the value
:param str param: the parameter
:param str msg: an explanatory message
"""
# pylint: disable=super-init-not-called
self._value = value
self._param = param
self._msg = msg
def __str__(self):
if self._msg:
fmt_str = self._FMT_STR + ": %s"
return fmt_str % (self._value, self._param, self._msg)
else:
return self._FMT_STR % (self._value, self._param)

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern <amulhern@redhat.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._os
==========
Extras to compensate for deficiencies in python os module.
.. moduleauthor:: mulhern <amulhern@redhat.com>
"""
from . import pipe
from . import poll

@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2013 Sebastian Wiesner <lunaryorn@gmail.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._os.pipe
===============
Fallback implementations for pipe.
1. pipe2 from python os module
2. pipe2 from libc
3. pipe from python os module
The Pipe class wraps the chosen implementation.
.. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
import fcntl
from functools import partial
from .._ctypeslib.libc import fd_pair
from .._ctypeslib.libc import ERROR_CHECKERS
from .._ctypeslib.libc import SIGNATURES
from .._ctypeslib.utils import load_ctypes_library
# Define O_CLOEXEC, if not present in os already
O_CLOEXEC = getattr(os, 'O_CLOEXEC', 0o2000000)
def _pipe2_ctypes(libc, flags):
"""A ``pipe2`` implementation using ``pipe2`` from ctypes.
``libc`` is a :class:`ctypes.CDLL` object for libc. ``flags`` is an
integer providing the flags to ``pipe2``.
Return a pair of file descriptors ``(r, w)``.
"""
fds = fd_pair()
libc.pipe2(fds, flags)
return fds[0], fds[1]
def _pipe2_by_pipe(flags):
"""A ``pipe2`` implementation using :func:`os.pipe`.
``flags`` is an integer providing the flags to ``pipe2``.
.. warning::
This implementation is not atomic!
Return a pair of file descriptors ``(r, w)``.
"""
fds = os.pipe()
if flags & os.O_NONBLOCK != 0:
for fd in fds:
set_fd_status_flag(fd, os.O_NONBLOCK)
if flags & O_CLOEXEC != 0:
for fd in fds:
set_fd_flag(fd, O_CLOEXEC)
return fds
def _get_pipe2_implementation():
"""Find the appropriate implementation for ``pipe2``.
Return a function implementing ``pipe2``."""
if hasattr(os, 'pipe2'):
return os.pipe2 # pylint: disable=no-member
else:
try:
libc = load_ctypes_library("libc", SIGNATURES, ERROR_CHECKERS)
return (partial(_pipe2_ctypes, libc)
if hasattr(libc, 'pipe2') else
_pipe2_by_pipe)
except ImportError:
return _pipe2_by_pipe
_PIPE2 = _get_pipe2_implementation()
def set_fd_flag(fd, flag):
"""Set a flag on a file descriptor.
``fd`` is the file descriptor or file object, ``flag`` the flag as integer.
"""
flags = fcntl.fcntl(fd, fcntl.F_GETFD, 0)
fcntl.fcntl(fd, fcntl.F_SETFD, flags | flag)
def set_fd_status_flag(fd, flag):
"""Set a status flag on a file descriptor.
``fd`` is the file descriptor or file object, ``flag`` the flag as integer.
"""
flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
fcntl.fcntl(fd, fcntl.F_SETFL, flags | flag)
class Pipe(object):
"""A unix pipe.
A pipe object provides two file objects: :attr:`source` is a readable file
object, and :attr:`sink` a writeable. Bytes written to :attr:`sink` appear
at :attr:`source`.
Open a pipe with :meth:`open()`.
"""
@classmethod
def open(cls):
"""Open and return a new :class:`Pipe`.
The pipe uses non-blocking IO."""
source, sink = _PIPE2(os.O_NONBLOCK | O_CLOEXEC)
return cls(source, sink)
def __init__(self, source_fd, sink_fd):
"""Create a new pipe object from the given file descriptors.
``source_fd`` is a file descriptor for the readable side of the pipe,
``sink_fd`` is a file descriptor for the writeable side."""
self.source = os.fdopen(source_fd, 'rb', 0)
self.sink = os.fdopen(sink_fd, 'wb', 0)
def close(self):
"""Closes both sides of the pipe."""
try:
self.source.close()
finally:
self.sink.close()

@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2013 Sebastian Wiesner <lunaryorn@gmail.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._os.poll
===============
Operating system interface for pyudev.
.. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import select
from .._util import eintr_retry_call
class Poll(object):
"""A poll object.
This object essentially provides a more convenient interface around
:class:`select.poll`.
"""
_EVENT_TO_MASK = {'r': select.POLLIN,
'w': select.POLLOUT}
@staticmethod
def _has_event(events, event):
return events & event != 0
@classmethod
def for_events(cls, *events):
"""Listen for ``events``.
``events`` is a list of ``(fd, event)`` pairs, where ``fd`` is a file
descriptor or file object and ``event`` either ``'r'`` or ``'w'``. If
``r``, listen for whether that is ready to be read. If ``w``, listen
for whether the channel is ready to be written to.
"""
notifier = eintr_retry_call(select.poll)
for fd, event in events:
mask = cls._EVENT_TO_MASK.get(event)
if not mask:
raise ValueError('Unknown event type: {0!r}'.format(event))
notifier.register(fd, mask)
return cls(notifier)
def __init__(self, notifier):
"""Create a poll object for the given ``notifier``.
``notifier`` is the :class:`select.poll` object wrapped by the new poll
object.
"""
self._notifier = notifier
def poll(self, timeout=None):
"""Poll for events.
``timeout`` is an integer specifying how long to wait for events (in
milliseconds). If omitted, ``None`` or negative, wait until an event
occurs.
Return a list of all events that occurred before ``timeout``, where
each event is a pair ``(fd, event)``. ``fd`` is the integral file
descriptor, and ``event`` a string indicating the event type. If
``'r'``, there is data to read from ``fd``. If ``'w'``, ``fd`` is
writable without blocking now. If ``'h'``, the file descriptor was
hung up (i.e. the remote side of a pipe was closed).
"""
# Return a list to allow clients to determine whether there are any
# events at all with a simple truthiness test.
return list(self._parse_events(eintr_retry_call(self._notifier.poll, timeout)))
def _parse_events(self, events):
"""Parse ``events``.
``events`` is a list of events as returned by
:meth:`select.poll.poll()`.
Yield all parsed events.
"""
for fd, event_mask in events:
if self._has_event(event_mask, select.POLLNVAL):
raise IOError('File descriptor not open: {0!r}'.format(fd))
elif self._has_event(event_mask, select.POLLERR):
raise IOError('Error while polling fd: {0!r}'.format(fd))
if self._has_event(event_mask, select.POLLIN):
yield fd, 'r'
if self._has_event(event_mask, select.POLLOUT):
yield fd, 'w'
if self._has_event(event_mask, select.POLLHUP):
yield fd, 'h'

@ -0,0 +1,207 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner <lunaryorn@gmail.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._qt_base
===============
Base mixin class for Qt4,Qt5 support.
.. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import six
from pyudev.device import Device
class MonitorObserverMixin(object):
"""
Base mixin for pyqt monitor observers.
"""
# pylint: disable=too-few-public-methods
def _setup_notifier(self, monitor, notifier_class):
self.monitor = monitor
self.notifier = notifier_class(
monitor.fileno(), notifier_class.Read, self)
self.notifier.activated[int].connect(self._process_udev_event)
@property
def enabled(self):
"""
Whether this observer is enabled or not.
If ``True`` (the default), this observer is enabled, and emits events.
Otherwise it is disabled and does not emit any events. This merely
reflects the state of the ``enabled`` property of the underlying
:attr:`notifier`.
.. versionadded:: 0.14
"""
return self.notifier.isEnabled()
@enabled.setter
def enabled(self, value):
self.notifier.setEnabled(value)
def _process_udev_event(self):
"""
Attempt to receive a single device event from the monitor, process
the event and emit corresponding signals.
Called by ``QSocketNotifier``, if data is available on the udev
monitoring socket.
"""
device = self.monitor.poll(timeout=0)
if device is not None:
self._emit_event(device)
def _emit_event(self, device):
self.deviceEvent.emit(device)
class QUDevMonitorObserverMixin(MonitorObserverMixin):
"""
Obsolete monitor observer mixin.
"""
# pylint: disable=too-few-public-methods
def _setup_notifier(self, monitor, notifier_class):
MonitorObserverMixin._setup_notifier(self, monitor, notifier_class)
self._action_signal_map = {
'add': self.deviceAdded, 'remove': self.deviceRemoved,
'change': self.deviceChanged, 'move': self.deviceMoved,
}
import warnings
warnings.warn('Will be removed in 1.0. '
'Use pyudev.pyqt4.MonitorObserver instead.',
DeprecationWarning)
def _emit_event(self, device):
self.deviceEvent.emit(device.action, device)
signal = self._action_signal_map.get(device.action)
if signal is not None:
signal.emit(device)
def make_init(qobject, socket_notifier):
"""
Generates an initializer to observer the given ``monitor``
(a :class:`~pyudev.Monitor`):
``parent`` is the parent :class:`~PyQt{4,5}.QtCore.QObject` of this
object. It is passed unchanged to the inherited constructor of
:class:`~PyQt{4,5}.QtCore.QObject`.
"""
def __init__(self, monitor, parent=None):
qobject.__init__(self, parent)
# pylint: disable=protected-access
self._setup_notifier(monitor, socket_notifier)
return __init__
class MonitorObserverGenerator(object):
"""
Class to generate a MonitorObserver class.
"""
# pylint: disable=too-few-public-methods
@staticmethod
def make_monitor_observer(qobject, signal, socket_notifier):
"""Generates an observer for device events integrating into the
PyQt{4,5} mainloop.
This class inherits :class:`~PyQt{4,5}.QtCore.QObject` to turn device
events into Qt signals:
>>> from pyudev import Context, Monitor
>>> from pyudev.pyqt4 import MonitorObserver
>>> context = Context()
>>> monitor = Monitor.from_netlink(context)
>>> monitor.filter_by(subsystem='input')
>>> observer = MonitorObserver(monitor)
>>> def device_event(device):
... print('event {0} on device {1}'.format(device.action, device))
>>> observer.deviceEvent.connect(device_event)
>>> monitor.start()
This class is a child of :class:`~{PySide, PyQt{4,5}}.QtCore.QObject`.
"""
return type(
str("MonitorObserver"),
(qobject, MonitorObserverMixin),
{
str("__init__") : make_init(qobject, socket_notifier),
str("deviceEvent") : signal(Device)
}
)
class QUDevMonitorObserverGenerator(object):
"""
Class to generate a MonitorObserver class.
"""
# pylint: disable=too-few-public-methods
@staticmethod
def make_monitor_observer(qobject, signal, socket_notifier):
"""Generates an observer for device events integrating into the
PyQt{4,5} mainloop.
This class inherits :class:`~PyQt{4,5}.QtCore.QObject` to turn device
events into Qt signals:
>>> from pyudev import Context, Monitor
>>> from pyudev.pyqt4 import MonitorObserver
>>> context = Context()
>>> monitor = Monitor.from_netlink(context)
>>> monitor.filter_by(subsystem='input')
>>> observer = MonitorObserver(monitor)
>>> def device_event(device):
... print('event {0} on device {1}'.format(device.action, device))
>>> observer.deviceEvent.connect(device_event)
>>> monitor.start()
This class is a child of :class:`~{PyQt{4,5}, PySide}.QtCore.QObject`.
"""
return type(
str("QUDevMonitorObserver"),
(qobject, QUDevMonitorObserverMixin),
{
str("__init__") : make_init(qobject, socket_notifier),
#: emitted upon arbitrary device events
str("deviceEvent") : signal(six.text_type, Device),
#: emitted if a device was added
str("deviceAdded") : signal(Device),
#: emitted if a device was removed
str("deviceRemoved") : signal(Device),
#: emitted if a device was changed
str("deviceChanged") : signal(Device),
#: emitted if a device was moved
str("deviceMoved") : signal(Device)
}
)

@ -0,0 +1,202 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012 Sebastian Wiesner <lunaryorn@gmail.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._util
============
Internal utilities
.. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
try:
from subprocess import check_output
except ImportError:
from pyudev._compat import check_output
import os
import sys
import stat
import errno
import six
def ensure_byte_string(value):
"""
Return the given ``value`` as bytestring.
If the given ``value`` is not a byte string, but a real unicode string, it
is encoded with the filesystem encoding (as in
:func:`sys.getfilesystemencoding()`).
"""
if not isinstance(value, bytes):
value = value.encode(sys.getfilesystemencoding())
return value
def ensure_unicode_string(value):
"""
Return the given ``value`` as unicode string.
If the given ``value`` is not a unicode string, but a byte string, it is
decoded with the filesystem encoding (as in
:func:`sys.getfilesystemencoding()`).
"""
if not isinstance(value, six.text_type):
value = value.decode(sys.getfilesystemencoding())
return value
def property_value_to_bytes(value):
"""
Return a byte string, which represents the given ``value`` in a way
suitable as raw value of an udev property.
If ``value`` is a boolean object, it is converted to ``'1'`` or ``'0'``,
depending on whether ``value`` is ``True`` or ``False``. If ``value`` is a
byte string already, it is returned unchanged. Anything else is simply
converted to a unicode string, and then passed to
:func:`ensure_byte_string`.
"""
# udev represents boolean values as 1 or 0, therefore an explicit
# conversion to int is required for boolean values
if isinstance(value, bool):
value = int(value)
if isinstance(value, bytes):
return value
else:
return ensure_byte_string(six.text_type(value))
def string_to_bool(value):
"""
Convert the given unicode string ``value`` to a boolean object.
If ``value`` is ``'1'``, ``True`` is returned. If ``value`` is ``'0'``,
``False`` is returned. Any other value raises a
:exc:`~exceptions.ValueError`.
"""
if value not in ('1', '0'):
raise ValueError('Not a boolean value: {0!r}'.format(value))
return value == '1'
def udev_list_iterate(libudev, entry):
"""
Iteration helper for udev list entry objects.
Yield a tuple ``(name, value)``. ``name`` and ``value`` are bytestrings
containing the name and the value of the list entry. The exact contents
depend on the list iterated over.
"""
while entry:
name = libudev.udev_list_entry_get_name(entry)
value = libudev.udev_list_entry_get_value(entry)
yield (name, value)
entry = libudev.udev_list_entry_get_next(entry)
def get_device_type(filename):
"""
Get the device type of a device file.
``filename`` is a string containing the path of a device file.
Return ``'char'`` if ``filename`` is a character device, or ``'block'`` if
``filename`` is a block device. Raise :exc:`~exceptions.ValueError` if
``filename`` is no device file at all. Raise
:exc:`~exceptions.EnvironmentError` if ``filename`` does not exist or if
its metadata was inaccessible.
.. versionadded:: 0.15
"""
mode = os.stat(filename).st_mode
if stat.S_ISCHR(mode):
return 'char'
elif stat.S_ISBLK(mode):
return 'block'
else:
raise ValueError('not a device file: {0!r}'.format(filename))
def eintr_retry_call(func, *args, **kwargs):
"""
Handle interruptions to an interruptible system call.
Run an interruptible system call in a loop and retry if it raises EINTR.
The signal calls that may raise EINTR prior to Python 3.5 are listed in
PEP 0475. Any calls to these functions must be wrapped in eintr_retry_call
in order to handle EINTR returns in older versions of Python.
This function is safe to use under Python 3.5 and newer since the wrapped
function will simply return without raising EINTR.
This function is based on _eintr_retry_call in python's subprocess.py.
"""
# select.error inherits from Exception instead of OSError in Python 2
import select
while True:
try:
return func(*args, **kwargs)
except (OSError, IOError, select.error) as err:
# If this is not an IOError or OSError, it's the old select.error
# type, which means that the errno is only accessible via subscript
if isinstance(err, (OSError, IOError)):
error_code = err.errno
else:
error_code = err.args[0]
if error_code == errno.EINTR:
continue
raise
def udev_version():
"""
Get the version of the underlying udev library.
udev doesn't use a standard major-minor versioning scheme, but instead
labels releases with a single consecutive number. Consequently, the
version number returned by this function is a single integer, and not a
tuple (like for instance the interpreter version in
:data:`sys.version_info`).
As libudev itself does not provide a function to query the version number,
this function calls the ``udevadm`` utility, so be prepared to catch
:exc:`~exceptions.EnvironmentError` and
:exc:`~subprocess.CalledProcessError` if you call this function.
Return the version number as single integer. Raise
:exc:`~exceptions.ValueError`, if the version number retrieved from udev
could not be converted to an integer. Raise
:exc:`~exceptions.EnvironmentError`, if ``udevadm`` was not found, or could
not be executed. Raise :exc:`subprocess.CalledProcessError`, if
``udevadm`` returned a non-zero exit code. On Python 2.7 or newer, the
``output`` attribute of this exception is correctly set.
.. versionadded:: 0.8
"""
output = ensure_unicode_string(check_output(['udevadm', '--version']))
return int(output.strip())

@ -0,0 +1,394 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011 Sebastian Wiesner <lunaryorn@gmail.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.core
===========
Core types and functions of :mod:`pyudev`.
.. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
from .device import Devices
from ._errors import DeviceNotFoundAtPathError
from ._ctypeslib.libudev import ERROR_CHECKERS
from ._ctypeslib.libudev import SIGNATURES
from ._ctypeslib.utils import load_ctypes_library
from ._util import ensure_byte_string
from ._util import ensure_unicode_string
from ._util import property_value_to_bytes
from ._util import udev_list_iterate
class Context(object):
"""
A device database connection.
This class represents a connection to the udev device database, and is
really *the* central object to access udev. You need an instance of this
class for almost anything else in pyudev.
This class itself gives access to various udev configuration data (e.g.
:attr:`sys_path`, :attr:`device_path`), and provides device enumeration
(:meth:`list_devices()`).
Instances of this class can directly be given as ``udev *`` to functions
wrapped through :mod:`ctypes`.
"""
def __init__(self):
"""
Create a new context.
"""
self._libudev = load_ctypes_library('udev', SIGNATURES, ERROR_CHECKERS)
self._as_parameter_ = self._libudev.udev_new()
def __del__(self):
self._libudev.udev_unref(self)
@property
def sys_path(self):
"""
The ``sysfs`` mount point defaulting to ``/sys'`` as unicode string.
"""
if hasattr(self._libudev, 'udev_get_sys_path'):
return ensure_unicode_string(self._libudev.udev_get_sys_path(self))
else:
# Fixed path since udev 183
return '/sys'
@property
def device_path(self):
"""
The device directory path defaulting to ``/dev`` as unicode string.
"""
if hasattr(self._libudev, 'udev_get_dev_path'):
return ensure_unicode_string(self._libudev.udev_get_dev_path(self))
else:
# Fixed path since udev 183
return '/dev'
@property
def run_path(self):
"""
The run runtime directory path defaulting to ``/run`` as unicode
string.
.. udevversion:: 167
.. versionadded:: 0.10
"""
if hasattr(self._libudev, 'udev_get_run_path'):
return ensure_unicode_string(self._libudev.udev_get_run_path(self))
else:
return '/run/udev'
@property
def log_priority(self):
"""
The logging priority of the interal logging facitility of udev as
integer with a standard :mod:`syslog` priority. Assign to this
property to change the logging priority.
UDev uses the standard :mod:`syslog` priorities. Constants for these
priorities are defined in the :mod:`syslog` module in the standard
library:
>>> import syslog
>>> context = pyudev.Context()
>>> context.log_priority = syslog.LOG_DEBUG
.. versionadded:: 0.9
"""
return self._libudev.udev_get_log_priority(self)
@log_priority.setter
def log_priority(self, value):
"""
Set the log priority.
:param int value: the log priority.
"""
self._libudev.udev_set_log_priority(self, value)
def list_devices(self, **kwargs):
"""
List all available devices.
The arguments of this method are the same as for
:meth:`Enumerator.match()`. In fact, the arguments are simply passed
straight to method :meth:`~Enumerator.match()`.
This function creates and returns an :class:`Enumerator` object,
that can be used to filter the list of devices, and eventually
retrieve :class:`Device` objects representing matching devices.
.. versionchanged:: 0.8
Accept keyword arguments now for easy matching.
"""
return Enumerator(self).match(**kwargs)
class Enumerator(object):
"""
A filtered iterable of devices.
To retrieve devices, simply iterate over an instance of this class.
This operation yields :class:`Device` objects representing the available
devices.
Before iteration the device list can be filtered by subsystem or by
property values using :meth:`match_subsystem` and
:meth:`match_property`. Multiple subsystem (property) filters are
combined using a logical OR, filters of different types are combined
using a logical AND. The following filter for instance::
devices.match_subsystem('block').match_property(
'ID_TYPE', 'disk').match_property('DEVTYPE', 'disk')
means the following::
subsystem == 'block' and (ID_TYPE == 'disk' or DEVTYPE == 'disk')
Once added, a filter cannot be removed anymore. Create a new object
instead.
Instances of this class can directly be given as given ``udev_enumerate *``
to functions wrapped through :mod:`ctypes`.
"""
def __init__(self, context):
"""
Create a new enumerator with the given ``context`` (a
:class:`Context` instance).
While you can create objects of this class directly, this is not
recommended. Call :method:`Context.list_devices()` instead.
"""
if not isinstance(context, Context):
raise TypeError('Invalid context object')
self.context = context
self._as_parameter_ = context._libudev.udev_enumerate_new(context)
self._libudev = context._libudev
def __del__(self):
self._libudev.udev_enumerate_unref(self)
def match(self, **kwargs):
"""
Include devices according to the rules defined by the keyword
arguments. These keyword arguments are interpreted as follows:
- The value for the keyword argument ``subsystem`` is forwarded to
:meth:`match_subsystem()`.
- The value for the keyword argument ``sys_name`` is forwared to
:meth:`match_sys_name()`.
- The value for the keyword argument ``tag`` is forwared to
:meth:`match_tag()`.
- The value for the keyword argument ``parent`` is forwared to
:meth:`match_parent()`.
- All other keyword arguments are forwareded one by one to
:meth:`match_property()`. The keyword argument itself is interpreted
as property name, the value of the keyword argument as the property
value.
All keyword arguments are optional, calling this method without no
arguments at all is simply a noop.
Return the instance again.
.. versionadded:: 0.8
.. versionchanged:: 0.13
Add ``parent`` keyword.
"""
subsystem = kwargs.pop('subsystem', None)
if subsystem is not None:
self.match_subsystem(subsystem)
sys_name = kwargs.pop('sys_name', None)
if sys_name is not None:
self.match_sys_name(sys_name)
tag = kwargs.pop('tag', None)
if tag is not None:
self.match_tag(tag)
parent = kwargs.pop('parent', None)
if parent is not None:
self.match_parent(parent)
for prop, value in kwargs.items():
self.match_property(prop, value)
return self
def match_subsystem(self, subsystem, nomatch=False):
"""
Include all devices, which are part of the given ``subsystem``.
``subsystem`` is either a unicode string or a byte string, containing
the name of the subsystem. If ``nomatch`` is ``True`` (default is
``False``), the match is inverted: A device is only included if it is
*not* part of the given ``subsystem``.
Note that, if a device has no subsystem, it is not included either
with value of ``nomatch`` True or with value of ``nomatch`` False.
Return the instance again.
"""
match = self._libudev.udev_enumerate_add_nomatch_subsystem \
if nomatch else self._libudev.udev_enumerate_add_match_subsystem
match(self, ensure_byte_string(subsystem))
return self
def match_sys_name(self, sys_name):
"""
Include all devices with the given name.
``sys_name`` is a byte or unicode string containing the device name.
Return the instance again.
.. versionadded:: 0.8
"""
self._libudev.udev_enumerate_add_match_sysname(
self, ensure_byte_string(sys_name))
return self
def match_property(self, prop, value):
"""
Include all devices, whose ``prop`` has the given ``value``.
``prop`` is either a unicode string or a byte string, containing
the name of the property to match. ``value`` is a property value,
being one of the following types:
- :func:`int`
- :func:`bool`
- A byte string
- Anything convertable to a unicode string (including a unicode string
itself)
Return the instance again.
"""
self._libudev.udev_enumerate_add_match_property(
self, ensure_byte_string(prop), property_value_to_bytes(value))
return self
def match_attribute(self, attribute, value, nomatch=False):
"""
Include all devices, whose ``attribute`` has the given ``value``.
``attribute`` is either a unicode string or a byte string, containing
the name of a sys attribute to match. ``value`` is an attribute value,
being one of the following types:
- :func:`int`,
- :func:`bool`
- A byte string
- Anything convertable to a unicode string (including a unicode string
itself)
If ``nomatch`` is ``True`` (default is ``False``), the match is
inverted: A device is include if the ``attribute`` does *not* match
the given ``value``.
.. note::
If ``nomatch`` is ``True``, devices which do not have the given
``attribute`` at all are also included. In other words, with
``nomatch=True`` the given ``attribute`` is *not* guaranteed to
exist on all returned devices.
Return the instance again.
"""
match = (self._libudev.udev_enumerate_add_match_sysattr
if not nomatch else
self._libudev.udev_enumerate_add_nomatch_sysattr)
match(self, ensure_byte_string(attribute),
property_value_to_bytes(value))
return self
def match_tag(self, tag):
"""
Include all devices, which have the given ``tag`` attached.
``tag`` is a byte or unicode string containing the tag name.
Return the instance again.
.. udevversion:: 154
.. versionadded:: 0.6
"""
self._libudev.udev_enumerate_add_match_tag(self, ensure_byte_string(tag))
return self
def match_is_initialized(self):
"""
Include only devices, which are initialized.
Initialized devices have properly set device node permissions and
context, and are (in case of network devices) fully renamed.
Currently this will not affect devices which do not have device nodes
and are not network interfaces.
Return the instance again.
.. seealso:: :attr:`Device.is_initialized`
.. udevversion:: 165
.. versionadded:: 0.8
"""
self._libudev.udev_enumerate_add_match_is_initialized(self)
return self
def match_parent(self, parent):
"""
Include all devices on the subtree of the given ``parent`` device.
The ``parent`` device itself is also included.
``parent`` is a :class:`~pyudev.Device`.
Return the instance again.
.. udevversion:: 172
.. versionadded:: 0.13
"""
self._libudev.udev_enumerate_add_match_parent(self, parent)
return self
def __iter__(self):
"""
Iterate over all matching devices.
Yield :class:`Device` objects.
"""
self._libudev.udev_enumerate_scan_devices(self)
entry = self._libudev.udev_enumerate_get_list_entry(self)
for name, _ in udev_list_iterate(self._libudev, entry):
try:
yield Devices.from_sys_path(self.context, name)
except DeviceNotFoundAtPathError:
continue

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern <amulhern@redhat.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.device
=============
Device class implementation of :mod:`pyudev`.
.. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
"""
from ._device import Attributes
from ._device import Device
from ._device import Devices
from ._device import Tags

File diff suppressed because it is too large Load Diff

@ -0,0 +1,391 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern <amulhern@redhat.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.discover
===============
Tools to discover a device given limited information.
.. moduleauthor:: mulhern <amulhern@redhat.com>
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import abc
import functools
import os
import re
import six
from ._errors import DeviceNotFoundError
from .device import Devices
def wrap_exception(func):
"""
Allow Device discovery methods to return None instead of raising an
exception.
"""
@functools.wraps(func)
def the_func(*args, **kwargs):
"""
Returns result of calling ``func`` on ``args``, ``kwargs``.
Returns None if ``func`` raises :exc:`DeviceNotFoundError`.
"""
try:
return func(*args, **kwargs)
except DeviceNotFoundError:
return None
return the_func
@six.add_metaclass(abc.ABCMeta)
class Hypothesis(object):
"""
Represents a hypothesis about the meaning of the device identifier.
"""
@classmethod
@abc.abstractmethod
def match(cls, value): # pragma: no cover
"""
Match the given string according to the hypothesis.
The purpose of this method is to obtain a value corresponding to
``value`` if that is possible. It may use a regular expression, but
in general it should just return ``value`` and let the lookup method
sort out the rest.
:param str value: the string to inspect
:returns: the matched thing or None if unmatched
:rtype: the type of lookup's key parameter or NoneType
"""
raise NotImplementedError()
@classmethod
@abc.abstractmethod
def lookup(cls, context, key): # pragma: no cover
"""
Lookup the given string according to the hypothesis.
:param Context context: the pyudev context
:param key: a key with which to lookup the device
:type key: the type of match's return value if not None
:returns: a list of Devices obtained
:rtype: frozenset of :class:`Device`
"""
raise NotImplementedError()
@classmethod
def setup(cls, context):
"""
A potentially expensive method that may allow an :class:`Hypothesis`
to find devices more rapidly or to find a device that it would
otherwise miss.
:param Context context: the pyudev context
"""
pass
@classmethod
def get_devices(cls, context, value):
"""
Get any devices that may correspond to the given string.
:param Context context: the pyudev context
:param str value: the value to look for
:returns: a list of devices obtained
:rtype: set of :class:`Device`
"""
key = cls.match(value)
return cls.lookup(context, key) if key is not None else frozenset()
class DeviceNumberHypothesis(Hypothesis):
"""
Represents the hypothesis that the device is a device number.
The device may be separated into major/minor number or a composite number.
"""
@classmethod
def _match_major_minor(cls, value):
"""
Match the number under the assumption that it is a major,minor pair.
:param str value: value to match
:returns: the device number or None
:rtype: int or NoneType
"""
major_minor_re = re.compile(
r'^(?P<major>\d+)(\D+)(?P<minor>\d+)$'
)
match = major_minor_re.match(value)
return match and os.makedev(
int(match.group('major')),
int(match.group('minor'))
)
@classmethod
def _match_number(cls, value):
"""
Match the number under the assumption that it is a single number.
:param str value: value to match
:returns: the device number or None
:rtype: int or NoneType
"""
number_re = re.compile(r'^(?P<number>\d+)$')
match = number_re.match(value)
return match and int(match.group('number'))
@classmethod
def match(cls, value):
"""
Match the number under the assumption that it is a device number.
:returns: the device number or None
:rtype: int or NoneType
"""
return cls._match_major_minor(value) or cls._match_number(value)
@classmethod
def find_subsystems(cls, context):
"""
Find subsystems in /sys/dev.
:param Context context: the context
:returns: a lis of available subsystems
:rtype: list of str
"""
sys_path = context.sys_path
return os.listdir(os.path.join(sys_path, 'dev'))
@classmethod
def lookup(cls, context, key):
"""
Lookup by the device number.
:param Context context: the context
:param int key: the device number
:returns: a list of matching devices
:rtype: frozenset of :class:`Device`
"""
func = wrap_exception(Devices.from_device_number)
res = (func(context, s, key) for s in cls.find_subsystems(context))
return frozenset(r for r in res if r is not None)
class DevicePathHypothesis(Hypothesis):
"""
Discover the device assuming the identifier is a device path.
"""
@classmethod
def match(cls, value):
"""
Match ``value`` under the assumption that it is a device path.
:returns: the device path or None
:rtype: str or NoneType
"""
return value
@classmethod
def lookup(cls, context, key):
"""
Lookup by the path.
:param Context context: the context
:param str key: the device path
:returns: a list of matching devices
:rtype: frozenset of :class:`Device`
"""
res = wrap_exception(Devices.from_path)(context, key)
return frozenset((res,)) if res is not None else frozenset()
class DeviceNameHypothesis(Hypothesis):
"""
Discover the device assuming the input is a device name.
Try every available subsystem.
"""
@classmethod
def find_subsystems(cls, context):
"""
Find all subsystems in sysfs.
:param Context context: the context
:rtype: frozenset
:returns: subsystems in sysfs
"""
sys_path = context.sys_path
dirnames = ('bus', 'class', 'subsystem')
absnames = (os.path.join(sys_path, name) for name in dirnames)
realnames = (d for d in absnames if os.path.isdir(d))
return frozenset(n for d in realnames for n in os.listdir(d))
@classmethod
def match(cls, value):
"""
Match ``value`` under the assumption that it is a device name.
:returns: the device path or None
:rtype: str or NoneType
"""
return value
@classmethod
def lookup(cls, context, key):
"""
Lookup by the path.
:param Context context: the context
:param str key: the device path
:returns: a list of matching devices
:rtype: frozenset of :class:`Device`
"""
func = wrap_exception(Devices.from_name)
res = (func(context, s, key) for s in cls.find_subsystems(context))
return frozenset(r for r in res if r is not None)
class DeviceFileHypothesis(Hypothesis):
"""
Discover the device assuming the value is some portion of a device file.
The device file may be a link to a device node.
"""
_LINK_DIRS = [
'/dev',
'/dev/disk/by-id',
'/dev/disk/by-label',
'/dev/disk/by-partlabel',
'/dev/disk/by-partuuid',
'/dev/disk/by-path',
'/dev/disk/by-uuid',
'/dev/input/by-path',
'/dev/mapper',
'/dev/md',
'/dev/vg'
]
@classmethod
def get_link_dirs(cls, context):
"""
Get all directories that may contain links to device nodes.
This method checks the device links of every device, so it is very
expensive.
:param Context context: the context
:returns: a sorted list of directories that contain device links
:rtype: list
"""
devices = context.list_devices()
devices_with_links = (d for d in devices if list(d.device_links))
links = (l for d in devices_with_links for l in d.device_links)
return sorted(set(os.path.dirname(l) for l in links))
@classmethod
def setup(cls, context):
"""
Set the link directories to be used when discovering by file.
Uses `get_link_dirs`, so is as expensive as it is.
:param Context context: the context
"""
cls._LINK_DIRS = cls.get_link_dirs(context)
@classmethod
def match(cls, value):
return value
@classmethod
def lookup(cls, context, key):
"""
Lookup the device under the assumption that the key is part of
the name of a device file.
:param Context context: the context
:param str key: a portion of the device file name
It is assumed that either it is the whole name of the device file
or it is the basename.
A device file may be a device node or a device link.
"""
func = wrap_exception(Devices.from_device_file)
if '/' in key:
device = func(context, key)
return frozenset((device,)) if device is not None else frozenset()
else:
files = (os.path.join(ld, key) for ld in cls._LINK_DIRS)
devices = (func(context, f) for f in files)
return frozenset(d for d in devices if d is not None)
class Discovery(object):
# pylint: disable=too-few-public-methods
"""
Provides discovery methods for devices.
"""
_HYPOTHESES = [
DeviceFileHypothesis,
DeviceNameHypothesis,
DeviceNumberHypothesis,
DevicePathHypothesis
]
def __init__(self):
self._hypotheses = self._HYPOTHESES
def setup(self, context):
"""
Set up individual hypotheses.
May be an expensive call.
:param Context context: the context
"""
for hyp in self._hypotheses:
hyp.setup(context)
def get_devices(self, context, value):
"""
Get the devices corresponding to value.
:param Context context: the context
:param str value: some identifier of the device
:returns: a list of corresponding devices
:rtype: frozenset of :class:`Device`
"""
return frozenset(
d for h in self._hypotheses for d in h.get_devices(context, value)
)

@ -0,0 +1,169 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner <lunaryorn@gmail.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""pyudev.glib
===========
Glib integration.
:class:`MonitorObserver` integrates device monitoring into the Glib
mainloop by turing device events into Glib signals.
:mod:`glib` and :mod:`gobject` from PyGObject_ must be available when
importing this module. PyGtk is not required.
.. _PyGObject: http://www.pygtk.org/
.. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
.. versionadded:: 0.7
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
# thanks to absolute imports, this really imports the glib binding and not this
# module again
import glib
import gobject
class _ObserverMixin(object):
"""Mixin to provide observer behavior to the old and the new API."""
# pylint: disable=too-few-public-methods
def _setup_observer(self, monitor):
# pylint: disable=attribute-defined-outside-init
self.monitor = monitor
self.event_source = None
self.enabled = True
@property
def enabled(self):
"""
Whether this observer is enabled or not.
If ``True`` (the default), this observer is enabled, and emits events.
Otherwise it is disabled and does not emit any events.
.. versionadded:: 0.14
"""
return self.event_source is not None
@enabled.setter
def enabled(self, value):
if value and self.event_source is None:
# pylint: disable=attribute-defined-outside-init
self.event_source = glib.io_add_watch(
self.monitor, glib.IO_IN, self._process_udev_event)
elif not value and self.event_source is not None:
glib.source_remove(self.event_source)
def _process_udev_event(self, source, condition):
# pylint: disable=unused-argument
if condition == glib.IO_IN:
device = self.monitor.poll(timeout=0)
if device is not None:
self._emit_event(device)
return True
def _emit_event(self, device):
self.emit('device-event', device)
class MonitorObserver(gobject.GObject, _ObserverMixin):
"""
An observer for device events integrating into the :mod:`glib` mainloop.
This class inherits :class:`~gobject.GObject` to turn device events into
glib signals.
>>> from pyudev import Context, Monitor
>>> from pyudev.glib import MonitorObserver
>>> context = Context()
>>> monitor = Monitor.from_netlink(context)
>>> monitor.filter_by(subsystem='input')
>>> observer = MonitorObserver(monitor)
>>> def device_event(observer, device):
... print('event {0} on device {1}'.format(device.action, device))
>>> observer.connect('device-event', device_event)
>>> monitor.start()
This class is a child of :class:`gobject.GObject`.
"""
__gsignals__ = {
# explicitly convert the signal to str, because glib expects the
# *native* string type of the corresponding python version as type of
# signal name, and str() is the name of the native string type of both
# python versions. We could also remove the "unicode_literals" import,
# but I don't want to make exceptions to the standard set of future
# imports used throughout pyudev for the sake of consistency.
str('device-event'): (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,)),
}
def __init__(self, monitor):
gobject.GObject.__init__(self)
self._setup_observer(monitor)
gobject.type_register(MonitorObserver)
class GUDevMonitorObserver(gobject.GObject, _ObserverMixin):
"""
An observer for device events integrating into the :mod:`glib` mainloop.
.. deprecated:: 0.17
Will be removed in 1.0. Use :class:`MonitorObserver` instead.
"""
_action_signal_map = {
'add': 'device-added', 'remove': 'device-removed',
'change': 'device-changed', 'move': 'device-moved'}
__gsignals__ = {
str('device-event'): (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)),
str('device-added'): (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,)),
str('device-removed'): (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,)),
str('device-changed'): (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,)),
str('device-moved'): (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,)),
}
def __init__(self, monitor):
gobject.GObject.__init__(self)
self._setup_observer(monitor)
import warnings
warnings.warn('Will be removed in 1.0. '
'Use pyudev.glib.MonitorObserver instead.',
DeprecationWarning)
def _emit_event(self, device):
self.emit('device-event', device.action, device)
signal = self._action_signal_map.get(device.action)
if signal is not None:
self.emit(signal, device)
gobject.type_register(GUDevMonitorObserver)

@ -0,0 +1,582 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner <lunaryorn@gmail.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.monitor
==============
Monitor implementation.
.. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
import os
import errno
from threading import Thread
from functools import partial
from .device import Device
from ._util import eintr_retry_call
from ._util import ensure_byte_string
from ._os import pipe
from ._os import poll
class Monitor(object):
"""
A synchronous device event monitor.
A :class:`Monitor` objects connects to the udev daemon and listens for
changes to the device list. A monitor is created by connecting to the
kernel daemon through netlink (see :meth:`from_netlink`):
>>> from pyudev import Context, Monitor
>>> context = Context()
>>> monitor = Monitor.from_netlink(context)
Once the monitor is created, you can add a filter using :meth:`filter_by()`
or :meth:`filter_by_tag()` to drop incoming events in subsystems, which are
not of interest to the application:
>>> monitor.filter_by('input')
When the monitor is eventually set up, you can either poll for events
synchronously:
>>> device = monitor.poll(timeout=3)
>>> if device:
... print('{0.action}: {0}'.format(device))
...
Or you can monitor events asynchronously with :class:`MonitorObserver`.
To integrate into various event processing frameworks, the monitor provides
a :func:`selectable <select.select>` file description by :meth:`fileno()`.
However, do *not* read or write directly on this file descriptor.
Instances of this class can directly be given as ``udev_monitor *`` to
functions wrapped through :mod:`ctypes`.
.. versionchanged:: 0.16
Remove :meth:`from_socket()` which is deprecated, and even removed in
recent udev versions.
"""
def __init__(self, context, monitor_p):
self.context = context
self._as_parameter_ = monitor_p
self._libudev = context._libudev
self._started = False
def __del__(self):
self._libudev.udev_monitor_unref(self)
@classmethod
def from_netlink(cls, context, source='udev'):
"""
Create a monitor by connecting to the kernel daemon through netlink.
``context`` is the :class:`Context` to use. ``source`` is a string,
describing the event source. Two sources are available:
``'udev'`` (the default)
Events emitted after udev as registered and configured the device.
This is the absolutely recommended source for applications.
``'kernel'``
Events emitted directly after the kernel has seen the device. The
device has not yet been configured by udev and might not be usable
at all. **Never** use this, unless you know what you are doing.
Return a new :class:`Monitor` object, which is connected to the
given source. Raise :exc:`~exceptions.ValueError`, if an invalid
source has been specified. Raise
:exc:`~exceptions.EnvironmentError`, if the creation of the monitor
failed.
"""
if source not in ('kernel', 'udev'):
raise ValueError('Invalid source: {0!r}. Must be one of "udev" '
'or "kernel"'.format(source))
monitor = context._libudev.udev_monitor_new_from_netlink(
context, ensure_byte_string(source))
if not monitor:
raise EnvironmentError('Could not create udev monitor')
return cls(context, monitor)
@property
def started(self):
"""
``True``, if this monitor was started, ``False`` otherwise. Readonly.
.. seealso:: :meth:`start()`
.. versionadded:: 0.16
"""
return self._started
def fileno(self):
# pylint: disable=anomalous-backslash-in-string
"""
Return the file description associated with this monitor as integer.
This is really a real file descriptor ;), which can be watched and
:func:`select.select`\ ed.
"""
return self._libudev.udev_monitor_get_fd(self)
def filter_by(self, subsystem, device_type=None):
"""
Filter incoming events.
``subsystem`` is a byte or unicode string with the name of a
subsystem (e.g. ``'input'``). Only events originating from the
given subsystem pass the filter and are handed to the caller.
If given, ``device_type`` is a byte or unicode string specifying the
device type. Only devices with the given device type are propagated
to the caller. If ``device_type`` is not given, no additional
filter for a specific device type is installed.
These filters are executed inside the kernel, and client processes
will usually not be woken up for device, that do not match these
filters.
.. versionchanged:: 0.15
This method can also be after :meth:`start()` now.
"""
subsystem = ensure_byte_string(subsystem)
if device_type is not None:
device_type = ensure_byte_string(device_type)
self._libudev.udev_monitor_filter_add_match_subsystem_devtype(
self, subsystem, device_type)
self._libudev.udev_monitor_filter_update(self)
def filter_by_tag(self, tag):
"""
Filter incoming events by the given ``tag``.
``tag`` is a byte or unicode string with the name of a tag. Only
events for devices which have this tag attached pass the filter and are
handed to the caller.
Like with :meth:`filter_by` this filter is also executed inside the
kernel, so that client processes are usually not woken up for devices
without the given ``tag``.
.. udevversion:: 154
.. versionadded:: 0.9
.. versionchanged:: 0.15
This method can also be after :meth:`start()` now.
"""
self._libudev.udev_monitor_filter_add_match_tag(
self, ensure_byte_string(tag))
self._libudev.udev_monitor_filter_update(self)
def remove_filter(self):
"""
Remove any filters installed with :meth:`filter_by()` or
:meth:`filter_by_tag()` from this monitor.
.. warning::
Up to udev 181 (and possibly even later versions) the underlying
``udev_monitor_filter_remove()`` seems to be broken. If used with
affected versions this method always raises
:exc:`~exceptions.ValueError`.
Raise :exc:`~exceptions.EnvironmentError` if removal of installed
filters failed.
.. versionadded:: 0.15
"""
self._libudev.udev_monitor_filter_remove(self)
self._libudev.udev_monitor_filter_update(self)
def enable_receiving(self):
"""
Switch the monitor into listing mode.
Connect to the event source and receive incoming events. Only after
calling this method, the monitor listens for incoming events.
.. note::
This method is implicitly called by :meth:`__iter__`. You don't
need to call it explicitly, if you are iterating over the
monitor.
.. deprecated:: 0.16
Will be removed in 1.0. Use :meth:`start()` instead.
"""
import warnings
warnings.warn('Will be removed in 1.0. Use Monitor.start() instead.',
DeprecationWarning)
self.start()
def start(self):
"""
Start this monitor.
The monitor will not receive events until this method is called. This
method does nothing if called on an already started :class:`Monitor`.
.. note::
Typically you don't need to call this method. It is implicitly
called by :meth:`poll()` and :meth:`__iter__()`.
.. seealso:: :attr:`started`
.. versionchanged:: 0.16
This method does nothing if the :class:`Monitor` was already
started.
"""
if not self._started:
self._libudev.udev_monitor_enable_receiving(self)
# Force monitor FD into non-blocking mode
pipe.set_fd_status_flag(self, os.O_NONBLOCK)
self._started = True
def set_receive_buffer_size(self, size):
"""
Set the receive buffer ``size``.
``size`` is the requested buffer size in bytes, as integer.
.. note::
The CAP_NET_ADMIN capability must be contained in the effective
capability set of the caller for this method to succeed. Otherwise
:exc:`~exceptions.EnvironmentError` will be raised, with ``errno``
set to :data:`~errno.EPERM`. Unprivileged processes typically lack
this capability. You can check the capabilities of the current
process with the python-prctl_ module:
>>> import prctl
>>> prctl.cap_effective.net_admin
Raise :exc:`~exceptions.EnvironmentError`, if the buffer size could not
bet set.
.. versionadded:: 0.13
.. _python-prctl: http://packages.python.org/python-prctl
"""
self._libudev.udev_monitor_set_receive_buffer_size(self, size)
def _receive_device(self):
"""Receive a single device from the monitor.
Return the received :class:`Device`, or ``None`` if no device could be
received.
"""
while True:
try:
device_p = self._libudev.udev_monitor_receive_device(self)
return Device(self.context, device_p) if device_p else None
except EnvironmentError as error:
if error.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
# No data available
return None
elif error.errno == errno.EINTR:
# Try again if our system call was interrupted
continue
else:
raise
def poll(self, timeout=None):
"""
Poll for a device event.
You can use this method together with :func:`iter()` to synchronously
monitor events in the current thread::
for device in iter(monitor.poll, None):
print('{0.action} on {0.device_path}'.format(device))
Since this method will never return ``None`` if no ``timeout`` is
specified, this is effectively an endless loop. With
:func:`functools.partial()` you can also create a loop that only waits
for a specified time::
for device in iter(partial(monitor.poll, 3), None):
print('{0.action} on {0.device_path}'.format(device))
This loop will only wait three seconds for a new device event. If no
device event occurred after three seconds, the loop will exit.
``timeout`` is a floating point number that specifies a time-out in
seconds. If omitted or ``None``, this method blocks until a device
event is available. If ``0``, this method just polls and will never
block.
.. note::
This method implicitly calls :meth:`start()`.
Return the received :class:`Device`, or ``None`` if a timeout
occurred. Raise :exc:`~exceptions.EnvironmentError` if event retrieval
failed.
.. seealso::
:attr:`Device.action`
The action that created this event.
:attr:`Device.sequence_number`
The sequence number of this event.
.. versionadded:: 0.16
"""
if timeout is not None and timeout > 0:
# .poll() takes timeout in milliseconds
timeout = int(timeout * 1000)
self.start()
if eintr_retry_call(poll.Poll.for_events((self, 'r')).poll, timeout):
return self._receive_device()
else:
return None
def receive_device(self):
"""
Receive a single device from the monitor.
.. warning::
You *must* call :meth:`start()` before calling this method.
The caller must make sure, that there are events available in the
event queue. The call blocks, until a device is available.
If a device was available, return ``(action, device)``. ``device``
is the :class:`Device` object describing the device. ``action`` is
a string describing the action. Usual actions are:
``'add'``
A device has been added (e.g. a USB device was plugged in)
``'remove'``
A device has been removed (e.g. a USB device was unplugged)
``'change'``
Something about the device changed (e.g. a device property)
``'online'``
The device is online now
``'offline'``
The device is offline now
Raise :exc:`~exceptions.EnvironmentError`, if no device could be
read.
.. deprecated:: 0.16
Will be removed in 1.0. Use :meth:`Monitor.poll()` instead.
"""
import warnings
warnings.warn('Will be removed in 1.0. Use Monitor.poll() instead.',
DeprecationWarning)
device = self.poll()
return device.action, device
def __iter__(self):
"""
Wait for incoming events and receive them upon arrival.
This methods implicitly calls :meth:`start()`, and starts polling the
:meth:`fileno` of this monitor. If a event comes in, it receives the
corresponding device and yields it to the caller.
The returned iterator is endless, and continues receiving devices
without ever stopping.
Yields ``(action, device)`` (see :meth:`receive_device` for a
description).
.. deprecated:: 0.16
Will be removed in 1.0. Use an explicit loop over :meth:`poll()`
instead, or monitor asynchronously with :class:`MonitorObserver`.
"""
import warnings
warnings.warn('Will be removed in 1.0. Use an explicit loop over '
'"poll()" instead, or monitor asynchronously with '
'"MonitorObserver".', DeprecationWarning)
self.start()
while True:
device = self.poll()
if device is not None:
yield device.action, device
class MonitorObserver(Thread):
"""
An asynchronous observer for device events.
This class subclasses :class:`~threading.Thread` class to asynchronously
observe a :class:`Monitor` in a background thread:
>>> from pyudev import Context, Monitor, MonitorObserver
>>> context = Context()
>>> monitor = Monitor.from_netlink(context)
>>> monitor.filter_by(subsystem='input')
>>> def print_device_event(device):
... print('background event {0.action}: {0.device_path}'.format(device))
>>> observer = MonitorObserver(monitor, callback=print_device_event, name='monitor-observer')
>>> observer.daemon
True
>>> observer.start()
In the above example, input device events will be printed in background,
until :meth:`stop()` is called on ``observer``.
.. note::
Instances of this class are always created as daemon thread. If you do
not want to use daemon threads for monitoring, you need explicitly set
:attr:`~threading.Thread.daemon` to ``False`` before invoking
:meth:`~threading.Thread.start()`.
.. seealso::
:attr:`Device.action`
The action that created this event.
:attr:`Device.sequence_number`
The sequence number of this event.
.. versionadded:: 0.14
.. versionchanged:: 0.15
:meth:`Monitor.start()` is implicitly called when the thread is started.
"""
def __init__(self, monitor, event_handler=None, callback=None, *args,
**kwargs):
"""
Create a new observer for the given ``monitor``.
``monitor`` is the :class:`Monitor` to observe. ``callback`` is the
callable to invoke on events, with the signature ``callback(device)``
where ``device`` is the :class:`Device` that caused the event.
.. warning::
``callback`` is invoked in the observer thread, hence the observer
is blocked while callback executes.
``args`` and ``kwargs`` are passed unchanged to the constructor of
:class:`~threading.Thread`.
.. deprecated:: 0.16
The ``event_handler`` argument will be removed in 1.0. Use
the ``callback`` argument instead.
.. versionchanged:: 0.16
Add ``callback`` argument.
"""
if callback is None and event_handler is None:
raise ValueError('callback missing')
elif callback is not None and event_handler is not None:
raise ValueError('Use either callback or event handler')
Thread.__init__(self, *args, **kwargs)
self.monitor = monitor
# observer threads should not keep the interpreter alive
self.daemon = True
self._stop_event = None
if event_handler is not None:
import warnings
warnings.warn('"event_handler" argument will be removed in 1.0. '
'Use Monitor.poll() instead.', DeprecationWarning)
callback = lambda d: event_handler(d.action, d)
self._callback = callback
def start(self):
"""Start the observer thread."""
if not self.is_alive():
self._stop_event = pipe.Pipe.open()
Thread.start(self)
def run(self):
self.monitor.start()
notifier = poll.Poll.for_events(
(self.monitor, 'r'), (self._stop_event.source, 'r'))
while True:
for file_descriptor, event in eintr_retry_call(notifier.poll):
if file_descriptor == self._stop_event.source.fileno():
# in case of a stop event, close our pipe side, and
# return from the thread
self._stop_event.source.close()
return
elif file_descriptor == self.monitor.fileno() and event == 'r':
read_device = partial(eintr_retry_call, self.monitor.poll, timeout=0)
for device in iter(read_device, None):
self._callback(device)
else:
raise EnvironmentError('Observed monitor hung up')
def send_stop(self):
"""
Send a stop signal to the background thread.
The background thread will eventually exit, but it may still be running
when this method returns. This method is essentially the asynchronous
equivalent to :meth:`stop()`.
.. note::
The underlying :attr:`monitor` is *not* stopped.
"""
if self._stop_event is None:
return
with self._stop_event.sink:
# emit a stop event to the thread
eintr_retry_call(self._stop_event.sink.write, b'\x01')
self._stop_event.sink.flush()
def stop(self):
"""
Synchronously stop the background thread.
.. note::
This method can safely be called from the observer thread. In this
case it is equivalent to :meth:`send_stop()`.
Send a stop signal to the backgroud (see :meth:`send_stop`), and waits
for the background thread to exit (see :meth:`~threading.Thread.join`)
if the current thread is *not* the observer thread.
After this method returns in a thread *that is not the observer
thread*, the ``callback`` is guaranteed to not be invoked again
anymore.
.. note::
The underlying :attr:`monitor` is *not* stopped.
.. versionchanged:: 0.16
This method can be called from the observer thread.
"""
self.send_stop()
try:
self.join()
except RuntimeError:
pass

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner <lunaryorn@gmail.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# pylint: disable=anomalous-backslash-in-string
"""
pyudev.pyqt4
============
PyQt4 integration.
:class:`MonitorObserver` integrates device monitoring into the PyQt4\_
mainloop by turning device events into Qt signals.
:mod:`PyQt4.QtCore` from PyQt4\_ must be available when importing this
module.
.. _PyQt4: http://riverbankcomputing.co.uk/software/pyqt/intro
.. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from PyQt4 import QtCore
from ._qt_base import MonitorObserverGenerator
from ._qt_base import QUDevMonitorObserverGenerator
# pylint: disable=invalid-name
MonitorObserver = MonitorObserverGenerator.make_monitor_observer(
QtCore.QObject,
QtCore.pyqtSignal,
QtCore.QSocketNotifier
)
"""
.. deprecated:: 0.17
Will be removed in 1.0. Use :class:`MonitorObserver` instead.
"""
QUDevMonitorObserver = QUDevMonitorObserverGenerator.make_monitor_observer(
QtCore.QObject,
QtCore.pyqtSignal,
QtCore.QSocketNotifier
)

@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.pyqt5
============
PyQt5 integration.
:class:`MonitorObserver` integrates device monitoring into the PyQt5_
mainloop by turning device events into Qt signals.
:mod:`PyQt5.QtCore` from PyQt5_ must be available when importing this
module.
.. _gPyQt5: http://riverbankcomputing.co.uk/software/pyqt/intro
.. moduleauthor:: Tobias Gehring <mail@tobiasgehring.de>
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from PyQt5 import QtCore
from ._qt_base import MonitorObserverGenerator
# pylint: disable=invalid-name
MonitorObserver = MonitorObserverGenerator.make_monitor_observer(
QtCore.QObject,
QtCore.pyqtSignal,
QtCore.QSocketNotifier
)

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner <lunaryorn@gmail.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# pylint: disable=anomalous-backslash-in-string
"""
pyudev.pyside
=============
PySide integration.
:class:`QUDevMonitorObserver` integrates device monitoring into the PySide\_
mainloop by turing device events into Qt signals.
:mod:`PySide.QtCore` from PySide\_ must be available when importing this
module.
.. _PySide: http://www.pyside.org
.. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
.. versionadded:: 0.6
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from PySide import QtCore
from ._qt_base import MonitorObserverGenerator
from ._qt_base import QUDevMonitorObserverGenerator
# pylint: disable=invalid-name
MonitorObserver = MonitorObserverGenerator.make_monitor_observer(
QtCore.QObject,
QtCore.Signal,
QtCore.QSocketNotifier
)
"""
.. deprecated:: 0.17
Will be removed in 1.0. Use :class:`MonitorObserver` instead.
"""
QUDevMonitorObserver = QUDevMonitorObserverGenerator.make_monitor_observer(
QtCore.QObject,
QtCore.Signal,
QtCore.QSocketNotifier
)

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern <amulhern@redhat.com>
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.version
==============
Version information.
.. moduleauthor:: mulhern <amulhern@redhat.com>
"""
__version_info__ = (0, 21, 0, '')
__version__ = "%s%s" % \
(
".".join(str(x) for x in __version_info__[:3]),
"".join(str(x) for x in __version_info__[3:])
)

@ -0,0 +1,150 @@
# -*- coding: utf-8 -*-
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# pylint: disable=anomalous-backslash-in-string
"""pyudev.wx
=========
Wx integration.
:class:`MonitorObserver` integrates device monitoring into the wxPython\_
mainloop by turing device events into wx events.
:mod:`wx` from wxPython\_ must be available when importing this module.
.. _wxPython: http://wxpython.org/
.. moduleauthor:: Tobias Eberle <tobias.eberle@gmx.de>
.. versionadded:: 0.14
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
from wx import EvtHandler, PostEvent
from wx.lib.newevent import NewEvent
import pyudev
DeviceEvent, EVT_DEVICE_EVENT = NewEvent()
class MonitorObserver(EvtHandler):
"""
An observer for device events integrating into the :mod:`wx` mainloop.
This class inherits :class:`~wx.EvtHandler` to turn device events into
wx events:
>>> from pyudev import Context, Monitor
>>> from pyudev.wx import MonitorObserver
>>> context = Context()
>>> monitor = Monitor.from_netlink(context)
>>> monitor.filter_by(subsystem='input')
>>> observer = MonitorObserver(monitor)
>>> def device_event(event):
... print('action {0} on device {1}'.format(event.device.action, event.device))
>>> observer.Bind(EVT_DEVICE_EVENT, device_event)
>>> monitor.start()
This class is a child of :class:`wx.EvtHandler`.
.. versionadded:: 0.17
"""
def __init__(self, monitor):
EvtHandler.__init__(self)
self.monitor = monitor
self._observer_thread = None
self.start()
@property
def enabled(self):
"""
Whether this observer is enabled or not.
If ``True`` (the default), this observer is enabled, and emits events.
Otherwise it is disabled and does not emit any events.
"""
return self._observer_thread is not None
@enabled.setter
def enabled(self, value):
if value:
self.start()
else:
self.stop()
def start(self):
"""
Enable this observer.
Do nothing, if the observer is already enabled.
"""
if self._observer_thread is not None:
return
self._observer_thread = pyudev.MonitorObserver(
self.monitor, callback=self._emit_event,
name='wx-observer-thread')
self._observer_thread.start()
def stop(self):
"""
Disable this observer.
Do nothing, if the observer is already disabled.
"""
if self._observer_thread is None:
return
self._observer_thread.stop()
def _emit_event(self, device):
PostEvent(self, DeviceEvent(device=device))
DeviceAddedEvent, EVT_DEVICE_ADDED = NewEvent()
DeviceRemovedEvent, EVT_DEVICE_REMOVED = NewEvent()
DeviceChangedEvent, EVT_DEVICE_CHANGED = NewEvent()
DeviceMovedEvent, EVT_DEVICE_MOVED = NewEvent()
class WxUDevMonitorObserver(MonitorObserver):
"""An observer for device events integrating into the :mod:`wx` mainloop.
.. deprecated:: 0.17
Will be removed in 1.0. Use :class:`MonitorObserver` instead.
"""
_action_event_map = {
'add': DeviceAddedEvent,
'remove': DeviceRemovedEvent,
'change': DeviceChangedEvent,
'move': DeviceMovedEvent
}
def __init__(self, monitor):
MonitorObserver.__init__(self, monitor)
import warnings
warnings.warn('Will be removed in 1.0. '
'Use pyudev.wx.MonitorObserver instead.',
DeprecationWarning)
def _emit_event(self, device):
PostEvent(self, DeviceEvent(action=device.action, device=device))
event_class = self._action_event_map.get(device.action)
if event_class is not None:
PostEvent(self, event_class(device=device))

@ -0,0 +1,196 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Name: qemu.py
# Purpose: Module to boot ISO and USB disks using QEMU.
# Depends: QEMU must be installed under Linux for availing this feature. For windows, QEMU package is shipped
# along with executable file
# 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
import os
import subprocess
import platform
from .admin import adminCmd
from PyQt5 import QtWidgets
from .gui.ui_multibootusb import Ui_Dialog
from .gen import *
class Qemu(QtWidgets.QDialog, Ui_Dialog):
"""
ISO and USB booting using QEMU.
"""
def on_Qemu_Browse_iso_Click(self):
"""
Browse and choose an ISO.
:return:
"""
self.ui.lineEdit_2.clear()
qemu = self.check_qemu_exist()
if not qemu is None:
qemu_iso_link = QtWidgets.QFileDialog.getOpenFileName(self, 'Select an iso...', "", "ISO Files (*.iso)")[0]
else:
log("QEMU does not exist.\nPlease install qemu package to avail this feature.")
QtWidgets.QMessageBox.information(self, 'No QEMU...', 'Please install qemu package to avail this feature.')
qemu_iso_link = None
if not qemu_iso_link is None:
self.ui.lineEdit_2.insert(qemu_iso_link)
else:
log ("File not selected.")
def on_Qemu_Boot_iso_Click(self):
"""
Main function to boot a selected ISO.
:return:
"""
if not self.ui.lineEdit_2.text():
QtWidgets.QMessageBox.information(self, 'No ISO...', 'No ISO selected.\n\nPlease choose an iso and click Boot ISO.')
else:
qemu = self.check_qemu_exist()
qemu_iso_link = str(self.ui.lineEdit_2.text())
if qemu is None:
log("QEMU does not exist.\nPlease install qemu package to avail this feature.")
QtWidgets.QMessageBox.information(self, 'No QEMU...', 'Please install qemu to avail this feature.')
else:
ram = self.qemu_iso_ram()
if not ram is None:
self.ui.lineEdit_2.clear()
if platform.system() == "Windows":
try:
log("Executing ==> " + qemu + " -cdrom " + str(qemu_iso_link) + " -boot d -m " + ram)
subprocess.Popen(qemu + " -cdrom " + str(qemu_iso_link) + " -boot d -m " + ram, shell=True)
except:
QtWidgets.QMessageBox.information(self, 'Error...', 'Unable to start QEMU.')
else:
log(qemu + ' -m ' + ram + ' -cdrom ' + str(qemu_iso_link) + ' -boot d')
try:
log("Executing ==> " + qemu + " -cdrom " + str(qemu_iso_link) + " -boot d -m " + ram)
subprocess.Popen(qemu + " -cdrom " + str(qemu_iso_link) + " -boot d -m " + ram, shell=True)
except:
QtWidgets.QMessageBox.information(self, 'Error...', 'Error booting ISO\n'
'Unable to start QEMU.')
else:
QtWidgets.QMessageBox.information(self, 'No ram...', 'No ram selected.\n\nPlease choose any ram value and click Boot ISO.')
def on_Qemu_Boot_usb_Click(self, usb_disk):
"""
Main function to boot a selected USB disk.
:param usb_disk: Path to usb disk.
:return:
"""
qemu = self.check_qemu_exist()
if qemu is None:
log("QEMU does not exist.\nPlease install qemu package to avail this feature.")
QtWidgets.QMessageBox.information(self, 'No QEMU...', 'Please install qemu to avail this feature.')
else:
ram = self.qemu_usb_ram()
if ram is None:
QtWidgets.QMessageBox.information(self, 'No ram...', 'No ram selected.\n\nPlease choose any ram value and click Boot USB.')
else:
if platform.system() == "Windows":
disk_number = self.get_physical_disk_number(usb_disk)
parent_dir = os.getcwd()
os.chdir(resource_path(os.path.join("data", "tools", "qemu")))
try:
log("Executing ==> " + qemu + " -L . -boot c -m " + ram + " -hda //./PhysicalDrive" + disk_number)
subprocess.Popen("qemu-system-x86_64.exe -L . -boot c -m " + ram + " -hda //./PhysicalDrive" + disk_number, shell=True)
except:
QtWidgets.QMessageBox.information(self, 'Error...', 'Error booting USB\n'
'Unable to start QEMU.')
os.chdir(parent_dir)
elif platform.system() == "Linux":
try:
qemu_cmd = qemu + ' -hda ' + usb_disk[:-1] + ' -m ' + ram + ' -vga std'
log('Executing ==> ' + qemu_cmd)
# adminCmd([qemu, '-hda', usb_disk[:-1], '-m', ram, '-vga std'], gui=True)
subprocess.Popen(qemu_cmd, shell=True)
# adminCmd(qemu_cmd, gui=True)
except:
QtWidgets.QMessageBox.information(self, 'Error...', 'Error booting USB\n\nUnable to start QEMU.')
def qemu_iso_ram(self):
"""
Choose a ram size for ISO booting.
:return: Ram size as string.
"""
if self.ui.ram_iso_256.isChecked():
return str(256)
elif self.ui.ram_iso_512.isChecked():
return str(512)
elif self.ui.ram_iso_768.isChecked():
return str(768)
elif self.ui.ram_iso_1024.isChecked():
return str(1024)
elif self.ui.ram_iso_2048.isChecked():
return str(2047)
else:
return None
def qemu_usb_ram(self):
"""
Choose a ram size for USB booting.
:return: Ram size as string.
"""
if self.ui.ram_usb_256.isChecked():
return str(256)
if self.ui.ram_usb_512.isChecked():
return str(512)
if self.ui.ram_usb_768.isChecked():
return str(768)
if self.ui.ram_usb_1024.isChecked():
return str(1024)
if self.ui.ram_usb_2048.isChecked():
return str(2047)
else:
return None
def check_qemu_exist(self):
"""
Check if QEMU is available on host system.
:return: path to QEMU program or None otherwise.
"""
if platform.system() == "Linux":
if subprocess.call('which qemu-system-x86_64', shell=True) == 0:
log("qemu-system-x86_64 exists...")
qemu = "qemu-system-x86_64"
elif subprocess.call('which qemu', shell=True) == 0:
log("qemu exists")
qemu = "qemu"
else:
qemu = None
if qemu:
return qemu
else:
return None
elif platform.system() == "Windows":
log(resource_path(os.path.join("data", "tools", "qemu", "qemu-system-x86_64.exe")))
return resource_path(os.path.join("data", "tools", "qemu", "qemu-system-x86_64.exe"))
def get_physical_disk_number(self, usb_disk):
"""
Get the physical disk number as detected ny Windows.
:param usb_disk: USB disk (Like F:)
:return: Disk number.
"""
import wmi
c = wmi.WMI ()
for physical_disk in c.Win32_DiskDrive ():
for partition in physical_disk.associators ("Win32_DiskDriveToDiskPartition"):
for logical_disk in partition.associators ("Win32_LogicalDiskToPartition"):
if logical_disk.Caption == usb_disk:
"""
log physical_disk.Caption
log partition.Caption
log logical_disk.Caption
"""
log("Physical Device Number is " + partition.Caption[6:-14])
return str(partition.Caption[6:-14])

@ -0,0 +1,189 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Name: syslinux.py
# Purpose: Module to install syslinux and extlinux on selected USB disk.
# 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
import os
import sys
import subprocess
import platform
from .gen import *
from . import usb
from .iso import *
from . import config
extlinux_path = os.path.join(multibootusb_host_dir(), "syslinux", "bin", "extlinux4")
syslinux_path = os.path.join(multibootusb_host_dir(), "syslinux", "bin", "syslinux4")
extlinux_fs = ["ext2", "ext3", "ext4", "Btrfs"]
syslinux_fs = ["vfat", "ntfs", "FAT32", "NTFS"]
mbr_bin = resource_path(os.path.join("data", "tools", "mbr.bin"))
def set_boot_flag(usb_disk):
if platform.system() == "Linux":
log ("\nChecking boot flag on " + usb_disk[:-1], '\n')
cmd_out = subprocess.check_output("parted -m -s " + usb_disk[:-1] + " print", shell=True)
if b'boot' in cmd_out:
log ("\nDisk " + usb_disk[:-1] + " already has boot flag.\n")
return True
else:
log ("\nExecuting ==> parted " + usb_disk[:-1] + " set 1 boot on", '\n')
if subprocess.call("parted " + usb_disk[:-1] + " set 1 boot on", shell=True) == 0:
log ("\nBoot flag set to bootable " + usb_disk[:-1], '\n')
return True
else:
log ("\nUnable to set boot flag on " + usb_disk[:-1], '\n')
return False
def syslinux_default(usb_disk, version=4):
"""
Install Syslinux of a selected drive
:param usb_disk: '/dev/sdx' on linux and 'E:' on Windows
:version: Default version is 4. Change it if you wish. But necessary files needs to be copied accordingly
:return: Bootable USB disk :-)
"""
usb_details = usb.details(usb_disk)
usb_fs = usb_details['file_system']
usb_mount = usb_details['mount_point']
mbr_install_cmd = 'dd bs=440 count=1 conv=notrunc if=' + mbr_bin + ' of=' + usb_disk[:-1]
# log (usb_fs)
if usb_fs in extlinux_fs:
extlinu_cmd = extlinux_path + ' --install ' + os.path.join(usb_mount, 'multibootusb')
if os.access(extlinux_path, os.X_OK) is False:
subprocess.call('chmod +x ' + extlinux_path, shell=True)
log ("\nExecuting ==> " + extlinu_cmd)
if subprocess.call(extlinu_cmd, shell=True) == 0:
log ("\nDefault Extlinux install is success...\n")
log ('\nExecuting ==> ' + mbr_install_cmd)
if subprocess.call(mbr_install_cmd, shell=True) == 0:
log ("\nmbr install is success...\n")
if set_boot_flag(usb_disk) is True:
return True
elif usb_fs in syslinux_fs:
if platform.system() == "Linux":
syslinux_cmd = syslinux_path + ' -i -d multibootusb ' + usb_disk
if os.access(syslinux_path, os.X_OK) is False:
subprocess.call('chmod +x ' + syslinux_path, shell=True)
log ("\nExecuting ==> " + syslinux_cmd + "\n")
if subprocess.call(syslinux_cmd, shell=True) == 0:
log ("\nDefault syslinux install is success...\n")
if subprocess.call(mbr_install_cmd, shell=True) == 0:
log ("\nmbr install is success...\n")
if set_boot_flag(usb_disk) is True:
return True
else:
log ("\nFailed to install default syslinux...\n")
return False
elif platform.system() == "Windows":
syslinux = resource_path(os.path.join(multibootusb_host_dir(), "syslinux", "bin", "syslinux4.exe"))
log ('Executing ==>' + syslinux + ' -maf -d multibootusb ' + usb_disk)
if subprocess.call(syslinux + ' -maf -d multibootusb ' + usb_disk, shell=True) == 0:
log ("\nDefault syslinux install is success...\n")
return True
else:
log ("\nFailed to install default syslinux...\n")
return False
def syslinux_distro_dir(usb_disk, iso_link, distro):
"""
Install syslinux/extlinux on distro specific isolinux directory.
:param usb_disk: '/dev/sdx' on linux and 'E:' on Windows
:param iso_link: Path to ISO file
:return:
"""
usb_details = usb.details(usb_disk)
usb_fs = usb_details['file_system']
usb_mount = usb_details['mount_point']
isolinux_bin_dir(iso_link)
if isolinux_bin_exist(iso_link) is False:
log ('Distro does not use isolinux for booting ISO.')
else:
# iso_cfg_ext_dir = os.path.join(multibootusb_host_dir(), "iso_cfg_ext_dir")
_iso_cfg_ext_dir = iso_cfg_ext_dir()
isolinux_path = os.path.join(_iso_cfg_ext_dir, isolinux_bin_path(iso_link))
iso_linux_bin_dir = isolinux_bin_dir(iso_link)
config.syslinux_version = isolinux_version(isolinux_path)
if distro == "generic" or distro == "alpine":
install_dir = usb_mount
distro_syslinux_install_dir = os.path.join(usb_mount, iso_linux_bin_dir.strip("/")).replace(usb_mount, "")
distro_sys_install_bs = os.path.join(install_dir, iso_linux_bin_dir.strip("/"), distro + '.bs')
else:
install_dir = os.path.join(usb_mount, "multibootusb", iso_basename(iso_link))
distro_syslinux_install_dir = os.path.join(install_dir, iso_linux_bin_dir.strip("/")).replace(usb_mount, "")
distro_sys_install_bs = os.path.join(install_dir, iso_linux_bin_dir.strip("/"), distro + '.bs')
log (distro_sys_install_bs)
#log (distro_syslinux_install_dir)
if usb_fs in syslinux_fs:
if config.syslinux_version == str(3):
if distro == "generic" and iso_linux_bin_dir == "/":
option = ""
else:
option = " -d "
else:
if distro == "generic" and iso_linux_bin_dir == "/":
option = " -i "
else:
option = " -i -d "
if platform.system() == "Linux":
syslinux_path = os.path.join(multibootusb_host_dir(), "syslinux", "bin", "syslinux") + config.syslinux_version
if os.access(syslinux_path, os.X_OK) is False:
subprocess.call('chmod +x ' + syslinux_path, shell=True) == 0
sys_cmd = syslinux_path + option + quote(distro_syslinux_install_dir) + ' ' + usb_disk
dd_cmd = 'dd if=' + usb_disk + ' ' + 'of=' + quote(distro_sys_install_bs) + ' count=1'
log ("Executing ==> " + sys_cmd)
if subprocess.call(sys_cmd, shell=True) == 0:
log ("\nSyslinux install on distro directory is success...\n")
log ('Executing ==> ' + dd_cmd + '\n')
if subprocess.call(dd_cmd, shell=True) == 0:
log ("\nBootsector copy is success...\n")
else:
log ("\nFailed to copy boot sector...\n")
else:
log ("\nFailed to install syslinux on distro directory...\n")
elif platform.system() == "Windows":
syslinux_path = resource_path(os.path.join(multibootusb_host_dir(), "syslinux", "bin")) + \
"\syslinux" + config.syslinux_version + ".exe"
distro_syslinux_install_dir = "/" + distro_syslinux_install_dir.replace("\\", "/")
distro_sys_install_bs = distro_sys_install_bs.replace("/", "\\")
sys_cmd = syslinux_path + option + distro_syslinux_install_dir + ' ' + usb_disk + ' ' + \
distro_sys_install_bs
log ("\nExecuting ==> " + sys_cmd, '\n')
if subprocess.call(sys_cmd, shell=True) == 0:
log ("\nSyslinux install was successful on distro directory...\n")
else:
log ("\nFailed to install syslinux on distro directory...\n")
elif usb_fs in extlinux_fs:
if platform.system() == "Linux":
distro_syslinux_install_dir = os.path.join(install_dir, iso_linux_bin_dir.strip("/"))
syslinux_path = os.path.join(multibootusb_host_dir(), "syslinux", "bin", "extlinux") + config.syslinux_version
ext_cmd = syslinux_path + " --install " + distro_syslinux_install_dir
dd_cmd = 'dd if=' + usb_disk + ' ' + 'of=' + usb_mount + quote(distro_sys_install_bs) + ' count=1'
if os.access(syslinux_path, os.X_OK) is False:
subprocess.call('chmod +x ' + syslinux_path, shell=True) == 0
log ("Executing ==> " + ext_cmd)
if subprocess.call(ext_cmd, shell=True) == 0:
log ("\nSyslinux install on distro directory is success...\n")
log ('Executing ==> ' + dd_cmd, '\n')
if subprocess.call(dd_cmd, shell=True) == 0:
log ("\nBootsector copy is success...\n")
else:
log ("\nFailed to install syslinux on distro directory...\n")
if __name__ == '__main__':
if os.geteuid() != 0:
log ('Please running this script with sudo/root/admin privilage.')
exit(1)
else:
syslinux_distro_dir('/dev/sdb1', '../../../DISTROS/2016/debian-live-8.3.0-amd64-lxde-desktop.iso', 'debian')
syslinux_default('/dev/sdb1')

@ -0,0 +1,223 @@
#!/usr/bin/env python3
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
# Name: udisks.py
# Purpose: Module to mount unmount and eject using dbus and udisk
# Authors: Original author is Kovid Goyal <kovid@kovidgoyal.net> and python3
# supporte by Sundar for multibootusb project
# Licence: 'GPL v3' as per original Licence
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
# from __future__ import print_function
import os, re
def node_mountpoint(node):
def de_mangle(raw):
return raw.replace('\\040', ' ').replace('\\011', '\t').replace('\\012',
'\n').replace('\\0134', '\\')
for line in open('/proc/mounts').readlines():
line = line.split()
if line[0] == node:
return de_mangle(line[1])
return None
class NoUDisks1(Exception):
pass
class UDisks(object):
def __init__(self):
import dbus
self.bus = dbus.SystemBus()
try:
self.main = dbus.Interface(self.bus.get_object('org.freedesktop.UDisks',
'/org/freedesktop/UDisks'), 'org.freedesktop.UDisks')
except dbus.exceptions.DBusException as e:
if getattr(e, '_dbus_error_name', None) == 'org.freedesktop.DBus.Error.ServiceUnknown':
raise NoUDisks1()
raise
def device(self, device_node_path):
import dbus
devpath = self.main.FindDeviceByDeviceFile(device_node_path)
return dbus.Interface(self.bus.get_object('org.freedesktop.UDisks',
devpath), 'org.freedesktop.UDisks.Device')
def mount(self, device_node_path):
d = self.device(device_node_path)
try:
return str(d.FilesystemMount('',
['auth_no_user_interaction', 'rw', 'noexec', 'nosuid',
'nodev', 'uid=%d'%os.geteuid(), 'gid=%d'%os.getegid()]))
except:
# May be already mounted, check
mp = node_mountpoint(str(device_node_path))
if mp is None:
raise
return mp
def unmount(self, device_node_path):
d = self.device(device_node_path)
d.FilesystemUnmount(['force'])
def eject(self, device_node_path):
parent = device_node_path
while parent[-1] in '0123456789':
parent = parent[:-1]
d = self.device(parent)
d.DriveEject([])
class NoUDisks2(Exception):
pass
class UDisks2(object):
BLOCK = 'org.freedesktop.UDisks2.Block'
FILESYSTEM = 'org.freedesktop.UDisks2.Filesystem'
DRIVE = 'org.freedesktop.UDisks2.Drive'
def __init__(self):
import dbus
self.bus = dbus.SystemBus()
try:
self.bus.get_object('org.freedesktop.UDisks2',
'/org/freedesktop/UDisks2')
except dbus.exceptions.DBusException as e:
if getattr(e, '_dbus_error_name', None) == 'org.freedesktop.DBus.Error.ServiceUnknown':
raise NoUDisks2()
raise
def device(self, device_node_path):
device_node_path = os.path.realpath(device_node_path)
devname = device_node_path.split('/')[-1]
# First we try a direct object path
bd = self.bus.get_object('org.freedesktop.UDisks2',
'/org/freedesktop/UDisks2/block_devices/%s'%devname)
try:
device = bd.Get(self.BLOCK, 'Device',
dbus_interface='org.freedesktop.DBus.Properties')
device = bytearray(device).replace(b'\x00', b'').decode('utf-8')
except:
device = None
if device == device_node_path:
return bd
# Enumerate all devices known to UDisks
devs = self.bus.get_object('org.freedesktop.UDisks2',
'/org/freedesktop/UDisks2/block_devices')
xml = devs.Introspect(dbus_interface='org.freedesktop.DBus.Introspectable')
for dev in re.finditer(r'name=[\'"](.+?)[\'"]', type('')(xml)):
bd = self.bus.get_object('org.freedesktop.UDisks2',
'/org/freedesktop/UDisks2/block_devices/%s2'%dev.group(1))
try:
device = bd.Get(self.BLOCK, 'Device',
dbus_interface='org.freedesktop.DBus.Properties')
device = bytearray(device).replace(b'\x00', b'').decode('utf-8')
except:
device = None
if device == device_node_path:
return bd
raise ValueError('%r not known to UDisks2'%device_node_path)
def mount(self, device_node_path):
d = self.device(device_node_path)
mount_options = ['rw', 'noexec', 'nosuid',
'nodev', 'uid=%d'%os.geteuid(), 'gid=%d'%os.getegid()]
try:
return str(d.Mount(
{
'auth.no_user_interaction':True,
'options':','.join(mount_options)
},
dbus_interface=self.FILESYSTEM))
except:
# May be already mounted, check
mp = node_mountpoint(str(device_node_path))
if mp is None:
raise
return mp
def unmount(self, device_node_path):
d = self.device(device_node_path)
d.Unmount({'force':True, 'auth.no_user_interaction':True},
dbus_interface=self.FILESYSTEM)
def drive_for_device(self, device):
drive = device.Get(self.BLOCK, 'Drive',
dbus_interface='org.freedesktop.DBus.Properties')
return self.bus.get_object('org.freedesktop.UDisks2', drive)
def eject(self, device_node_path):
drive = self.drive_for_device(self.device(device_node_path))
drive.Eject({'auth.no_user_interaction':True},
dbus_interface=self.DRIVE)
def get_udisks(ver=None):
if ver is None:
try:
u = UDisks2()
except NoUDisks2:
u = UDisks()
return u
return UDisks2() if ver == 2 else UDisks()
def get_udisks1():
u = None
try:
u = UDisks()
except NoUDisks1:
try:
u = UDisks2()
except NoUDisks2:
pass
if u is None:
raise EnvironmentError('UDisks not available on your system')
return u
def mount(node_path):
u = get_udisks1()
u.mount(node_path)
def eject(node_path):
u = get_udisks1()
u.eject(node_path)
def umount(node_path):
u = get_udisks1()
u.unmount(node_path)
def test_udisks(ver=None):
import sys
dev = sys.argv[1]
print('Testing with node', dev)
u = get_udisks(ver=ver)
print('Using Udisks:', u.__class__.__name__)
print('Mounted at:', u.mount(dev))
print('Unmounting')
u.unmount(dev)
print('Mounting')
u.mount(dev)
print('Ejecting:')
u.eject(dev)
if __name__ == '__main__':
print('Run test here...')
# test_udisks()

@ -0,0 +1,222 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Name: uninstall_distro.py
# Purpose: Module to uninstall distros installed by multibootusb
# 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
import os
import re
import shutil
import threading
import platform
from .usb import *
from . import config
from . import gen
def install_distro_list():
"""
List all distro names installed by previous install
:return: List of distro names as list
"""
usb_details = details(config.usb_disk)
config.usb_mount = usb_details['mount_point']
sys_cfg_file = os.path.join(config.usb_mount, "multibootusb", "syslinux.cfg")
if os.path.exists(sys_cfg_file):
distro_list = []
for line in open(sys_cfg_file):
if "#start " in line:
distro_list.append(line[7:])
return distro_list
else:
return None
def unin_distro():
usb_details = details(config.usb_disk)
usb_mount = usb_details['mount_point']
config.uninstall_distro_dir_name = config.uninstall_distro_dir_name.replace('\n', '')
gen.log(os.path.join(usb_mount, "multibootusb", config.uninstall_distro_dir_name, "multibootusb.cfg"))
if os.path.exists(os.path.join(usb_mount, "multibootusb", config.uninstall_distro_dir_name, "multibootusb.cfg")):
with open(os.path.join(usb_mount, "multibootusb", config.uninstall_distro_dir_name, "multibootusb.cfg"), "r") as multibootusb_cfg:
config.distro = multibootusb_cfg.read().replace('\n', '')
if config.distro:
uninstall_distro()
else:
return ""
def delete_frm_file_list():
"""
Generic way to remove files from USB disk.
:param config.usb_disk:
:param iso_file_list: List of files installed in the USB disk
:param config.uninstall_distro_dir_name: Directory where the distro is installed
:return:
"""
usb_details = details(config.usb_disk)
usb_mount = usb_details['mount_point']
if config.iso_file_list is not None:
for f in config.iso_file_list:
if platform.system() == "Windows":
f = f.replace('\n', '').strip("/").replace("/", "\\")
else:
f = f.replace('\n', '').strip("/")
if os.path.exists(os.path.join(usb_mount, "ldlinux.sys")):
os.chmod(os.path.join(usb_mount, "ldlinux.sys"), 0o777)
os.unlink(os.path.join(usb_mount, "ldlinux.sys"))
if os.path.exists(os.path.join(usb_mount, f)):
gen.log("Removing " + (os.path.join(usb_mount, f)))
if os.path.isfile(os.path.join(usb_mount, f)):
os.remove(os.path.join(usb_mount, f))
elif os.path.isdir(os.path.join(usb_mount, f)):
shutil.rmtree(os.path.join(usb_mount, f))
if os.path.exists(os.path.join(usb_mount, "multibootusb", config.uninstall_distro_dir_name, "generic.cfg")):
with open(os.path.join(usb_mount, "multibootusb", config.uninstall_distro_dir_name, "generic.cfg"), "r") as generic_cfg:
if platform.system() == "Windows":
generic = generic_cfg.read().replace('\n', '').replace("/", "\\")
else:
generic = generic_cfg.read().replace('\n', '')
if os.path.exists(os.path.join(usb_mount, generic.strip("/"))):
os.remove(os.path.join(usb_mount, generic.strip("/")))
if platform.system() == 'Linux':
gen.log('Removed files from' + config.uninstall_distro_dir_name)
gen.log('Syncing....')
os.system('sync')
def uninstall_distro():
"""
Uninstall selected distro from selected USB disk.
:param config.usb_disk: Path of the USB disk
:param config.uninstall_distro_dir_name: Directory where the distro is installed
:param _distro: Generic name applied to distro install by multibootusb
:return:
"""
usb_details = details(config.usb_disk)
usb_mount = usb_details['mount_point']
if platform.system() == 'Linux':
os.system('sync')
if os.path.exists(os.path.join(usb_mount, "multibootusb", config.uninstall_distro_dir_name, "iso_file_list.cfg")):
with open(os.path.join(usb_mount, "multibootusb", config.uninstall_distro_dir_name, "iso_file_list.cfg"), "r") as f:
config.iso_file_list = f.readlines()
# gen.log iso_file_list
for path, subdirs, files in os.walk(os.path.join(usb_mount, "multibootusb", config.uninstall_distro_dir_name)):
for name in files:
if name.endswith('ldlinux.sys') or name.endswith('ldlinux.c32'):
os.chmod(os.path.join(path, name), 0o777)
os.unlink(os.path.join(path, name))
if config.distro == "opensuse":
if os.path.exists(os.path.join(usb_mount, config.uninstall_distro_dir_name + ".iso")):
os.remove(os.path.join(usb_mount, config.uninstall_distro_dir_name + ".iso"))
elif config.distro == "windows" or config.distro == "alpine" or config.distro == "generic":
delete_frm_file_list()
if config.distro == "ipfire":
files = os.listdir(usb_mount)
for f in files:
if f.endswith('.tlz'):
os.remove(os.path.join(usb_mount, f))
if os.path.exists(os.path.join(usb_mount, "distro.img")):
os.remove(os.path.join(usb_mount, "distro.img"))
elif config.distro == "trinity-rescue":
shutil.rmtree(os.path.join(usb_mount, "trk3"))
if os.path.exists(os.path.join(usb_mount, "multibootusb", config.uninstall_distro_dir_name)):
if platform.system() == 'Linux':
os.system('sync')
shutil.rmtree(os.path.join(usb_mount, "multibootusb", config.uninstall_distro_dir_name))
delete_frm_file_list()
update_sys_cfg_file()
def update_sys_cfg_file():
"""
Main function to remove uninstall distro specific operations.
:return:
"""
if platform.system() == 'Linux':
os.system('sync')
sys_cfg_file = os.path.join(config.usb_mount, "multibootusb", "syslinux.cfg")
if not os.path.exists(sys_cfg_file):
gen.log("syslinux.cfg file not found for updating changes.")
else:
gen.log("Updating syslinux.cfg file...")
string = open(sys_cfg_file).read()
string = re.sub(r'#start ' + config.uninstall_distro_dir_name + '.*?' + '#end ' + config.uninstall_distro_dir_name + '\s*', '', string, flags=re.DOTALL)
config_file = open(sys_cfg_file, "w")
config_file.write(string)
config_file.close()
def uninstall_progress():
"""
Calculate uninstall progress percentage.
:return:
"""
from . import progressbar
usb_details = details(config.usb_disk)
usb_mount = usb_details['mount_point']
if platform.system() == 'Linux':
os.system('sync')
if os.path.exists(os.path.join(usb_mount, "multibootusb", config.uninstall_distro_dir_name, "multibootusb.cfg")):
with open(os.path.join(usb_mount, "multibootusb", config.uninstall_distro_dir_name, "multibootusb.cfg"),
"r") as multibootusb_cfg:
config.distro = multibootusb_cfg.read().replace('\n', '')
else:
config.distro = ""
gen.log("Installed distro type is " + config.distro)
if config.distro == "opensuse":
if os.path.exists(os.path.join(usb_mount, config.uninstall_distro_dir_name) + ".iso"):
folder_size_to_remove = os.path.getsize(os.path.join(usb_mount, config.uninstall_distro_dir_name) + ".iso")
else:
folder_size_to_remove = 0
folder_size_to_remove += disk_usage(str(usb_mount) + "/multibootusb/" + config.uninstall_distro_dir_name).used
elif config.distro == "windows" or config.distro == "Windows":
if os.path.exists(os.path.join(usb_mount, "SOURCES")):
folder_size_to_remove = disk_usage(str(usb_mount) + "/SOURCES").used
else:
folder_size_to_remove = disk_usage(str(usb_mount) + "/SSTR").used
elif config.distro == "ipfire":
folder_size_to_remove = disk_usage(str(usb_mount) + "/multibootusb/" + config.uninstall_distro_dir_name).used
files = os.listdir(os.path.join(str(usb_mount)))
for f in files:
if f.endswith('.tlz'):
folder_size_to_remove += os.path.getsize(os.path.join(config.usb_mount, f))
elif config.distro == "trinity-rescue":
folder_size_to_remove = disk_usage(os.path.join(usb_mount, "trk3")).used
folder_size_to_remove += disk_usage(usb_mount + "/multibootusb/" + config.uninstall_distro_dir_name).used
else:
folder_size_to_remove = disk_usage(os.path.join(usb_mount, "multibootusb", config.uninstall_distro_dir_name)).used
thrd = threading.Thread(target=unin_distro, name="uninstall_progress")
initial_usb_size = disk_usage(usb_mount).used
thrd.start()
config.status_text = "Uninstalling " + config.uninstall_distro_dir_name
pbar = progressbar.ProgressBar(maxval=100).start() # bar = progressbar.ProgressBar(redirect_stdout=True)
while thrd.is_alive():
current_size = disk_usage(usb_mount).used
diff_size = int(initial_usb_size - current_size)
config.percentage = round(float(diff_size) / folder_size_to_remove * 100)
if config.percentage > 100:
config.percentage = 100
pbar.update(config.percentage)
if not thrd.is_alive():
config.persistence = 0
config.status_text = ""

@ -0,0 +1,383 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Name: update_cfg_file.py
# Purpose: Module to manipulate distro specific and main config 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
import os
import re
import shutil
from .usb import *
from .gen import *
from .iso import *
from . import config
def update_distro_cfg_files(iso_link, usb_disk, distro, persistence=0):
"""
Main function to modify/update distro specific strings on distro config files.
:return:
"""
usb_details = details(usb_disk)
usb_mount = usb_details['mount_point']
usb_uuid = usb_details['uuid']
usb_label = usb_details['label']
patch = None
iso_cfg_ext_dir = os.path.join(multibootusb_host_dir(), "iso_cfg_ext_dir")
if isolinux_bin_exist(config.iso_link):
isolinux_path = os.path.join(iso_cfg_ext_dir, isolinux_bin_path(iso_link)[1:])
config.status_text = "Updating config files..."
install_dir = os.path.join(usb_mount, "multibootusb", iso_basename(iso_link))
log('Updating distro specific config files...')
for dirpath, dirnames, filenames in os.walk(install_dir):
for f in filenames:
if f.endswith(".cfg") or f.endswith('.CFG') or f.endswith('.lst'):
cfg_file = os.path.join(dirpath, f)
try:
string = open(cfg_file, errors='ignore').read()
except IOError:
log("Unable to read ", cfg_file)
else:
if not distro == "generic":
replace_text = r'\1/multibootusb/' + iso_basename(iso_link) + '/'
string = re.sub(r'([ \t =,])/', replace_text, string)
if distro == "ubuntu":
string = re.sub(r'boot=casper',
'boot=casper cdrom-detect/try-usb=true floppy.allowed_drive_mask=0 ignore_uuid '
'ignore_bootid root=UUID=' + usb_uuid + ' live-media-path=/multibootusb/'
+ iso_basename(iso_link) + '/casper', string)
string = re.sub(r'ui gfxboot', '#ui gfxboot', string)
if not persistence == 0:
string = re.sub(r'boot=casper', 'boot=casper persistent persistent-path=/multibootusb/' +
iso_basename(iso_link) + "/", string)
elif distro == "debian" or distro == "debian-install":
string = re.sub(r'boot=live', 'boot=live ignore_bootid live-media-path=/multibootusb/' +
iso_basename(iso_link) + '/live', string)
if not persistence == 0:
string = re.sub(r'boot=live', 'boot=live persistent persistent-path=/multibootusb/' +
iso_basename(iso_link) + "/", string)
elif distro == "ubuntu-server":
string = re.sub(r'file',
'cdrom-detect/try-usb=true floppy.allowed_drive_mask=0 ignore_uuid ignore_bootid root=UUID=' +
usb_uuid + ' file', string)
elif distro == "fedora":
string = re.sub(r'root=\S*', 'root=live:UUID=' + usb_uuid, string)
if re.search(r'liveimg', string, re.I):
string = re.sub(r'liveimg', 'liveimg live_dir=/multibootusb/' +
iso_basename(iso_link) + '/LiveOS', string)
elif re.search(r'rd.live.image', string, re.I):
string = re.sub(r'rd.live.image', 'rd.live.image rd.live.dir=/multibootusb/' +
iso_basename(iso_link) + '/LiveOS', string)
elif re.search(r'Solus', string, re.I):
string = re.sub(r'initrd=', 'rd.live.dir=/multibootusb/' + iso_basename(iso_link) +
'/LiveOS initrd=', string)
if not persistence == 0:
if re.search(r'liveimg', string, re.I):
string = re.sub(r'liveimg', 'liveimg overlay=UUID=' + usb_uuid, string)
elif re.search(r'rd.live.image', string, re.I):
string = re.sub(r'rd.live.image', 'rd.live.image rw rd.live.overlay=UUID=' + usb_uuid, string)
string = re.sub(r' ro ', '', string)
elif distro == 'kaspersky':
if not os.path.exists(os.path.join(usb_mount, 'multibootusb', iso_basename(iso_link), 'kaspersky.cfg')):
shutil.copyfile(resource_path(os.path.join('data', 'multibootusb', 'syslinux.cfg')),
os.path.join(usb_mount, 'multibootusb', iso_basename(iso_link), 'kaspersky.cfg'))
config_string = kaspersky_config('kaspersky')
config_string = config_string.replace('$INSTALL_DIR', '/multibootusb/' + iso_basename(iso_link))
config_string = re.sub(r'root=live:UUID=', 'root=live:UUID=' + usb_uuid, config_string)
with open(os.path.join(usb_mount, 'multibootusb', iso_basename(iso_link), 'kaspersky.cfg'), "a") as f:
f.write(config_string)
elif distro == "parted-magic":
if re.search(r'append', string, re.I):
string = re.sub(r'append', 'append directory=/multibootusb/' + iso_basename(iso_link), string,
flags=re.I)
string = re.sub(r'initrd=', 'directory=/multibootusb/' + iso_basename(iso_link) + '/ initrd=',
string)
elif distro == "ubcd":
string = re.sub(r'iso_filename=\S*', 'directory=/multibootusb/' + iso_basename(iso_link),
string, flags=re.I)
elif distro == 'f4ubcd':
if not 'multibootusb' in string:
string = re.sub(r'/HBCD', '/multibootusb/' + iso_basename(iso_link) + '/HBCD', string)
if not 'multibootusb' in string:
string = re.sub(r'/F4UBCD', '/multibootusb/' + iso_basename(iso_link) + '/F4UBCD', string)
elif distro == "ipcop":
string = re.sub(r'ipcopboot=cdrom\S*', 'ipcopboot=usb', string)
elif distro == "puppy":
string = re.sub(r'pmedia=cd\S*',
'pmedia=usbflash psubok=TRUE psubdir=/multibootusb/' + iso_basename(iso_link) + '/',
string)
elif distro == "slax":
string = re.sub(r'initrd=',
r'from=/multibootusb/' + iso_basename(iso_link) + '/slax fromusb initrd=', string)
elif distro == "knoppix":
string = re.sub(r'initrd=', 'knoppix_dir=/multibootusb/' + iso_basename(iso_link) + '/KNOPPIX initrd=', string)
#string = re.sub(r'(append)',
# r'\1 knoppix_dir=/multibootusb/' + iso_basename(iso_link) + '/KNOPPIX',
# string)
elif distro == "gentoo":
string = re.sub(r'append ',
'append real_root=' + usb_disk + ' slowusb subdir=/multibootusb/' +
iso_basename(iso_link) + '/ ', string, flags=re.I)
elif distro == "systemrescuecd":
rows = []
subdir = '/multibootusb/' + iso_basename(iso_link) + '/'
for line in string.splitlines(True):
addline = True
if re.match(r'append.*--.*', line, flags=re.I):
line = re.sub(r'(append)(.*)--(.*)', r'\1\2subdir=' + subdir + r' --\3 subdir=' + subdir,
line, flags=re.I)
elif re.match(r'append', line, flags=re.I):
line = re.sub(r'(append)', r'\1 subdir=' + subdir, line, flags=re.I)
elif re.match(r'label rescue(32|64)_1', line, flags=re.I):
rows.append(line)
rows.append('append subdir=%s\n' % (subdir,))
addline = False
if addline:
rows.append(line)
string = ''.join(rows)
elif distro == "arch" or distro == "chakra":
string = re.sub(r'isolabel=\S*',
'isodevice=/dev/disk/by-uuid/' + usb_uuid, string, flags=re.I)
string = re.sub(r'isobasedir=',
'isobasedir=/multibootusb/' + iso_basename(iso_link) + '/', string, flags=re.I)
string = re.sub(r'ui gfxboot', '# ui gfxboot', string) # Bug in the isolinux package
if 'manjaro' in string:
if not os.path.exists(os.path.join(usb_mount, '.miso')):
with open(os.path.join(usb_mount, '.miso'), "w") as f:
f.write('')
elif distro == "kaos":
string = re.sub(r'kdeosisolabel=\S*',
'kdeosisodevice=/dev/disk/by-uuid/' + usb_uuid, string, flags=re.I)
string = re.sub(r'append',
'append kdeosisobasedir=/multibootusb/' + iso_basename(iso_link) + '/kdeos/', string, flags=re.I)
string = re.sub(r'ui gfxboot', '# ui gfxboot', string) # Bug in the isolinux package
elif distro == "suse" or distro == "opensuse":
if re.search(r'opensuse_12', string, re.I):
string = re.sub(r'append',
'append loader=syslinux isofrom_system=/dev/disk/by-uuid/' + usb_uuid + ":/" +
iso_name(iso_link), string, flags=re.I)
else:
string = re.sub(r'append',
'append loader=syslinux isofrom_device=/dev/disk/by-uuid/' + usb_uuid +
' isofrom_system=/multibootusb/' + iso_basename(iso_link) + '/' + iso_name(iso_link),
string, flags=re.I)
elif distro == "pclinuxos":
string = re.sub(r'livecd=',
'fromusb livecd=' + '/multibootusb/' + iso_basename(iso_link) + '/',
string)
string = re.sub(r'prompt', '#prompt', string)
string = re.sub(r'ui gfxboot.com', '#ui gfxboot.com', string)
string = re.sub(r'timeout', '#timeout', string)
elif distro == "wifislax":
string = re.sub(r'vmlinuz',
'vmlinuz from=multibootusb/' + iso_basename(iso_link) + ' noauto', string)
string = re.sub(r'vmlinuz2',
'vmlinuz2 from=multibootusb/' + iso_basename(iso_link) + ' noauto', string)
elif distro == "porteus":
string = re.sub(r'APPEND',
'APPEND from=/multibootusb/' + iso_basename(iso_link) + ' noauto', string)
string = re.sub(r'vmlinuz2',
'vmlinuz2 from=multibootusb/' + iso_basename(iso_link) + ' noauto', string)
elif distro == "hbcd":
if not 'multibootusb' in string:
string = re.sub(r'/HBCD', '/multibootusb/' + iso_basename(iso_link) + '/HBCD', string)
elif distro == "zenwalk":
string = re.sub(r'initrd=',
'from=/multibootusb/' + iso_basename(iso_link) + '/' + iso_name(iso_link) + ' initrd=',
string)
elif distro == "mageialive":
string = re.sub(r'LABEL=\S*', 'LABEL=' + usb_label, string)
elif distro == "antix":
string = re.sub(r'APPEND', 'image_dir=/multibootusb/' + iso_basename(iso_link), string)
elif distro == "solydx":
string = re.sub(r'live-media-path=', 'live-media-path=/multibootusb/' + iso_basename(iso_link),
string)
elif distro == "salix-live":
string = re.sub(r'iso_path', '/multibootusb/' + iso_basename(iso_link) + '/' + iso_name(iso_link),
string)
#string = re.sub(r'initrd', 'from=/multibootusb/' + iso_basename(iso_link) + '/' + ' initrd', string)
elif distro == 'alt-linux':
string = re.sub(r':cdrom', ':disk', string)
elif distro == 'fsecure':
string = re.sub(r'APPEND ramdisk_size', 'APPEND noprompt ' + 'knoppix_dir=/multibootusb/' + iso_basename(iso_link)
+ '/KNOPPIX ramdisk_size', string)
config_file = open(cfg_file, "w")
config_file.write(string)
config_file.close()
update_mbusb_cfg_file(iso_link, usb_uuid, usb_mount, distro)
def update_mbusb_cfg_file(iso_link, usb_uuid, usb_mount, distro):
"""
Update main multibootusb suslinux.cfg file after distro is installed.
:return:
"""
log('Updating multibootusb config file...')
sys_cfg_file = os.path.join(usb_mount, "multibootusb", "syslinux.cfg")
install_dir = os.path.join(usb_mount, "multibootusb", iso_basename(iso_link))
if os.path.exists(sys_cfg_file):
if distro == "hbcd":
if os.path.exists(os.path.join(usb_mount, "multibootusb", "menu.lst")):
_config_file = os.path.join(usb_mount, "multibootusb", "menu.lst")
config_file = open(_config_file,"w")
string = re.sub(r'/HBCD', '/multibootusb/' + iso_basename(iso_link) + '/HBCD', _config_file)
config_file.write(string)
config_file.close()
with open(sys_cfg_file, "a") as f:
f.write("#start " + iso_basename(config.iso_link) + "\n")
f.write("LABEL " + iso_basename(config.iso_link) + "\n")
f.write("MENU LABEL " + iso_basename(config.iso_link) + "\n")
f.write("BOOT " + '/multibootusb/' + iso_basename(iso_link) + '/' + isolinux_bin_dir(iso_link).replace("\\", "/") + '/' + distro + '.bs' + "\n")
f.write("#end " + iso_basename(config.iso_link) + "\n")
elif distro == "Windows":
if os.path.exists(sys_cfg_file):
config_file = open(sys_cfg_file, "a")
config_file.write("#start " + iso_basename(iso_link) + "\n")
config_file.write("LABEL " + iso_basename(iso_link) + "\n")
config_file.write("MENU LABEL " + iso_basename(iso_link) + "\n")
config_file.write("KERNEL chain.c32 hd0 1 ntldr=/bootmgr" + "\n")
config_file.write("#end " + iso_basename(iso_link) + "\n")
config_file.close()
elif distro == 'f4ubcd':
if os.path.exists(sys_cfg_file):
config_file = open(sys_cfg_file, "a")
config_file.write("#start " + iso_basename(iso_link) + "\n")
config_file.write("LABEL " + iso_basename(iso_link) + "\n")
config_file.write("MENU LABEL " + iso_basename(iso_link) + "\n")
config_file.write("KERNEL grub.exe" + "\n")
config_file.write('APPEND --config-file=/multibootusb/' + iso_basename(config.iso_link) + '/menu.lst' + "\n")
config_file.write("#end " + iso_basename(iso_link) + "\n")
config_file.close()
elif distro == 'kaspersky':
if os.path.exists(sys_cfg_file):
config_file = open(sys_cfg_file, "a")
config_file.write("#start " + iso_basename(iso_link) + "\n")
config_file.write("LABEL " + iso_basename(iso_link) + "\n")
config_file.write("MENU LABEL " + iso_basename(iso_link) + "\n")
config_file.write("CONFIG " + '/multibootusb/' + iso_basename(config.iso_link) + '/kaspersky.cfg' + "\n")
config_file.write("#end " + iso_basename(iso_link) + "\n")
config_file.close()
elif distro == 'grub4dos':
update_menu_lst()
elif distro == 'grub4dos_iso':
update_grub4dos_iso_menu()
else:
# admin.adminCmd(["mount", "-o", "remount,rw", config.usb_disk])
config_file = open(sys_cfg_file, "a")
config_file.write("#start " + iso_basename(iso_link) + "\n")
config_file.write("LABEL " + iso_basename(iso_link) + "\n")
config_file.write("MENU LABEL " + iso_basename(iso_link) + "\n")
if distro == "salix-live":
if os.path.exists(os.path.join(config.usb_mount, 'multibootusb', iso_basename(iso_link), 'boot', 'grub2-linux.img')):
config_file.write(
"LINUX " + '/multibootusb/' + iso_basename(iso_link) + '/boot/grub2-linux.img' + "\n")
else:
config_file.write("BOOT " + '/multibootusb/' + iso_basename(iso_link) + '/' + isolinux_bin_dir(iso_link).replace("\\", "/") + '/' + distro + '.bs' + "\n")
elif distro == "pclinuxos":
config_file.write("kernel " + '/multibootusb/' + iso_basename(iso_link) + '/isolinux/vmlinuz' + "\n")
config_file.write("append livecd=livecd root=/dev/rd/3 acpi=on vga=788 keyb=us vmalloc=256M nokmsboot "
"fromusb root=UUID=" + usb_uuid + " bootfromiso=/multibootusb/" +
iso_basename(iso_link) + "/" + iso_name(iso_link) + " initrd=/multibootusb/"
+ iso_basename(iso_link) + '/isolinux/initrd.gz' + "\n")
elif distro == "mentest":
config_file.write("kernel " + '/multibootusb/' + iso_basename(iso_link) + '/BOOT/MEMTEST.IMG\n')
elif distro == "sgrubd2":
config_file.write("LINUX memdisk\n")
config_file.write("INITRD " + "/multibootusb/" + iso_basename(iso_link) + '/' + iso_name(iso_link) + '\n')
config_file.write("APPEND iso\n")
elif distro == 'ReactOS':
config_file.write("COM32 mboot.c32" + '\n')
config_file.write("APPEND /loader/setupldr.sys" + '\n')
elif distro == 'pc-unlocker':
config_file.write("kernel ../ldntldr" + '\n')
config_file.write("append initrd=../ntldr" + '\n')
else:
if isolinux_bin_exist(config.iso_link) is True:
if distro == "generic":
distro_syslinux_install_dir = isolinux_bin_dir(iso_link)
if not isolinux_bin_dir(iso_link) == "/":
distro_sys_install_bs = os.path.join(usb_mount, isolinux_bin_dir(iso_link)) + '/' + distro + '.bs'
else:
distro_sys_install_bs = '/' + distro + '.bs'
else:
distro_syslinux_install_dir = install_dir
distro_syslinux_install_dir = distro_syslinux_install_dir.replace(usb_mount, '')
distro_sys_install_bs = distro_syslinux_install_dir + '/' + isolinux_bin_dir(iso_link) + '/' + distro + '.bs'
distro_sys_install_bs = "/" + distro_sys_install_bs.replace("\\", "/") # Windows path issue.
if config.syslinux_version == '3':
config_file.write("CONFIG /multibootusb/" + iso_basename(iso_link) + '/' + isolinux_bin_dir(iso_link).replace("\\", "/") + '/isolinux.cfg\n')
config_file.write("APPEND /multibootusb/" + iso_basename(iso_link) + '/' + isolinux_bin_dir(iso_link).replace("\\", "/") + '\n')
else:
config_file.write("BOOT " + distro_sys_install_bs.replace("//", "/") + "\n")
config_file.write("#end " + iso_basename(iso_link) + "\n")
config_file.close()
for dirpath, dirnames, filenames in os.walk(install_dir):
for f in filenames:
if f.endswith("isolinux.cfg") or f.endswith("ISOLINUX.CFG"):
if not os.path.exists(os.path.join(dirpath, "syslinux.cfg")) or not os.path.exists(os.path.join(dirpath, "SYSLINUX.CFG")):
shutil.copy2(os.path.join(dirpath, f), os.path.join(dirpath, "syslinux.cfg"))
def kaspersky_config(distro):
if distro == 'kaspersky':
return """
menu label Kaspersky Rescue Disk
kernel $INSTALL_DIR/boot/rescue
append root=live:UUID= live_dir=$INSTALL_DIR/rescue/LiveOS/ subdir=$INSTALL_DIR/rescue/LiveOS/ looptype=squashfs rootfstype=auto vga=791 init=/linuxrc loop=$INSTALL_DIR/rescue/LiveOS/squashfs.img initrd=$INSTALL_DIR/boot/rescue.igz lang=en udev liveimg splash quiet doscsi nomodeset
label text
menu label Kaspersky Rescue Disk - Text Mode
kernel $INSTALL_DIR/boot/rescue
append root=live:UUID= live_dir=$INSTALL_DIR/rescue/LiveOS/ subdir=$INSTALL_DIR/rescue/LiveOS/ rootfstype=auto vga=791 init=/linuxrc loop=/multiboot/rescue/LiveOS/squashfs.img initrd=$INSTALL_DIR/boot/rescue.igz SLUG_lang=en udev liveimg quiet nox shell noresume doscsi nomodeset
label hwinfo
menu label Kaspersky Hardware Info
kernel $INSTALL_DIR/boot/rescue
append root=live:UUID= live_dir=$INSTALL_DIR/rescue/LiveOS/ subdir=$INSTALL_DIR/rescue/LiveOS/ rootfstype=auto vga=791 init=/linuxrc loop=$INSTALL_DIR/rescue/LiveOS/squashfs.img initrd=$INSTALL_DIR/boot/rescue.igz SLUG_lang=en udev liveimg quiet softlevel=boot nox hwinfo noresume doscsi nomodeset """
def update_menu_lst():
sys_cfg_file = os.path.join(config.usb_mount, "multibootusb", "syslinux.cfg")
install_dir = os.path.join(config.usb_mount, "multibootusb", iso_basename(config.iso_link))
menu_lst = iso_menu_lst_path(config.iso_link).replace("\\", "/")
with open(sys_cfg_file, "a") as f:
f.write("#start " + iso_basename(config.iso_link) + "\n")
f.write("LABEL " + iso_basename(config.iso_link) + "\n")
f.write("MENU LABEL " + iso_basename(config.iso_link) + "\n")
f.write("KERNEL grub.exe" + "\n")
f.write('APPEND --config-file=/' + menu_lst + "\n")
f.write("#end " + iso_basename(config.iso_link) + "\n")
def update_grub4dos_iso_menu():
sys_cfg_file = os.path.join(config.usb_mount, "multibootusb", "syslinux.cfg")
install_dir = os.path.join(config.usb_mount, "multibootusb", iso_basename(config.iso_link))
menu_lst_file = os.path.join(install_dir, 'menu.lst')
with open(menu_lst_file, "w") as f:
f.write("title Boot " + iso_name(config.iso_link) + "\n")
f.write("find --set-root --ignore-floppies --ignore-cd /multibootusb/" + iso_basename(config.iso_link) + '/'
+ iso_name(config.iso_link) + "\n")
f.write("map --heads=0 --sectors-per-track=0 /multibootusb/" + iso_basename(config.iso_link)
+ '/' + iso_name(config.iso_link) + ' (hd32)' + "\n")
f.write("map --hook" + "\n")
f.write("chainloader (hd32)")
with open(sys_cfg_file, "a") as f:
f.write("#start " + iso_basename(config.iso_link) + "\n")
f.write("LABEL " + iso_basename(config.iso_link) + "\n")
f.write("MENU LABEL " + iso_basename(config.iso_link) + "\n")
f.write("KERNEL grub.exe" + "\n")
f.write('APPEND --config-file=/multibootusb/' + iso_basename(config.iso_link) + '/' + iso_name(config.iso_link) + "\n")
f.write("#end " + iso_basename(config.iso_link) + "\n")

@ -0,0 +1,417 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Name: usb.py
# Purpose: Module to list USB devices and get details under Linux and Windows
# 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
import sys
import platform
import os
import shutil
import collections
import ctypes
import subprocess
from . import gen
if platform.system() == 'Linux':
from . import udisks
u = udisks.get_udisks(ver=None)
if platform.system() == 'Windows':
import psutil
import win32com.client
import win32com.client
import wmi
import pythoncom
def is_block(usb_disk):
"""
Function to detect if the USB is block device
:param usb_disk: USB disk path
:return: True is devie is block device else False
"""
import stat
if platform.system() == 'Linux':
if len(usb_disk) != 9:
return False
elif platform.system() == 'Windows':
if len(usb_disk) != 2:
return False
else:
return True
try:
mode = os.stat(usb_disk).st_mode
gen.log(mode)
gen.log(stat.S_ISBLK(mode))
except:
return False
return stat.S_ISBLK(mode)
def disk_usage(mount_path):
"""
Return disk usage statistics about the given path as a (total, used, free)
namedtuple. Values are expressed in bytes.
"""
# Author: Giampaolo Rodola' <g.rodola [AT] gmail [DOT] com>
# License: MIT
_ntuple_diskusage = collections.namedtuple('usage', 'total used free')
if platform.system() == "Linux":
st = os.statvfs(mount_path)
free = st.f_bavail * st.f_frsize
total = st.f_blocks * st.f_frsize
used = (st.f_blocks - st.f_bfree) * st.f_frsize
return _ntuple_diskusage(total, used, free)
elif platform.system() == "Windows":
_, total, free = ctypes.c_ulonglong(), ctypes.c_ulonglong(), \
ctypes.c_ulonglong()
if sys.version_info >= (3,) or isinstance(mount_path, unicode):
fun = ctypes.windll.kernel32.GetDiskFreeSpaceExW
else:
fun = ctypes.windll.kernel32.GetDiskFreeSpaceExA
ret = fun(mount_path, ctypes.byref(_), ctypes.byref(total), ctypes.byref(free))
if ret == 0:
raise ctypes.WinError()
used = total.value - free.value
return _ntuple_diskusage(total.value, used, free.value)
else:
raise NotImplementedError("Platform not supported.")
def list(partition=1, fixed=None):
"""
List inserted USB devices.
:return: USB devices as list.
"""
devices = []
if platform.system() == "Linux":
from . import pyudev
import dbus
try:
# pyudev is good enough to detect USB devices on modern Linux machines.
gen.log("Using pyudev for detecting USB drives...")
context = pyudev.Context()
if fixed is None:
for device in context.list_devices(subsystem='block', DEVTYPE='partition',
ID_FS_USAGE="filesystem", ID_TYPE="disk",
ID_BUS="usb"):
# if device['ID_BUS'] == "usb" and device['DEVTYPE'] == "partition":
if device.get('ID_BUS') in ("usb", "scsi") and device.get('DEVTYPE') == "partition":
# gen.log(device['DEVNAME'])
devices.append(str(device['DEVNAME']))
else:
for device in context.list_devices(subsystem='block', DEVTYPE='partition'):
devices.append(str(device['DEVNAME']))
except:
bus = dbus.SystemBus()
try:
# You should come here only if your system does'nt have udev installed.
# We will use udiskd2 for now.
gen.log("Falling back to Udisks2..")
ud_manager_obj = bus.get_object(
'org.freedesktop.UDisks2', '/org/freedesktop/UDisks2')
ud_manager = dbus.Interface(
ud_manager_obj, 'org.freedesktop.DBus.ObjectManager')
for k, v in ud_manager.GetManagedObjects().iteritems():
drive_info = v.get('org.freedesktop.UDisks2.Block', {})
if drive_info.get('IdUsage') == "filesystem" and not drive_info.get(
'HintSystem') and not drive_info.get('ReadOnly'):
device = drive_info.get('Device')
device = bytearray(device).replace(
b'\x00', b'').decode('utf-8')
devices.append(device)
except:
try:
# You must be using really old distro. Otherwise, the code
# should not reach here.
gen.log("Falling back to Udisks1...")
ud_manager_obj = bus.get_object(
"org.freedesktop.UDisks", "/org/freedesktop/UDisks")
ud_manager = dbus.Interface(
ud_manager_obj, 'org.freedesktop.UDisks')
for dev in ud_manager.EnumerateDevices():
device_obj = bus.get_object(
"org.freedesktop.UDisks", dev)
device_props = dbus.Interface(
device_obj, dbus.PROPERTIES_IFACE)
if device_props.Get('org.freedesktop.UDisks.Device',
"DriveConnectionInterface") == "usb" and device_props.Get(
'org.freedesktop.UDisks.Device', "DeviceIsPartition"):
if device_props.Get('org.freedesktop.UDisks.Device', "DeviceIsMounted"):
device_file = device_props.Get(
'org.freedesktop.UDisks.Device', "DeviceFile")
devices.append(device_file)
except:
gen.log("No USB device found...")
elif platform.system() == "Windows":
if fixed is not None:
for drive in psutil.disk_partitions():
if 'cdrom' in drive.opts or drive.fstype == '':
# Skip cdrom drives or the disk with no filesystem
continue
devices.append(drive[0][:-1])
else:
try:
# Try new method using psutil. It should also detect USB 3.0 (but not tested by me)
for drive in psutil.disk_partitions():
if 'cdrom' in drive.opts or drive.fstype == '':
# Skip cdrom drives or the disk with no filesystem
continue
if 'removable' in drive.opts:
devices.append(drive[0][:-1])
except:
# Revert back to old method if psutil fails (which is unlikely)
oFS = win32com.client.Dispatch("Scripting.FileSystemObject")
oDrives = oFS.Drives
for drive in oDrives:
if drive.DriveType == 1 and drive.IsReady:
devices.append(drive)
if devices:
return devices
else:
gen.log("No USB device found...")
return None
def details_udev(usb_disk_part):
"""
Get details of USB partition using udev
"""
if platform.system() == "Linux":
from . import pyudev
"""
Try with PyUdev to get the details of USB disks.
This is the easiest and reliable method to find USB details.
Also, it is a standalone package and no dependencies are required.
"""
# gen.log "Using PyUdev for detecting USB details..."
context = pyudev.Context()
for device in context.list_devices(subsystem='block', DEVTYPE='partition',
ID_FS_USAGE="filesystem", ID_TYPE="disk",
ID_BUS="usb"):
fdisk_cmd_out = subprocess.check_output('fdisk -l ' + usb_disk_part, shell=True)
if b'Extended' in fdisk_cmd_out:
mount_point = ''
uuid = 'No_UUID'
file_system = 'No_FS'
vendor = 'No_Vendor'
model = 'No_Model'
label = 'No_Label'
elif device.get('ID_BUS') in ("usb", "scsi") and device.get('DEVTYPE') == "partition":
if (device['DEVNAME']) == usb_disk_part:
uuid = str(device['ID_FS_UUID'])
file_system = str(device['ID_FS_TYPE'])
try:
label = str(device['ID_FS_LABEL'])
except:
label = "No_Label"
mount_point = u.mount(usb_disk_part)
# mount_point = os.popen('findmnt -nr -o target -S %s' % usb_disk_part).read().strip()
# Convert the hex string of space to empty space.
mount_point = mount_point.replace('\\x20', ' ')
try:
vendor = str(device['ID_VENDOR'])
except:
vendor = str('No_Vendor')
try:
model = str(device['ID_MODEL'])
except:
model = str('No_Model')
if not mount_point == '':
size_total = shutil.disk_usage(mount_point)[0]
size_used = shutil.disk_usage(mount_point)[1]
size_free = shutil.disk_usage(mount_point)[2]
else:
size_total = str('No_Mount')
size_used = str('No_Mount')
size_free = str('No_Mount')
mount_point = str('No_Mount')
return {'uuid': uuid, 'file_system': file_system, 'label': label, 'mount_point': mount_point,
'size_total': size_total, 'size_used': size_used, 'size_free': size_free,
'vendor': vendor, 'model': model}
def details_udisks2(usb_disk_part):
"""
Get details of USB disk detail.
usb_disk_part: It is the partition of an USB removable disk.
"""
import dbus
bus = dbus.SystemBus()
bd = bus.get_object('org.freedesktop.UDisks2', '/org/freedesktop/UDisks2/block_devices%s'%usb_disk_part[4:])
device = bd.Get('org.freedesktop.UDisks2.Block', 'Device', dbus_interface='org.freedesktop.DBus.Properties')
device = bytearray(device).replace(b'\x00', b'').decode('utf-8')
uuid = bd.Get('org.freedesktop.UDisks2.Block', 'IdUUID', dbus_interface='org.freedesktop.DBus.Properties')
file_system = bd.Get('org.freedesktop.UDisks2.Block', 'IdType', dbus_interface='org.freedesktop.DBus.Properties')
mount_point = bd.Get('org.freedesktop.UDisks2.Filesystem', 'MountPoints', dbus_interface='org.freedesktop.DBus.Properties')
if mount_point:
# mount_point = str(bytearray(mount_point[0]).decode('utf-8').replace(b'\x00', b''))
mount_point = bytearray(mount_point[0]).replace(b'\x00', b'').decode('utf-8')
else:
try:
mount_point = u.mount(usb_disk_part)
except:
mount_point = "No_Mount"
try:
label = bd.Get('org.freedesktop.UDisks2.Block', 'IdLabel', dbus_interface='org.freedesktop.DBus.Properties')
except:
label = "No_Label"
usb_drive_id = (bd.Get('org.freedesktop.UDisks2.Block', 'Drive', dbus_interface='org.freedesktop.DBus.Properties'))
bd1 = bus.get_object('org.freedesktop.UDisks2', usb_drive_id)
try:
vendor = bd1.Get('org.freedesktop.UDisks2.Drive', 'Vendor', dbus_interface='org.freedesktop.DBus.Properties')
except:
vendor = str('No_Vendor')
try:
model = bd1.Get('org.freedesktop.UDisks2.Drive', 'Model', dbus_interface='org.freedesktop.DBus.Properties')
except:
model = str('No_Model')
if not mount_point == "No_Mount":
size_total = shutil.disk_usage(mount_point)[0]
size_used = shutil.disk_usage(mount_point)[1]
size_free = shutil.disk_usage(mount_point)[2]
else:
size_total = str('No_Mount')
size_used = str('No_Mount')
size_free = str('No_Mount')
return {'uuid': uuid, 'file_system': file_system, 'label': label, 'mount_point': mount_point,
'size_total': size_total, 'size_used': size_used, 'size_free': size_free,
'vendor': vendor, 'model': model}
def bytes2human(n):
"""
Convert the size to human readable format
Authored by 'Giampaolo Rodolà' and original link is:-
http://code.activestate.com/recipes/577972-disk-usage/
"""
try:
n = int(n)
except:
return 'Unknown'
symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
prefix = {}
for i, s in enumerate(symbols):
prefix[s] = 1 << (i + 1) * 10
for s in reversed(symbols):
if n >= prefix[s]:
value = float(n) / prefix[s]
return '%.1f%s' % (value, s)
return "%sB" % n
def win_disk_details(disk_drive):
"""
Populate and get details of an USB disk under windows. Minimum required windows version is Vista.
:param disk_drive: USB disk like 'G:'
:return: See the details(usb_disk_part) function for return values.
"""
pythoncom.CoInitialize()
vendor = 'Not_Found'
model = 'Not_Found'
selected_usb_part = str(disk_drive)
oFS = win32com.client.Dispatch("Scripting.FileSystemObject")
d = oFS.GetDrive(oFS.GetDriveName(oFS.GetAbsolutePathName(selected_usb_part)))
selected_usb_device = d.DriveLetter
label = (d.VolumeName).strip()
if not label.strip():
label = "No_label"
mount_point = selected_usb_device + ":\\"
serno = "%X" % (int(d.SerialNumber) & 0xFFFFFFFF)
uuid = serno[:4] + '-' + serno[4:]
file_system = (d.FileSystem).strip()
size_total = shutil.disk_usage(mount_point)[0]
size_used = shutil.disk_usage(mount_point)[1]
size_free = shutil.disk_usage(mount_point)[2]
'''
# The below code works only from vista and above. I have removed it as many people reported that the software
# was not working under windows xp. Even then, it is significantly slow if 'All Drives' option is checked.
# Removing the code doesn't affect the functionality as it is only used to find vendor id and model of the drive.
c = wmi.WMI()
for physical_disk in c.Win32_DiskDrive(InterfaceType="USB"):
for partition in physical_disk.associators("Win32_DiskDriveToDiskPartition"):
for logical_disk in partition.associators("Win32_LogicalDiskToPartition"):
if logical_disk.Caption == disk_drive:
vendor = (physical_disk.PNPDeviceID.split('&VEN_'))[1].split('&PROD_')[0]
model = (physical_disk.PNPDeviceID.split('&PROD_'))[1].split('&REV_')[0]
'''
return {'uuid': uuid, 'file_system': file_system, 'label': label, 'mount_point': mount_point,
'size_total': size_total, 'size_used': size_used, 'size_free': size_free,
'vendor': vendor, 'model': model}
def details(usb_disk_part):
"""
Populate and get details of an USB disk.
:param usb_disk_part: USB disk. Example.. "/dev/sdb1" on Linux and "D:\" on Windows.
:return: label == > returns name/label of an inserted USB device.
mount_point == > returns mount path of an inserted USB device.
uuid == > returns uuid of an inserted USB device.
file_system == > returns type of filesystem of an inserted USB device.
device == > returns device path of an inserted USB device.
size_total == > returns total size in MB/GB of an inserted USB device.
size_free == > returns free size in MB/GB of an inserted USB device.
size_used == > returns used size in MB/GB of an inserted USB device.
vendor == > returns the name of the manufacturer.
model == > returns the model name of the USB.
"""
if platform.system() == 'Linux':
try:
udev = details_udev(usb_disk_part)
uuid = udev['uuid']
file_system = udev['file_system']
label = udev['label']
mount_point = udev['mount_point']
size_total = udev['size_total']
size_used = udev['size_used']
size_free = udev['size_free']
vendor = udev['vendor']
model = udev['model']
except:
udisks2 = details_udisks2(usb_disk_part)
uuid = udisks2['uuid']
file_system = udisks2['file_system']
label = udisks2['label']
mount_point = udisks2['mount_point']
size_total = udisks2['size_total']
size_used = udisks2['size_used']
size_free = udisks2['size_free']
vendor = udisks2['vendor']
model = udisks2['model']
elif platform.system() == 'Windows':
win_details = win_disk_details(usb_disk_part)
uuid = win_details['uuid']
file_system = win_details['file_system']
label = win_details['label']
mount_point = win_details['mount_point']
size_total = win_details['size_total']
size_used = win_details['size_used']
size_free = win_details['size_free']
vendor = win_details['vendor']
model = win_details['model']
return {'uuid': uuid, 'file_system': file_system, 'label': label, 'mount_point': mount_point,
'size_total': size_total, 'size_used': size_used, 'size_free': size_free,
'vendor': vendor, 'model': model}
if __name__ == '__main__':
usb_devices = list()
if usb_devices is not None:
for dev in usb_devices:
gen.log(details(dev))

@ -0,0 +1,53 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Name: setup.py
# Purpose: Module to create packages or install multibootusb package from source
# 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 distutils.core import setup
#from setuptools import setup, find_packages
import os
import sys
from scripts.gen import mbusb_version
Version = mbusb_version()
print(Version)
setup(
name='multibootusb',
version=Version,
packages=['scripts', 'scripts.pyudev', 'scripts.pyudev.device', 'scripts.pyudev._ctypeslib', 'scripts.pyudev._os',
'scripts.gui', 'scripts.progressbar'],
#packages=find_packages(),
scripts=['multibootusb', 'multibootusb-pkexec'],
platforms=['Linux'],
url='http://multibootusb.org/',
license='General Public License (GPL)',
author='Sundar',
author_email='feedback.multibootusb@gmail.com',
description='Create multi boot live Linux on a USB disk...',
long_description='multibootusb is an advanced cross-platform application for installing/uninstalling Linux operating systems on to a single USB flash drives.',
data_files=[("/usr/share/applications", ["data/multibootusb.desktop"]),
('/usr/share/pixmaps', ["data/tools/multibootusb.png"]),
('/usr/share/polkit-1/actions/', ['org.debian.pkexec.run-multibootusb.policy']),
('/usr/share/multibootusb/data/tools', ["data/tools/mbr.bin"]),
('/usr/share/multibootusb/data', ["data/version.txt"]),
('/usr/share/multibootusb/data/tools', ["data/tools/multibootusb.png"]),
('/usr/share/multibootusb/data/tools/dd', ["data/tools/dd/dd.exe"]),
('/usr/share/multibootusb/data/tools/dd', ["data/tools/dd/diskio.dll"]),
('/usr/share/multibootusb/data/tools/mkfs', ["data/tools/mkfs/mke2fs.exe"]),
('/usr/share/multibootusb/data/tools/multibootusb', ["data/multibootusb/chain.c32"]),
('/usr/share/multibootusb/data/tools/multibootusb', ["data/multibootusb/bg.png"]),
('/usr/share/multibootusb/data/tools/multibootusb', ["data/multibootusb/extlinux.cfg"]),
('/usr/share/multibootusb/data/tools/multibootusb', ["data/multibootusb/grub.exe"]),
('/usr/share/multibootusb/data/tools/multibootusb', ["data/multibootusb/memdisk"]),
('/usr/share/multibootusb/data/tools/multibootusb', ["data/multibootusb/menu.c32"]),
('/usr/share/multibootusb/data/tools/multibootusb', ["data/multibootusb/menu.lst"]),
('/usr/share/multibootusb/data/tools/multibootusb', ["data/multibootusb/syslinux.cfg"]),
('/usr/share/multibootusb/data/tools/multibootusb', ["data/multibootusb/vesamenu.c32"]),
('/usr/share/multibootusb/data/tools/syslinux', ["data/tools/syslinux/syslinux_modules.zip"]),
('/usr/share/multibootusb/data/tools/syslinux', ["data/tools/syslinux/syslinux_linux.zip"]),
('/usr/share/multibootusb/data/tools/syslinux', ["data/tools/syslinux/syslinux_linux_64.zip"]),
('/usr/share/multibootusb/data/tools/syslinux', ["data/tools/syslinux/syslinux_windows.zip"])]
)

@ -0,0 +1,19 @@
Format: 3.0 (quilt)
Source: multibootusb
Binary: python3-multibootusb
Architecture: all
Version: 8.5.0-1
Maintainer: Sundar <feedback.multibootusb@gmail.com>
Standards-Version: 3.9.1
Build-Depends: python3-all, debhelper (>= 9)
Package-List:
python3-multibootusb deb system optional arch=all
Checksums-Sha1:
e1295668fecdb279e3cdbc53078f08cadf2a4948 4544191 multibootusb_8.5.0.orig.tar.gz
0a0c598babb9369395d013ffafe88ffaf2b812bb 856 multibootusb_8.5.0-1.debian.tar.xz
Checksums-Sha256:
7dd7a86b1a980a044045e224aa3145ef6177089a5e7601b6ac12175c86a3d1fe 4544191 multibootusb_8.5.0.orig.tar.gz
38df7e3104f4ea0f14318854131e05ef0693bcfc4295d8fda4b0e5cdaaad1868 856 multibootusb_8.5.0-1.debian.tar.xz
Files:
1eeb4bec75be17aaa260bc55088ab8ca 4544191 multibootusb_8.5.0.orig.tar.gz
a291c997070dd3133d0e560d7e27acec 856 multibootusb_8.5.0-1.debian.tar.xz

@ -0,0 +1,10 @@
Metadata-Version: 1.0
Name: multibootusb
Version: 8.5.0
Summary: Create multi boot live Linux on a USB disk...
Home-page: http://multibootusb.org/
Author: Sundar
Author-email: feedback.multibootusb@gmail.com
License: General Public License (GPL)
Description: multibootusb is an advanced cross-platform application for installing/uninstalling Linux operating systems on to a single USB flash drives.
Platform: Linux

@ -0,0 +1,8 @@
[Desktop Entry]
Type=Application
Name=multibootusb
Comment=Install multiple Linux Operating System on USB
Icon=multibootusb
Exec=multibootusb-pkexec
Categories=System;
StartupNotify=true

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -0,0 +1,35 @@
# This file is created by MultiBootUSB.
default vesamenu.c32
prompt 0
menu title MultiBootUSB
MENU BACKGROUND /multibootusb/bg.png
TIMEOUT 300
MENU WIDTH 80
MENU MARGIN 10
MENU PASSWORDMARGIN 3
MENU ROWS 12
MENU TABMSGROW 18
MENU CMDLINEROW 18
MENU ENDROW -1
MENU PASSWORDROW 11
MENU TIMEOUTROW 20
MENU HELPMSGROW 22
MENU HELPMSGENDROW -1
MENU HIDDENROW -2
MENU HSHIFT 0
MENU VSHIFT 0
MENU COLOR border 30;44 #40ffffff #a0000000 std
MENU COLOR title 1;36;44 #9033ccff #a0000000 std
MENU COLOR sel 7;37;40 #e0ffffff #20ffffff all
MENU COLOR unsel 37;44 #50ffffff #a0000000 std
MENU COLOR help 37;40 #c0ffffff #a0000000 std
MENU COLOR timeout_msg 37;40 #80ffffff #00000000 std
MENU COLOR timeout 1;37;40 #c0ffffff #00000000 std
MENU COLOR msg07 37;40 #90ffffff #a0000000 std
MENU COLOR tabmsg 31;40 #30ffffff #00000000 std
label Boot from Hard Drive
MENU LABEL Boot from Hard Disk
KERNEL chain.c32
APPEND hd1
MENU DEFAULT

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save