| Server IP : 178.105.222.151 / Your IP : 216.73.216.38 Web Server : nginx/1.28.3 System : Linux MNK 7.0.0-15-generic #15-Ubuntu SMP PREEMPT_DYNAMIC Wed Apr 22 16:06:43 UTC 2026 x86_64 User : www-data ( 33) PHP Version : 8.5.4 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /lib/python3/dist-packages/UbuntuDrivers/ |
Upload File : |
#
# kerneldetection.py
#
# Copyright 2013 Canonical Ltd.
#
# Author: Alberto Milone <alberto.milone@canonical.com>
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
import apt_pkg
import logging
import re
from typing import Optional, Tuple, List
from subprocess import Popen, PIPE
import os
import sys
class KernelDetection(object):
def __init__(self, cache: Optional[apt_pkg.Cache] = None) -> None:
if cache:
self.apt_cache = cache
self.apt_depcache = apt_pkg.DepCache(cache)
else:
apt_pkg.init_config()
apt_pkg.init_system()
self.apt_cache = apt_pkg.Cache(None)
self.apt_depcache = apt_pkg.DepCache(self.apt_cache)
def _is_greater_than(self, term1: str, term2: str) -> bool:
# We don't want to take into account
# the flavour
pattern = re.compile("(.+)-([0-9]+)-(.+)")
match1 = pattern.match(term1)
match2 = pattern.match(term2)
if match1 and match2:
term1 = "%s-%s" % (match1.group(1), match1.group(2))
term2 = "%s-%s" % (match2.group(1), match2.group(2))
logging.debug("Comparing %s with %s" % (term1, term2))
command = "dpkg --compare-versions %s gt %s" % (term1, term2)
process = Popen(command.split(" "))
process.communicate()
return not process.returncode
def _get_linux_flavour(self, candidates: List[str], image: str) -> str:
pattern = re.compile(r"linux-image-([0-9]+\.[0-9]+\.[0-9]+)-([0-9]+)-(.+)")
match = pattern.match(image)
flavour = ""
if match:
flavour = match.group(3)
return flavour
def _filter_cache(self, pkg: apt_pkg.Package) -> Optional[str]:
package_name = pkg.name
if (
package_name.startswith("linux-image")
and "extra" not in package_name
and (pkg.current_ver or self.apt_depcache.marked_install(pkg))
):
return package_name
else:
return None
def _get_linux_metapackage(self, target: str) -> str:
"""Get the linux headers, linux-image or linux metapackage"""
metapackage = ""
image_package = ""
version = ""
prefix = "linux-%s" % ("headers" if target == "headers" else "image")
pattern = re.compile("linux-image-(?:unsigned-)?(.+)-([0-9]+)-(.+)")
for package_name in map(self._filter_cache, self.apt_cache.packages):
if package_name:
match = pattern.match(package_name)
# Here we filter out packages other than
# the actual image or header packages
if match:
current_package = match.group(0)
current_version = "%s-%s" % (match.group(1), match.group(2))
# See if the current version is greater than
# the greatest that we've found so far
if self._is_greater_than(current_version, version):
version = current_version
image_package = current_package
if version:
if target == "headers":
target_package = image_package.replace("image", "headers")
else:
target_package = image_package
reverse_dependencies = [
dep.parent_pkg.name
for dep in self.apt_cache[target_package].rev_depends_list # type: ignore[attr-defined]
if dep.parent_pkg.name.startswith(prefix)
]
if reverse_dependencies:
# This should be something like linux-image-$flavour
# or linux-headers-$flavour
metapackage = ""
for candidate in reverse_dependencies:
try:
candidate_pkg = self.apt_cache[candidate]
if (
candidate.startswith(prefix)
and (
candidate_pkg
and (
candidate_pkg.current_ver
or self.apt_depcache.marked_install(candidate_pkg)
)
)
and candidate.replace(prefix, "")
> metapackage.replace(prefix, "")
):
metapackage = candidate
except KeyError:
continue
# if we are looking for headers, then we are good
if target == "meta":
# Let's get the metapackage
reverse_dependencies = [
dep.parent_pkg.name
for dep in self.apt_cache[metapackage].rev_depends_list # type: ignore[attr-defined]
if dep.parent_pkg.name.startswith("linux-")
]
if reverse_dependencies:
flavour = self._get_linux_flavour(
reverse_dependencies, target_package
)
linux_meta = ""
for meta in reverse_dependencies:
# For example linux-generic-hwe-20.04
if meta.startswith("linux-%s-" % (flavour)):
linux_meta = meta
break
# This should be something like linux-$flavour
if not linux_meta:
# Try the 1st reverse dependency
metapackage = reverse_dependencies[0]
else:
metapackage = linux_meta
return metapackage
def get_linux_headers_metapackage(self) -> str:
"""Get the linux headers for the newest_kernel installed"""
return self._get_linux_metapackage("headers")
def get_linux_image_metapackage(self) -> str:
"""Get the linux headers for the newest_kernel installed"""
return self._get_linux_metapackage("image")
def get_linux_metapackage(self) -> str:
"""Get the linux metapackage for the newest_kernel installed"""
return self._get_linux_metapackage("meta")
def get_linux_version(self) -> Optional[str]:
linux_image_meta = self.get_linux_image_metapackage()
linux_version = None
try:
# dependencies = self.apt_cache[linux_image_meta].candidate.\
# record['Depends']
candidate = self.apt_depcache.get_candidate_ver(
self.apt_cache[linux_image_meta]
)
if candidate:
for dep_list in candidate.depends_list_str.get("Depends"): # type: ignore[attr-defined]
for dep_name, dep_ver, dep_op in dep_list:
if dep_name.startswith("linux-image"):
linux_version = dep_name.strip().replace("linux-image-", "")
break
except KeyError:
logging.error("No dependencies can be found for %s" % (linux_image_meta))
return None
# if ', ' in dependencies:
# deps = dependencies.split(', ')
# for dep in deps:
# if dep.startswith('linux-image'):
# linux_version = dep.replace('linux-image-', '')
# else:
# if dependencies.strip().startswith('linux-image'):
# linux_version = dependencies.strip().replace('linux-image-', '')
return linux_version
def is_running_kernel_outdated(self) -> Tuple[bool, str, Optional[str], bool]:
"""Check if running kernel is outdated.
Returns a tuple (is_outdated, running_version, latest_version, requires_dkms):
is_outdated: True if the kernel is outdated
running_version: version string of the running kernel
latest_version: version string of the latest available kernel
requires_dkms: True if DKMS modules are required
"""
logging.debug("Checking if running kernel is outdated")
# Get running kernel version
running_version = os.uname().release
logging.debug("Running kernel version: %s", running_version)
# Check if running kernel matches expected format (if not, DKMS required)
# We make an assumption that a system on a non-ubuntu kernel version format
# is not on an Ubuntu prebuilt kernel upgrade path, and thus cannot benefit from prebuilt modules.
if not re.match(r"\d+\.\d+\.\d+-\d+", running_version):
logging.debug(
"Running kernel version does not match expected format - DKMS required"
)
return False, running_version, None, True
# Get list of installed kernel metapackages. If any are upgradable, we should warn the
# user to upgrade before proceeding.
# We make an assumption that any of the installed kernels could be the next default boot
# option (not necessarily just the currently running one) - and thus we should advise that
# any update candidates are applied before proceeding.
meta_pkgs = []
for pkg in self.apt_cache.packages:
if not pkg.current_ver:
continue
if not pkg.name.startswith("linux-image-"):
continue
# Skip actual kernel image packages
if re.match(
r"linux-image-(\d+\.\d+\.\d+-\d+|\w*unsigned-\d+\.\d+\.\d+-\d+)",
pkg.name,
):
continue
meta_pkgs.append(pkg)
logging.debug("Found installed kernel metapackage: %s", pkg.name)
for meta_pkg in meta_pkgs:
logging.debug("Checking metapackage %s for updates", meta_pkg.name)
# Check if this metapackage has an update available
candidate = self.apt_depcache.get_candidate_ver(meta_pkg)
if not candidate:
logging.debug("No candidate version for %s", meta_pkg.name)
continue
if not self.apt_depcache.is_upgradable(meta_pkg):
logging.debug("No update available for %s", meta_pkg.name)
continue
# Check candidate dependencies for new kernel version
for dep in candidate.depends_list.get("Depends", []):
for dep_or in dep:
if not dep_or.target_pkg.name.startswith("linux-image-"):
continue
if not re.match(
r"linux-image-\d+\.\d+\.\d+-\d+", dep_or.target_pkg.name
):
continue
latest_version = dep_or.target_pkg.name.split("linux-image-")[1]
logging.debug(
"Found new kernel version %s from %s",
latest_version,
meta_pkg.name,
)
return True, running_version, latest_version, False
logging.debug("No kernel updates found")
return False, running_version, None, False
def get_kernel_update_warning(self, include_dkms: bool = False) -> bool:
"""Print a warning message if the kernel needs updating.
Returns:
should_exit: True if the caller should exit (e.g. DKMS required but not included)
"""
is_outdated, running, latest, requires_dkms = self.is_running_kernel_outdated()
if requires_dkms:
if not include_dkms:
# Check Secure Boot state using mokutil
try:
process = Popen(["mokutil", "--sb-state"], stdout=PIPE, stderr=PIPE)
output_bytes, err_bytes = process.communicate()
output: str = output_bytes.decode("utf-8").lower()
err: str = err_bytes.decode("utf-8").lower()
if (
"secureboot enabled" in output
or "secure boot enabled" in output
):
print(
"Your running kernel (%s) requires DKMS modules, and you have Secure Boot enabled. "
"To proceed, ensure you have access to your machine’s UEFI menu and have the rights "
"to enroll a Machine Owner Key (MOK), "
"then re-run ubuntu-drivers with --include-dkms. This will install the DKMS modules, "
"then prompt you to enroll the new MOK and reboot."
% running,
file=sys.stderr,
)
return True
elif (
"secureboot disabled" in output
or "secure boot disabled" in output
):
print(
"Your running kernel (%s) requires DKMS modules. You have Secure Boot disabled, but if "
"you enable it in the future, you will need to sign or reinstall these DKMS modules for "
"them to work. "
"If you would like to continue, please re-run ubuntu-drivers with --include-dkms."
% running,
file=sys.stderr,
)
return True
elif "this system doesn't support secure boot" in err:
print(
"Your running kernel (%s) requires DKMS modules. Please use --include-dkms if you want"
" to proceed." % running,
file=sys.stderr,
)
return True
# else: fall through to generic response
except Exception as e:
logging.warning("Could not determine Secure Boot state: %s", str(e))
# fall through to generic response
# fallback generic response - n
print(
"Your running kernel (%s) requires DKMS modules, and ubuntu-drivers was unable to determine if"
" Secure Boot is enabled. If you have SB enabled, you will need to enroll a MOK to proceed, "
"which will require access to your UEFI menu and administrative privileges. Please use "
"--include-dkms if you want to proceed." % running,
file=sys.stderr,
)
return True
else:
# DKMS is allowed via --include-dkms, so no exit needed
return False
if is_outdated and latest:
print(
f"Warning: Your running kernel ({running}) is outdated. "
f"A newer kernel ({latest}) is available in the Ubuntu archives. "
f"Please run 'sudo apt update && sudo apt upgrade' to update your system.",
file=sys.stderr,
)
# Outdated kernel is only a warning, not a blocker
return False