701ced5ddb
Apply diff anaconda-21.48.21-1..anaconda-22.20.13-1
832 lines
26 KiB
Python
832 lines
26 KiB
Python
#!/usr/bin/python2
|
|
#
|
|
# Copyright (C) 2013 by Red Hat, Inc. All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program 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 General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# Author(s): Brian C. Lane <bcl@brianlane.com>
|
|
#
|
|
"""
|
|
Driver Update Disk UI
|
|
|
|
/tmp/dd_modules is a copy of /proc/modules at startup time
|
|
/tmp/dd_args is a parsed list of the inst.dd= cmdline args, and may include
|
|
'dd' or 'inst.dd' if it was specified without arguments
|
|
/tmp/dd_args_ks is the same format, but skips processing existing OEMDRV devices.
|
|
|
|
Pass a path and it will install the driver rpms from the path before checking
|
|
for new OEMDRV devices.
|
|
|
|
Repositories for installed drivers are copied into /run/install/DD-X where X
|
|
starts at 1 and increments for each repository.
|
|
|
|
Selected driver package names are saved in /run/install/dd_packages
|
|
|
|
Anaconda uses the repository and package list to install the same set of drivers
|
|
to the target system.
|
|
"""
|
|
import logging
|
|
from logging.handlers import SysLogHandler
|
|
import sys
|
|
import os
|
|
import subprocess
|
|
import time
|
|
import glob
|
|
import readline # pylint:disable=unused-import
|
|
|
|
log = logging.getLogger("DD")
|
|
|
|
|
|
class RunCmdError(Exception):
|
|
""" Raised when run_cmd gets a non-zero returncode
|
|
"""
|
|
pass
|
|
|
|
|
|
def run_cmd(cmd):
|
|
""" Run a command, collect stdout and the returncode. stderr is ignored.
|
|
|
|
:param cmd: command and arguments to run
|
|
:type cmd: list of strings
|
|
:returns: exit code and stdout from the command
|
|
:rtype: (int, string)
|
|
:raises: OSError if the cmd doesn't exist, RunCmdError if the rc != 0
|
|
"""
|
|
try:
|
|
with open("/dev/null", "w") as fd_null:
|
|
log.debug(" ".join(cmd))
|
|
proc = subprocess.Popen(cmd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=fd_null)
|
|
out = proc.communicate()[0]
|
|
if out:
|
|
for line in out.splitlines():
|
|
log.debug(line)
|
|
except OSError as e:
|
|
log.error("Error running %s: %s", cmd[0], e.strerror)
|
|
raise
|
|
if proc.returncode:
|
|
log.debug("%s returned %s", cmd[0], proc.returncode)
|
|
raise RunCmdError()
|
|
return (proc.returncode, out)
|
|
|
|
|
|
def oemdrv_list():
|
|
""" Get a list of devices labeled as OEMDRV
|
|
|
|
:returns: list of devices
|
|
:rtype: list
|
|
"""
|
|
try:
|
|
outlines = run_cmd(["blkid", "-t", "LABEL=OEMDRV", "-o", "device"])[1]
|
|
except (OSError, RunCmdError):
|
|
# Nothing with that label
|
|
return []
|
|
else:
|
|
return outlines.splitlines()
|
|
|
|
|
|
def get_dd_args():
|
|
""" Get the dd arguments from /tmp/dd_args or /tmp/dd_args_ks
|
|
|
|
:returns: List of arguments
|
|
:rtype: list of strings
|
|
"""
|
|
net_protocols = ["http", "https", "ftp", "nfs", "nfs4"]
|
|
args = []
|
|
for dd_args_file in ["/tmp/dd_args", "/tmp/dd_args_ks"]:
|
|
if not os.path.exists(dd_args_file):
|
|
continue
|
|
try:
|
|
dd_args = open(dd_args_file, "r").readline().split()
|
|
except IOError:
|
|
return []
|
|
|
|
# skip dd args that need networking
|
|
args.extend(filter(lambda x: x.split(":")[0].lower() not in net_protocols, dd_args))
|
|
return args
|
|
|
|
|
|
def is_interactive():
|
|
""" Determine if the user requested interactive driver selection
|
|
|
|
:returns: True if 'dd' or 'inst.dd' included in /tmp/dd_args False if not
|
|
:rtype: bool
|
|
"""
|
|
dd_args = get_dd_args()
|
|
if "dd" in dd_args or "inst.dd" in dd_args:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def umount(device):
|
|
""" Unmount the device
|
|
|
|
:param device: Device or mountpoint to unmount
|
|
:type device: string
|
|
:returns: None
|
|
"""
|
|
if not device:
|
|
return
|
|
|
|
try:
|
|
run_cmd(["umount", device])
|
|
except (OSError, RunCmdError):
|
|
pass
|
|
|
|
|
|
def mount_device(device, mnt="/media/DD/"):
|
|
""" Mount a device and check to see if it really is a driver disk
|
|
|
|
:param device: path to device to mount
|
|
:type device: string
|
|
:param mnt: path to mount the device on
|
|
:type mnt: string
|
|
:returns: True if it is a DD, False if not
|
|
:rtype: bool
|
|
|
|
It is unmounted if it is not a DD and left mounted if it is.
|
|
"""
|
|
try:
|
|
run_cmd(["mount", device, mnt])
|
|
except (OSError, RunCmdError):
|
|
return False
|
|
return True
|
|
|
|
|
|
def copy_repo(dd_path, dest_prefix):
|
|
""" Copy the current arch's repository to a unique destination
|
|
|
|
:param dd_path: Path to the driver repo directory
|
|
:type dd_path: string
|
|
:param dest_prefix: Destination directory prefix, a number is added
|
|
:type dest_prefix: string
|
|
:returns: None
|
|
|
|
The destination directory names are in the order that the drivers
|
|
were loaded, starting from 1
|
|
"""
|
|
suffix = 1
|
|
while os.path.exists(dest_prefix+str(suffix)):
|
|
suffix += 1
|
|
dest = dest_prefix+str(suffix)
|
|
os.makedirs(dest)
|
|
try:
|
|
run_cmd(["cp", "-ar", dd_path, dest])
|
|
except (OSError, RunCmdError):
|
|
pass
|
|
|
|
|
|
def copy_file(src, dest):
|
|
""" Copy a file
|
|
|
|
:param src: Source file
|
|
:type src: string
|
|
:param dest: Destination file
|
|
:type dest: string
|
|
:returns: None
|
|
"""
|
|
try:
|
|
run_cmd(["cp", "-a", src, dest])
|
|
except (OSError, RunCmdError):
|
|
pass
|
|
|
|
|
|
def move_file(src, dest):
|
|
""" Move a file
|
|
|
|
:param src: Source file
|
|
:type src: string
|
|
:param dest: Destination file
|
|
:type dest: string
|
|
:returns: None
|
|
"""
|
|
try:
|
|
run_cmd(["mv", "-f", src, dest])
|
|
except (OSError, RunCmdError):
|
|
pass
|
|
|
|
|
|
def find_dd(mnt="/media/DD"):
|
|
""" Find all suitable DD repositories under a path
|
|
|
|
:param mnt: Top of the directory tree to search
|
|
:type mnt: string
|
|
:returns: list of DD repositories
|
|
:rtype: list
|
|
"""
|
|
dd_repos = []
|
|
arch = os.uname()[4]
|
|
for root, dirs, files in os.walk(mnt, followlinks=True):
|
|
if "rhdd3" in files and "rpms" in dirs and \
|
|
os.path.exists(root+"/rpms/"+arch):
|
|
dd_repos.append(root+"/rpms/"+arch)
|
|
log.debug("Found repos - %s", " ".join(dd_repos))
|
|
return dd_repos
|
|
|
|
|
|
def get_module_set(fname):
|
|
""" Read a module list and return a set of the names
|
|
|
|
:param fname: Full path to filename
|
|
:type fname: string
|
|
:returns: set of the module names
|
|
"""
|
|
modules = set()
|
|
if os.path.exists(fname):
|
|
with open(fname, "r") as f:
|
|
for line in f:
|
|
mod_args = line.strip().split()
|
|
if mod_args:
|
|
modules.update([mod_args[0]])
|
|
return modules
|
|
|
|
def to_modname(modfile):
|
|
return os.path.basename(modfile)[:-3].replace('-','_')
|
|
|
|
def reload_modules(newfiles):
|
|
""" Reload new module versions from /lib/modules/<kernel>/updates/
|
|
"""
|
|
try:
|
|
run_cmd(["depmod", "-a"])
|
|
except (OSError, RunCmdError):
|
|
pass
|
|
|
|
# Make a list of modules added since startup
|
|
startup_modules = get_module_set("/tmp/dd_modules")
|
|
current_modules = get_module_set("/proc/modules")
|
|
new_modules = current_modules.difference(startup_modules)
|
|
log.debug("new_modules = %s", " ".join(new_modules))
|
|
|
|
# And a list of modules contained in the disk we just extracted
|
|
dd_modules = set(to_modname(f) for f in newfiles if f.endswith(".ko"))
|
|
# TODO: what modules do we unload when there's new firmware?
|
|
log.debug("dd_modules = %s", " ".join(dd_modules))
|
|
new_modules.update(dd_modules)
|
|
|
|
# I think we can just iterate once using modprobe -r to remove unused deps
|
|
for module in new_modules:
|
|
try:
|
|
run_cmd(["modprobe", "-r", module])
|
|
except (OSError, RunCmdError):
|
|
pass
|
|
|
|
time.sleep(2)
|
|
|
|
# Reload the modules, using the new versions from /lib/modules/<kernel>/updates/
|
|
try:
|
|
run_cmd(["udevadm", "trigger"])
|
|
except (OSError, RunCmdError):
|
|
pass
|
|
|
|
|
|
class Driver(object):
|
|
def __init__(self):
|
|
self.source = ""
|
|
self.name = ""
|
|
self.flags = ""
|
|
self.description = []
|
|
self.selected = False
|
|
|
|
@property
|
|
def args(self):
|
|
return ["--%s" % a for a in self.flags.split()]
|
|
|
|
@property
|
|
def rpm(self):
|
|
return self.source
|
|
|
|
|
|
def fake_drivers(num):
|
|
""" Generate a number of fake drivers for testing
|
|
"""
|
|
drivers = []
|
|
for i in range(0, num):
|
|
d = Driver()
|
|
d.source = "driver-%d" % i
|
|
d.flags = "modules"
|
|
drivers.append(d)
|
|
return drivers
|
|
|
|
|
|
def dd_list(dd_path, kernel_ver=None, anaconda_ver=None):
|
|
""" Build a list of the drivers in the directory
|
|
|
|
:param dd_path: Path to the driver repo
|
|
:type dd_path: string
|
|
:returns: list of drivers
|
|
:rtype: Driver object
|
|
|
|
By default none of the drivers are selected
|
|
"""
|
|
if not kernel_ver:
|
|
kernel_ver = os.uname()[2]
|
|
if not anaconda_ver:
|
|
anaconda_ver = "19.0"
|
|
|
|
try:
|
|
outlines = run_cmd(["dd_list", "-k", kernel_ver, "-a", anaconda_ver, "-d", dd_path])[1]
|
|
except (OSError, RunCmdError):
|
|
return []
|
|
|
|
# Output format is:
|
|
# source rpm\n
|
|
# name\n
|
|
# flags\n
|
|
# description (multi-line)\n
|
|
# ---\n
|
|
drivers = []
|
|
new_driver = Driver()
|
|
line_idx = 0
|
|
for line in outlines.splitlines():
|
|
log.debug(line)
|
|
if line == "---":
|
|
drivers.append(new_driver)
|
|
new_driver = Driver()
|
|
line_idx = 0
|
|
elif line_idx == 0:
|
|
new_driver.source = line
|
|
line_idx += 1
|
|
elif line_idx == 1:
|
|
new_driver.name = line
|
|
line_idx += 1
|
|
elif line_idx == 2:
|
|
new_driver.flags = line
|
|
line_idx += 1
|
|
elif line_idx == 3:
|
|
new_driver.description.append(line)
|
|
|
|
return drivers
|
|
|
|
|
|
def dd_extract(driver, dest_path="/updates/", kernel_ver=None):
|
|
""" Extract a driver rpm to a destination path
|
|
|
|
:param driver: Driver to extract
|
|
:type driver: Driver object
|
|
:param dest_path: Top directory of the destination path
|
|
:type dest_path: string
|
|
:returns: list of paths to extracted firmware and modules
|
|
|
|
This extracts the driver's files into 'dest_path' (which defaults
|
|
to /updates/ so that the normal live updates handling will overlay
|
|
any binary or library updates onto the initrd automatically.
|
|
"""
|
|
if not kernel_ver:
|
|
kernel_ver = os.uname()[2]
|
|
|
|
cmd = ["dd_extract", "-k", kernel_ver]
|
|
cmd += driver.args
|
|
cmd += ["--rpm", driver.rpm, "--directory", dest_path]
|
|
log.info("Extracting files from %s", driver.rpm)
|
|
|
|
# make sure the to be used directory exists
|
|
if not os.path.isdir(dest_path):
|
|
os.makedirs(dest_path)
|
|
|
|
try:
|
|
run_cmd(cmd)
|
|
except (OSError, RunCmdError):
|
|
log.error("dd_extract failed, skipped %s", driver.rpm)
|
|
return
|
|
|
|
# Create the destination directories
|
|
initrd_updates = "/lib/modules/" + os.uname()[2] + "/updates/"
|
|
ko_updates = dest_path + initrd_updates
|
|
initrd_firmware = "/lib/firmware/updates/"
|
|
firmware_updates = dest_path + initrd_firmware
|
|
for d in (initrd_updates, ko_updates, initrd_firmware, firmware_updates):
|
|
if not os.path.exists(d):
|
|
os.makedirs(d)
|
|
|
|
filelist = []
|
|
|
|
# Copy *.ko files over to /updates/lib/modules/<kernel>/updates/
|
|
for root, _dirs, files in os.walk(dest_path+"/lib/modules/"):
|
|
if root.endswith("/updates") and os.path.isdir(root):
|
|
continue
|
|
for f in (f for f in files if f.endswith(".ko")):
|
|
src = root+"/"+f
|
|
filelist.append(src)
|
|
copy_file(src, ko_updates)
|
|
move_file(src, initrd_updates)
|
|
|
|
# Copy the firmware updates
|
|
for root, _dirs, files in os.walk(dest_path+"/lib/firmware/"):
|
|
if root.endswith("/updates") and os.path.isdir(root):
|
|
continue
|
|
for f in (f for f in files):
|
|
src = root+"/"+f
|
|
filelist.append(src)
|
|
copy_file(src, firmware_updates)
|
|
move_file(src, initrd_firmware)
|
|
|
|
# Tell our caller about the newly-extracted stuff
|
|
return filelist
|
|
|
|
|
|
# an arbitrary value to signal refreshing the menu contents
|
|
DoRefresh = True
|
|
|
|
def selection_menu(items, title, info_func, multi_choice=True, refresh=False):
|
|
""" Display menu and let user select one or more choices.
|
|
|
|
:param items: list of items
|
|
:type items: list of objects (with the 'selected' property/attribute if
|
|
multi_choice=True is used)
|
|
:param title: title for the menu
|
|
:type title: str
|
|
:param info_func: function providing info about items
|
|
:type info_func: item -> str
|
|
:param multi_choice: whether it is a multiple choice menu or not
|
|
:type multi_choice: bool
|
|
:returns: the selected item in case of multi_choice=False and user did
|
|
selection, None otherwise
|
|
"""
|
|
|
|
page_length = 20
|
|
page = 1
|
|
num_pages = len(items) / page_length
|
|
if len(items) % page_length > 0:
|
|
num_pages += 1
|
|
|
|
if multi_choice:
|
|
choice_format = "[%s]"
|
|
else:
|
|
choice_format = ""
|
|
format_str = "%3d) " + choice_format + " %s"
|
|
|
|
while True:
|
|
# show a page of items
|
|
print("\nPage %d of %d" % (page, num_pages))
|
|
print(title)
|
|
if page * page_length <= len(items):
|
|
num_items = page_length
|
|
else:
|
|
num_items = len(items) % page_length
|
|
for i in range(0, num_items):
|
|
item_idx = ((page-1) * page_length) + i
|
|
if multi_choice:
|
|
if items[item_idx].selected:
|
|
selected = "x"
|
|
else:
|
|
selected = " "
|
|
args = (i+1, selected, info_func(items[item_idx]))
|
|
else:
|
|
args = (i+1, info_func(items[item_idx]))
|
|
print(format_str % args)
|
|
|
|
# Select an item to toggle, continue or change pages
|
|
opts = ["# to select",
|
|
"'n'-next page",
|
|
"'p'-previous page",
|
|
"'c'-continue"]
|
|
if multi_choice:
|
|
opts[0] = "# to toggle selection"
|
|
if refresh:
|
|
opts.insert(1,"'r'-refresh")
|
|
idx = raw_input(''.join(['\n',
|
|
", ".join(opts[:-1]),
|
|
" or ", opts[-1], ": "]))
|
|
if idx.isdigit() and not (int(idx) < 1 or int(idx) > num_items):
|
|
item_idx = ((page-1) * page_length) + int(idx) - 1
|
|
if multi_choice:
|
|
items[item_idx].selected = not items[item_idx].selected
|
|
else:
|
|
# single choice only, we can return now
|
|
return items[item_idx]
|
|
elif idx.lower() == 'n':
|
|
if page < num_pages:
|
|
page += 1
|
|
else:
|
|
print("Last page")
|
|
elif idx.lower() == 'p':
|
|
if page > 1:
|
|
page -= 1
|
|
else:
|
|
print("First page")
|
|
elif idx.lower() == 'r' and refresh:
|
|
return DoRefresh
|
|
elif idx.lower() == 'c':
|
|
return
|
|
else:
|
|
print("Invalid selection")
|
|
|
|
def select_drivers(drivers):
|
|
""" Display pages of drivers to be loaded.
|
|
|
|
:param drivers: Drivers to be selected by the user
|
|
:type drivers: list of Driver objects
|
|
:returns: None
|
|
"""
|
|
if not drivers:
|
|
return
|
|
|
|
selection_menu(drivers, "Select drivers to install",
|
|
lambda driver: driver.source)
|
|
|
|
def process_dd(dd_path):
|
|
""" Handle installing modules, firmware, enhancements from the dd repo
|
|
|
|
:param dd_path: Path to the driver repository
|
|
:type dd_path: string
|
|
:returns: None
|
|
"""
|
|
drivers = dd_list(dd_path)
|
|
log.debug("drivers = %s", " ".join([d.rpm for d in drivers]))
|
|
|
|
# If interactive mode or rhdd3.rules pass flag to deselect by default?
|
|
if os.path.exists(dd_path+"/rhdd3.rules") or is_interactive():
|
|
select_drivers(drivers)
|
|
if not any((d.selected for d in drivers)):
|
|
return
|
|
else:
|
|
map(lambda d: setattr(d, "selected", True), drivers)
|
|
|
|
# Copy the repository for Anaconda to use during install
|
|
copy_repo(dd_path, "/updates/run/install/DD-")
|
|
|
|
extracted = []
|
|
|
|
for driver in filter(lambda d: d.selected, drivers):
|
|
extracted += dd_extract(driver, "/updates/")
|
|
|
|
# Write the package names for all modules and firmware for Anaconda
|
|
if "modules" in driver.flags or "firmwares" in driver.flags:
|
|
with open("/run/install/dd_packages", "a") as f:
|
|
f.write("%s\n" % driver.name)
|
|
|
|
reload_modules(extracted)
|
|
|
|
|
|
def select_dd(device):
|
|
""" Mount a device and check it for Driver Update repos
|
|
|
|
:param device: Path to the device to mount and check
|
|
:type device: string
|
|
:returns: None
|
|
"""
|
|
mnt = "/media/DD/"
|
|
if not os.path.isdir(mnt):
|
|
os.makedirs(mnt)
|
|
if not mount_device(device, mnt):
|
|
return
|
|
|
|
dd_repos = find_dd(mnt)
|
|
for repo in dd_repos:
|
|
log.info("Processing DD repo %s on %s", repo, device)
|
|
process_dd(repo)
|
|
|
|
# TODO - does this need to be done before module reload?
|
|
umount(device)
|
|
|
|
|
|
def network_driver(dd_path):
|
|
""" Handle network driver download, then scan for new OEMDRV devices.
|
|
|
|
:param dd_path: Path to the downloaded driver rpms
|
|
:type dd_path: string
|
|
:returns: None
|
|
"""
|
|
skip_dds = set(oemdrv_list())
|
|
|
|
log.info("Processing Network Drivers from %s", dd_path)
|
|
isos = glob.glob(os.path.join(dd_path, "*.iso"))
|
|
for iso in isos:
|
|
select_dd(iso)
|
|
|
|
process_dd(dd_path)
|
|
|
|
# TODO: May need to add new drivers to /tmp/dd_modules to prevent them from being unloaded
|
|
|
|
# Scan for new OEMDRV devices and ignore dd_args
|
|
dd_scan(skip_dds, scan_dd_args=False, skip_device_menu=True)
|
|
|
|
class DeviceInfo(object):
|
|
def __init__(self, **kwargs):
|
|
self.device = kwargs.get("device", None)
|
|
self.label = kwargs.get("label", None)
|
|
self.uuid = kwargs.get("uuid", None)
|
|
self.fs_type = kwargs.get("fs_type", None)
|
|
|
|
def __str__(self):
|
|
return "%-10s %-20s %-15s %s" % (self.device or "", self.fs_type or "",
|
|
self.label or "", self.uuid or "")
|
|
|
|
def parse_blkid(line):
|
|
""" Parse a line of output from blkid
|
|
|
|
:param line: line of output from blkid
|
|
:param type: string
|
|
:returns: {} or dict of NAME=VALUE pairs including "device"
|
|
:rtype: dict
|
|
|
|
blkid output cannot be trusted. labels may be missing or in a different
|
|
order so we parse what we get and return a dict with their values.
|
|
"""
|
|
import shlex
|
|
|
|
device = {"device":None, "label":None, "uuid":None, "fs_type":None}
|
|
fields = shlex.split(line)
|
|
if len(fields) < 2 or not fields[0].startswith("/dev/"):
|
|
return {}
|
|
|
|
# device is in [0] and the remainder are NAME=VALUE with possible spaces
|
|
# Use the sda1 part of device "/dev/sda1:"
|
|
device['device'] = fields[0][5:-1]
|
|
for f in fields[1:]:
|
|
if "=" in f:
|
|
(key, val) = f.split("=", 1)
|
|
if key == "TYPE":
|
|
key = "fs_type"
|
|
device[key.lower()] = val
|
|
return device
|
|
|
|
def select_iso():
|
|
""" Let user select device and DD ISO on it.
|
|
|
|
:returns: path to the selected ISO file and mountpoint to be unmounted
|
|
or (None, None) if no ISO file is selected
|
|
:rtype: (str, str)
|
|
"""
|
|
header = " %-10s %-20s %-15s %s" % ("DEVICE", "TYPE", "LABEL", "UUID")
|
|
|
|
iso_dev = DoRefresh
|
|
while iso_dev is DoRefresh:
|
|
try:
|
|
_ret, out = run_cmd(["blkid"])
|
|
except (OSError, RunCmdError):
|
|
return (None, None)
|
|
|
|
devices = []
|
|
for line in out.splitlines():
|
|
dev = parse_blkid(line)
|
|
if dev:
|
|
devices.append(DeviceInfo(**dev))
|
|
|
|
iso_dev = selection_menu(devices,
|
|
"Driver disk device selection\n" + header,
|
|
str, multi_choice=False, refresh=True)
|
|
|
|
if not iso_dev:
|
|
return (None, None)
|
|
|
|
mnt = "/media/DD-search"
|
|
if not os.path.isdir(mnt):
|
|
os.makedirs(mnt)
|
|
if not mount_device("/dev/" + iso_dev.device, mnt):
|
|
print("===Cannot mount the chosen device!===\n")
|
|
return select_iso()
|
|
|
|
# is this device a Driver Update Disc?
|
|
if find_dd(mnt):
|
|
umount(mnt) # BLUH. unmount it first so select_dd can mount it OK
|
|
return ("/dev/" + iso_dev.device, None)
|
|
|
|
# maybe it's a device containing multiple DUDs - let the user pick one
|
|
isos = list()
|
|
for dir_path, _dirs, files in os.walk(mnt):
|
|
# trim the mount point path
|
|
rel_dir = dir_path[len(mnt):]
|
|
|
|
# and the starting "/" (if any)
|
|
if rel_dir.startswith("/"):
|
|
rel_dir = rel_dir[1:]
|
|
|
|
isos += (os.path.join(rel_dir, iso_file)
|
|
for iso_file in files if iso_file.endswith(".iso"))
|
|
|
|
if not isos:
|
|
print("===No ISO files found on %s!===\n" % iso_dev.device)
|
|
umount(mnt)
|
|
return select_iso()
|
|
else:
|
|
# mount writes out some mounting information, add blank line
|
|
print
|
|
|
|
# let user choose the ISO file
|
|
dd_iso = selection_menu(isos, "Choose driver disk ISO file",
|
|
lambda iso_file: iso_file,
|
|
multi_choice=False)
|
|
|
|
if not dd_iso:
|
|
return (None, None)
|
|
|
|
return (os.path.join(mnt, dd_iso), "/media/DD-search")
|
|
|
|
def dd_scan(skip_dds=None, scan_dd_args=True, skip_device_menu=False):
|
|
""" Scan the system for OEMDRV devices and and specified by dd=/dev/<device>
|
|
|
|
:param skip_dds: devices to skip when checking for OEMDRV label
|
|
:type skip_dds: set()
|
|
:param scan_dd_args: Scan devices passed in /tmp/dd_args or dd_args_ks
|
|
:type scan_dd_args: bool
|
|
:returns: None
|
|
"""
|
|
dd_todo = set(oemdrv_list())
|
|
|
|
if skip_dds is None:
|
|
skip_dds = set()
|
|
|
|
if skip_dds:
|
|
dd_todo.difference_update(skip_dds)
|
|
if dd_todo:
|
|
log.info("Found new OEMDRV device(s) - %s", ", ".join(dd_todo))
|
|
|
|
if scan_dd_args:
|
|
# Add the user specified devices
|
|
dd_devs = get_dd_args()
|
|
dd_devs = [dev for dev in dd_devs if dev not in ("dd", "inst.dd")]
|
|
dd_todo.update(dd_devs)
|
|
log.info("Checking devices %s", ", ".join(dd_todo))
|
|
|
|
# Process each Driver Disk, checking for new disks after each one
|
|
dd_finished = dd_load(dd_todo, skip_dds=skip_dds)
|
|
skip_dds.update(dd_finished)
|
|
|
|
# Skip interactive selection of an iso if OEMDRV was found
|
|
if skip_dds or skip_device_menu or not is_interactive():
|
|
return
|
|
|
|
# Handle interactive driver selection
|
|
mount_point = None
|
|
while True:
|
|
iso, mount_point = select_iso()
|
|
if iso:
|
|
if iso in skip_dds:
|
|
skip_dds.remove(iso)
|
|
dd_load(set([iso]), skip_dds=skip_dds)
|
|
# NOTE: we intentionally do not add the newly-loaded device to
|
|
# skip_dds - the user might (e.g.) swap DVDs and use /dev/sr0 twice
|
|
umount(mount_point)
|
|
else:
|
|
break
|
|
|
|
def dd_load(dd_todo, skip_dds=None):
|
|
""" Process each Driver Disk, checking for new disks after each one.
|
|
Return the set of devices that loaded stuff from.
|
|
|
|
:param dd_todo: devices to load drivers from
|
|
:type dd_todo: set
|
|
:param skip_dds: devices to skip when checking for OEMDRV label
|
|
:type skip_dds: set
|
|
:returns: set of devices that have been loaded
|
|
"""
|
|
if skip_dds is None:
|
|
skip_dds = set()
|
|
|
|
dd_finished = set()
|
|
while dd_todo:
|
|
device = dd_todo.pop()
|
|
if device in skip_dds:
|
|
continue
|
|
log.info("Checking device %s", device)
|
|
select_dd(device)
|
|
dd_finished.add(device)
|
|
new_oemdrv = set(oemdrv_list()).difference(dd_finished, dd_todo)
|
|
if new_oemdrv:
|
|
log.info("Found new OEMDRV device(s) - %s", ", ".join(new_oemdrv))
|
|
dd_todo.update(new_oemdrv)
|
|
return dd_finished
|
|
|
|
if __name__ == '__main__':
|
|
log.setLevel(logging.DEBUG)
|
|
handler = SysLogHandler(address="/dev/log")
|
|
log.addHandler(handler)
|
|
handler = logging.StreamHandler()
|
|
handler.setLevel(logging.INFO)
|
|
formatter = logging.Formatter("DD: %(message)s")
|
|
handler.setFormatter(formatter)
|
|
log.addHandler(handler)
|
|
|
|
if len(sys.argv) > 1:
|
|
# Network driver source
|
|
network_driver(sys.argv[1])
|
|
elif os.path.exists("/tmp/DD-net/"):
|
|
network_driver("/tmp/DD-net/")
|
|
elif os.path.exists("/tmp/dd_args_ks"):
|
|
# Kickstart driverdisk command, skip existing OEMDRV devices and
|
|
# process cmdline dd entries. This will process any OEMDRV that
|
|
# appear after loading the other drivers.
|
|
skip_devices = set(oemdrv_list())
|
|
dd_scan(skip_devices, skip_device_menu=True)
|
|
else:
|
|
# Process /tmp/dd_args and OEMDRV devices
|
|
# Show device selection menu when inst.dd passed and no OEMDRV devices
|
|
dd_scan()
|
|
|
|
sys.exit(0)
|
|
|