DDISKIT(1)

πŸ‘‘ ddiskit β€” Red Hat tool for Driver Update Disk creation πŸ‘‘

okay bestie, welcome to ddiskit(1) πŸ’– this is a General Commands Manual page and honestly? it's giving "power tool that runs the whole DUD pipeline." let's get into it.


🌸 NAME 🌸

ddiskit β€” the Red Hat tool for making Driver Update Disks πŸŽ€


✨ SYNOPSIS ✨

ddiskit [-h] [-vvv] [-qQ] [-p PROFILE] [-R RES_DIR] [-T TEMPLATE_DIR]
        [-P PROFILE_DIR] [-d] [-o DUMP_CONFIG_NAME] [-C KEY=VALUE]...
        ACTION [ACTION_OPTIONS]...

πŸ’… DESCRIPTION πŸ’…

so ddiskit β€” pronounced dee-dis-kit, yes really, that's the vibe β€” helps you and simplifies the creation of (back-ported) out-of-tree kernel module RPMs and the Driver Update Disks that contain them. slay. πŸ’«

these RPMs and DUDs are considered suitable for using with distributions based on Red Hat Enterprise Linux. this version of ddiskit supports RHEL 7 and is backward-compatible with RHEL 6. no cap.

ddiskit assumes a certain workflow, and that whole thing is described in the USAGE EXAMPLE section. it's tailored to the process used for developing and releasing Driver Update Disks, and β€” while it totally can be used for creating driver packages for RHEL-based distributions in general β€” it's also worth considering other tools for a similar purpose, like dkms(8) or akmods(1). just saying. πŸ‘€


πŸ”₯ OPTIONS πŸ”₯

please refer to the Configuration generation section for the description of the "configuration option name" and "default value" fields, and to the ACTIONS section for the description of the actions mentioned here. iykyk 🫢

🌈 General options 🌈

  • -h, --help β€” print a list of options. ddiskit will exit after this action and just straight-up ignore the rest of the command line. bye πŸ‘‹
  • -v, --verbosity β€” bump output verbosity by 1.
    • a value of 1 means verbose output (it toggles printing of most of executed commands and all warnings), and a value of 2 is the debugging era (it enables printing of all the executed commands output, actions, and messages). a value of 3 further increases output verbosity of issued rpm(1) commands. πŸ“’
    • Configuration option name: defaults.verbosity
    • Default value: 0
  • -p PROFILE, --profile PROFILE β€” the configuration profile to use. please refer to the Configuration generation section for the extra info about profiles. πŸ’–
    • Configuration option name: defaults.profile
    • Default value: none; "default" is provided in package configuration
  • -R RES_DIR, --res-dir RES_DIR β€” resources directory. used as a base of evaluation for template and profile directory paths.
    • Configuration option name: defaults.res_dir
    • Default value: none; "/usr/share/ddiskit/" is a hard-coded default
  • -T TEMPLATE_DIR, --template-dir TEMPLATE_DIR β€” templates directory, used as a base for searching for spec or module configuration templates. please refer to the File path evaluation section for the info regarding template file path resolution. πŸ—‚οΈ
    • Configuration option name: defaults.template_dir
    • Default value: none; "{res_dir}/templates" is a hard-coded default
  • -P PROFILE_DIR, --profile-dir PROFILE_DIR β€” profiles directory, used as a base for searching for profiles. please refer to the File path evaluation section for the info regarding profile file path resolution.
    • Configuration option name: defaults.profile_dir
    • Default value: none; "{res_dir}/profiles" is a hard-coded default
  • -d, --dump-config β€” dump the derived configuration after performing the action. πŸ“€
    • Configuration option name: defaults.dump_config
    • Default value: False
  • -o DUMP_CONFIG_NAME, --dump-config-name DUMP_CONFIG_NAME β€” path to the file targeted for the configuration dump.
    • Configuration option name: defaults.dump_config_name
    • Default value: none; "{config}.generated" is a hard-coded default
  • -q, --quilt-enable β€” enable quilt support. please refer to the QUILT INTEGRATION section for the info regarding quilt integration. this option sets the defaults.quilt_support configuration option to True. 🧡
  • -Q, --quilt-disable β€” disable quilt support. this option sets the defaults.quilt_support configuration option to False.
  • -C KEY=VALUE, --config-option KEY=VALUE β€” override an arbitrary configuration option that has the name KEY with VALUE. the KEY should follow the {[section_name.]key_name} syntax, and the defaults section is assumed in case the section_name part is omitted. this argument can be used multiple times in order to override multiple options. πŸ”
    • Configuration option name: (n/a)
    • Default value: (n/a)

🧚 Options for prepare_sources action 🧚

  • -c CONFIG, --config CONFIG β€” path to the module configuration file.
    • Configuration option name: defaults.config
    • Default value: "module.config"
  • -t CONFIG_TEMPLATE, --config-template CONFIG_TEMPLATE β€” configuration file template. please refer to the File path evaluation section for the info regarding configuration template file path resolution.
    • Configuration option name: defaults.config_template
    • Default value: none; "config" is a hard-coded default
  • -g GIT_DIRECTORY, --git-dir GIT_DIRECTORY β€” path to the .git directory containing the repository from where source files should be checked out. 🌿
    • Configuration option name: defaults.git_dir
    • Default value: none
  • -r REVISION, --git-revision REVISION β€” revision specification at which source files should be checked out from the repository.
    • Configuration option name: defaults.git_revision
    • Default value: "HEAD"
  • -d DIRECTORIES, --git-src-directory DIRECTORIES β€” whitespace-separated list of paths which should be checked out from the repository.
    • Configuration option name: defaults.git_src_directory
    • Default value: none
  • -M MAJOR, --major MAJOR β€” major version number of the distribution the package is built for (for example, "7" for RHEL 7.3).
    • Configuration option name: defaults.major
    • Default value: 7
  • -m MINOR, --minor MINOR β€” minor version number of the distribution the package is built for (for example, "3" for RHEL 7.3).
    • Configuration option name: defaults.minor
    • Default value: 0

πŸŽ€ Options for generate_spec action πŸŽ€

  • -c CONFIG, --config CONFIG β€” path to the module configuration file.
    • Configuration option name: defaults.config
    • Default value: "module.config"
  • -t SPEC_TEMPLATE, --spec-template SPEC_TEMPLATE β€” RPM spec file template. please refer to the File path evaluation section for the info regarding RPM spec template file path resolution. πŸ“
    • Configuration option name: defaults.spec_template
    • Default value: none; "spec" is a hard-coded default

πŸ¦‹ Options for build_rpm action πŸ¦‹

  • -c CONFIG, --config CONFIG β€” path to the module configuration file.
    • Configuration option name: defaults.config
    • Default value: "module.config"
  • -a, --tar-all β€” tar all files, including hidden ones (files with names starting with a dot). otherwise, only files with names starting with a non-dot character will be added to the source tarball. this option sets the value of the relevant configuration parameter to True.
    • > heads up bestie: this check is independent from the check controlled by the defaults.tar_strict configuration parameter.
    • Configuration option name: defaults.tar_all
    • Default value: False
  • -e, --tar-strict β€” tar only expected files. only the files with names matching the regular expression pattern provided in the defaults.src_patterns configuration option will be added to the source tarball. this option sets the value of the relevant configuration parameter to True.
    • > and again: this check is independent from the check controlled by the defaults.tar_all configuration parameter.
    • Configuration option name: defaults.tar_strict
    • Default value: False
  • -s, --srpm β€” force building of a source RPM instead of a binary one. ddiskit has several heuristics (whether the host architecture is among the architectures targeted by the module, whether the RPM build check passes) that detect the possibility of a binary RPM build, and it falls back to building a source RPM only in case they indicated that a binary RPM build is impossible; however, you can force building of a source RPM instead of a binary one with this switch. this option sets the value of the relevant configuration parameter to True. πŸ—οΈ
    • Configuration option name: defaults.srpm
    • Default value: False
  • -m, --mock β€” enable mock(1) usage for building the RPM. see the MOCK SUPPORT section for additional information.
    • Configuration option name: defaults.mock
    • Default value: False
  • -r MOCK_CONFIG, --mock-config MOCK_CONFIG β€” which mock configuration should be used for building the RPM.
    • Configuration option name: defaults.mock_config
    • Default value: "default"
  • -l, --mock-offline β€” whether to pass --offline option to mock.
    • Configuration option name: defaults.mock_offline
    • Default value: False
  • -O MOCK_OPTIONS, --mock-opts MOCK_OPTIONS β€” additional options that have to be passed to mock invocations. shell quoting is supported. overwrites options set by previous -O and/or -A options. ⚠️
    • Configuration option name: defaults.mock_opts
    • Default value: none
  • -A MOCK_OPTIONS, --mock-opts-append MOCK_OPTIONS β€” additional options that have to be passed to mock invocations. shell quoting is supported. appends options to the options set by previous -O and/or -A options. 🧷
    • Configuration option name: defaults.mock_opts
    • Default value: none
  • -g LEVEL, --check-git-src LEVEL β€” set the level of source code repository authenticity check. see the SOURCE CODE VERIFICATION section for the details. πŸ”
    • Configuration option name: defaults.check_get_src
    • Default value: 0
  • -G, --generate-spec-on-build β€” call the generate_spec action at the beginning of the build_rpm action execution. this saves you from calling the generate_spec action separately every single time the module configuration or patch list are changed (assuming the spec file doesn't need manual changes after generation). time-saver era ✨
    • Configuration option name: defaults.generate_spec_on_build
    • Default value: 0

🌸 Options for build_iso action 🌸

  • -c CONFIG, --config CONFIG β€” path to the module configuration file.
    • Configuration option name: defaults.config
    • Default value: "module.config"
  • -i ISOFILE, --isofile ISOFILE β€” file name for the output ISO. πŸ’Ώ
  • Configuration option name: defaults.isofile
    • Default value: none β€” and bestie, go peek at the build_iso action description section for the whole story πŸ‘€

Options for dump_config action πŸ—‚οΈ

  • -c CONFIG, --config CONFIG β€” where your module configuration file lives, path-style.
    • Configuration option name: defaults.config
    • Default value: "module.config"
  • -o DUMP_CONFIG_NAME, --dump-config-name DUMP_CONFIG_NAME β€” the name of the file where the configuration dump gets stored. Heads up: this is literally the same option as the -o in the General options section, it's just hanging out here too for convenience πŸ’…
    • Configuration option name: defaults.dump_config_name
    • Default value: none β€” but "{config}.generated" is a hard-coded default, so that's what you get if you say nothing.

Options for update_kabi action 🧬

  • -c CONFIG, --config CONFIG β€” path to the module configuration file.
    • Configuration option name: defaults.config
    • Default value: "module.config"
  • -g GIT_DIRECTORY, --git-dir GIT_DIRECTORY β€” path to the .git directory holding the repository that source files get checked out from.
    • Configuration option name: defaults.git_dir
    • Default value: none
  • -d KABI_DIRECTORY, --kabi-dest-dir KABI_DIRECTORY β€” path to the kABI symbol information files directory inside the kernel source tree that's supposed to get updated.
    • Configuration option name: defaults.kabi_dest_dir
    • Default value: none
  • -e, --extract-kmod β€” pull the kmods out of RPMs and use their modversion data instead of the RPM "Requires" tags. This one flips defaults.kabi_use_rpm_ko to True.
  • -E, --no-extract-kmod β€” do NOT extract kmods from RPM, just use whatever the "Requires" tags give you. This flips defaults.kabi_use_rpm_ko to False.
  • -o, --overwrite β€” overwrite the kABI symbol information files that already exist. This sets defaults.kabi_files_overwrite to 2.
  • -O, --no-overwrite β€” leave the existing kABI symbol information files alone. This sets defaults.kabi_files_overwrite to 0.
  • -i, --overwrite-interactive β€” whenever there's a possibility of overwriting an existing kABI symbol information file, it asks you first. This sets defaults.kabi_files_overwrite to 1.
  • -t, --commit β€” on success, actually create a git commit with the affected kABI symbol files. This sets defaults.kabi_commit to True.
  • -n, --no-commit β€” on success, do NOT create a git commit with the affected kABI symbol files. This sets defaults.kabi_commit to False.
  • -m MESSAGE, --kabi-commit-message MESSAGE β€” the commit message that gets supplied for that git commit with the affected kABI symbol files.
    • Configuration option name: defaults.kabi_commit
    • Default value: none
  • -M SYMVERS_PATH, --symvers-path SYMVERS_PATH β€” path to the relevant kernel's Module.symvers file (for example, "/usr/src/kernels/KERNEL_VERSION.{arch}/Module.symvers", which ships inside the kernel-devel RPM).
    • Configuration option name: defaults.symvers_path
    • Default value: none
  • -w WHITELIST_PATH, --kabi-whitelist WHITELIST_PATH β€” path to a file holding a list of symbols that are already white listed, in Module.symvers format.
    • Configuration option name: defaults.kabi_whitelist
    • Default value: none
  • -b, --break-on-errors β€” the second a symbol version conflict shows up, abort. This sets defaults.kabi_check_symvers_conflicts to 2.
  • -B, --no-break-on-version-conflicts β€” keep processing even if a symbol version conflict shows up. This sets defaults.kabi_check_symvers_conflicts to 0.
  • -a, --ask-on-version-conflicts β€” when a symbol version conflict shows up, ask the user what to do. This sets defaults.kabi_check_symvers_conflicts to 1.

✨ CONFIGURATION ✨

Okay so configuration here is a sectioned key-value store, bestie. The values are strings, and they get interpreted based on context (the CONFIGURATION VALUES REFERENCE section is your reference, go read it 🫢) as strings, integers, booleans (the Boolean values section breaks down exactly how boolean values get derived), or arrays.

Configuration generation 🧚

To build up its configuration, ddiskit collects configuration options from a bunch of different sources, and then does some fixed processing on top. The sources of configuration options are these:

  • Hard-coded defaults, sitting right there in the ddiskit source code. These are mostly for the default configuration search paths and for other values that are expected to be defined one way or another. Right now it contains these configuration options:
    • defaults section
      • res_dir = "/usr/share/ddiskit"
      • template_dir = "{res_dir}/templates"
      • profile_dir = "{res_dir}/profiles"
      • config_template = "config"
      • quilt_support = True
      • spec_template = "spec"
      • src_patterns = "^Kbuild$|^Kconfig$|^Makefile$|^.*.[ch]$"
    • global section
      • module_vendor = "ENTER_MODULE_VENDOR"
      • module_author = "ENTER_MODULE_AUTHOR"
      • module_author_email = "ENTER_MODULE_AUTHOR_EMAIL"
  • The "package" configuration. This one holds the rest of the configuration option defaults that need to be defined for proper operation (think spec file generation and such). Package configuration gets read from the fixed path "/usr/share/ddiskit/ddiskit.config", and nobody β€” neither user nor system administrator β€” is expected to modify it (it's usually just overwritten when the package updates).
  • The "site" configuration. Living at "/etc/ddiskit.config", this file is treated as a configuration file and it is subject to possible changes by the system administrator.
  • The "user" configuration. If a user wants some user-specific changes (like their own default values for the global.module_author or global.module_author_email configuration options, or a default profile), they should drop it into a .ddiskitrc file in their home directory.
  • Profile. The profile in use gets derived from the defaults.profile and default.profile_dir configuration variables (the File path evaluation section explains how the path to the profile gets evaluated). It contains overrides suited to a particular use case β€” for example, the rh-testing profile contains a spec file description suffix with a notice that the package provided is a testing package. Note that the values for those configuration variables can be overridden by the -p and -P command line arguments.
  • Module configuration. This file is usually called "module.config" (but the -c command line argument can override that) and it holds module-specific configuration. It's usually generated from a template by the prepare_sources action, and it's self-documented about which values the user is expected to provide in there.
  • Command-line arguments. These update the defaults section of the configuration dictionary, and their key name is usually equal to the long option name with the dashes swapped out for underscores. The configuration option name for each specific command line option is given in the OPTIONS section. Unless it's explicitly specified (with the default value being "none"), a command line option always updates the configuration option value.

These files get applied one after another in that exact order, so the "last wins" rule is in charge here. The exception to that rule is command line options, which take precedence at every point of configuration generation (during the profile path evaluation, for example).

The configuration files themselves are sectioned key-value files, and their syntax is described in the related Python module documentation β€” except for the interpolation part, which is home-grown and described over in the Configuration value evaluation section.

Kernel package versioning scheme 🐧

Red Hat Enterprise Linux follows a specific kernel package versioning scheme, and ddiskit employs it so it can generate proper dependencies on the kernel package. So basically, anywhere a kernel version shows up, it expects that version to follow the specific scheme. More specifically, two version schemes are supported:

  • Y-stream kernel version. This kernel package version ships as a part of a General Availability release, and it has this format:
kernel_version.kernel_patchlevel.kernel_sublevel-rhel_release.rpm_dist

For example, the RHEL 7.3 GA kernel has kernel version 3.10.0-514.el7. So consequently, it expects kernel_version, kernel_patchlevel, kernel_sublevel, rhel_release to be decimal numbers (with no more than 1, 2, 2, and 4 digits, respectively), and the rpm_dist part to be given in the form of "el<number>", where <number> is a 1-digit or 2-digit number that isn't less than 6.

  • Z-stream kernel version. These kernel packages are provided as a part of updates for the existing release (the so-called Z-stream). The versions of these packages have this format:
kernel_version.kernel_patchlevel.kernel_sublevel-rhel_release[.update_release]+.rpm_dist

The restrictions on the parts that are also used for the Y-stream kernel package version description stay the same, and update_release is a number that can have up to 3 digits. Example of a Z-stream kernel package version (the RHEL 7.3 update from 2017-05-25): 3.10.0-514.21.1.el7.

Generally speaking, kernel module RPMs and Driver Update Disks are expected to be built for use alongside the Y-stream GA kernel (and all the following Z-stream kernels, thanks to kABI compatibility πŸ’–), so when a Z-stream kernel package version gets detected, the user is warned about it. The differences between kernel package dependency generation in these cases are described in the Spec file generation section.

To enforce all these checks, ddiskit goes with a regular expression-based approach: it checks the version given in the defaults.kernel_version configuration variable against regular expressions set via the defaults.kernel_flex_version_re and defaults.kernel_fixed_version_re configuration options, which contain Python regular expressions (peep the Python re module documentation for the details about regular expression syntax) for matching the Y-stream and Z-stream kernel versioning scheme, respectively. And to extract the parts of the kernel version described above, these regular expression groups are used:

  • version β€” the kernel's major version (that's kernel_version up in the description, bestie πŸ’…)
  • patchlevel β€” the kernel's patch level (kernel_patchlevel)
  • sublevel β€” the kernel's sub-patch level (kernel_sublevel)
  • rpm_release β€” the major part of the RPM release (rhel_release)
  • rpm_release_add β€” the leftover part of the RPM release (update_release)
  • rpm_dist β€” the RPM release dist part (rpm_dist)

And the default values of the defaults.kernel_flex_version_re and defaults.kernel_fixed_version_re config options are themselves built from other config options, like this:

kernel_flex_verson_re   = {kernel_nvr_re}{kernel_dist_re}
kernel_fixed_version_re = {kernel_nvr_re}{kernel_fixed_re}{kernel_dist_re}

kernel_nvr_re   = (?P<version>[0-9]).(?P<patchlevel>[0-9]{1,2}).(?P<sublevel>[0-9]{1,2})-(?P<rpm_release>[0-9]{1,4})
kernel_fixed_re = (?P<rpm_release_add>(.[0-9]{1,3})+)
kernel_dist_re  = (?P<rpm_dist>.el([6-9]|[1-9][0-9]))

So yeah, there's some wiggle room in case you need to tune these checks. Very much iykyk energy πŸŽ€

Configuration check

Once the configuration has been constructed (and assuming module configuration is actually there), it goes through a whole lil vibe check πŸ¦‹:

  • Is the global configuration section there? Is the spec_file one there? Gotta have both.
  • Do all the configuration options in the global and spec_file sections have non-default values? Here's the tea: a default value is whatever you get by gluing "ENTER_" onto the upper-cased configuration key name (so "ENTER_MODULE_NAME" for the spec_file.module_name configuration option, for example). ✨ The one exception? The spec_file.firmware_version option, but only when the spec_file.firmware_include configuration option is set to False.
  • Does spec_file.kernel_version have the proper format? Only Y-stream and Z-stream kernel versions get through, periodt. (Scoot over to the Kernel package versioning scheme section for the acceptable version string format details.)
  • Do spec_file.module_name, spec_flie.module_version, and spec_file.module_rpm_release contain only characters RPM accepts? Here's the breakdown: alphanumeric plus '.', '-', '_', '+', '%', '{', '}' for spec_file.module_name; alphanumeric plus '.', '_', '+', '%', '{', '}', '~' for spec_flie.module_version and spec_file.module_rpm_release. πŸ’…

File path evaluation

Paths to all the external resource files (templates, profiles, the whole squad) get evaluated from the resource directory + name you gave, using this algorithm:

  • If the resource name has no slashes in it, then it's treated as referring to a file inside the directory you provided.
  • Otherwise? It's interpreted as a path relative to the current working directory (which is the directory where the module configuration lives).

So for example, the profile "my-profile" gets searched relative to the profile directory (that's the one stored in the defaults.profile_dir configuration option, which is "/usr/share/ddiskit/profiles" by default), but the profile "./my-profile" gets searched relative to the module's configuration directory. Same name, different vibe, different place. πŸ™ˆ

Configuration value evaluation

Configuration option values can reference other configuration options using the {[section_name.]key_name} syntax. If the section isn't there, it just assumes the referenced key lives in the same section as the value referencing it. And if the referenced key can't be found? No substitution happens, bestie, nothing. 🀷

Let's look at an example, assume this configuration file:

[foo]
foo = aaa {bar} {bar.baz}
bar = bbb {baz} {bar.foo}

[bar]
foo = ccc {baz}
bar = ddd {foo.foo}
baz = eee

After evaluation: the foo.foo key would hold the value "aaa bbb {baz} ddd ccc eee eee", foo.bar would equal "bbb {baz} ccc eee", bar.foo would be "ccc eee", and bar.bar is "ddd ccc eee". Look at all those values popping off ✨

Circular dependencies are not explicitly resolved β€” there's only a substitution depth limit (currently set to 8). So don't go making loops, it's giving chaos πŸ’€

Boolean values

Values that get treated as boolean can be any of the following (case-insensitive) strings to say "yeah, this is True": 1, t, y, true, or yes. To say False, you can use one of: 0, f, n, false, or no. And if a configuration value doesn't evaluate to True or False? It evaluates as None. 🫢

None gets treated as False in most places, but sometimes it genuinely matters that you make a specific choice β€” and in those cases, an error would occur if the boolean value evaluated to None. No neutral answers allowed, you have to pick a side. 🌸

Spec file generation

Before spec file generation even happens, there's extra configuration processing going down:

  • spec_file.source_patches and spec_file.source_patches_do get generated in accordance with a lexicographically sorted list of patch files found in the patch directory: src/patch relative to the current working directory (except when quilt(1) integration is enabled β€” see the QUILT INTEGRATION section for the details). spec_file.source_patches contains lines in the "PatchN: patch-file-name" format, and spec_file.source_patches_do contains lines in the "%patchN -p1" format. So the first configuration variable is the one you want for describing your patch file list, and the second one is the useful one in the %prep section for applying patches. 🧚 And if the default.quilt_support configuration option is enabled, a file named series gets ignored in the patch directory.
  • spec_file.firmware_files configuration variable holds the list of files found in the src/firmware directory with the /lib/firmware/ directory prepended β€” perfect for the %files section of the firmware sub-package.
  • spec_file.firmware_files_install configuration variable holds the list of firmware file installation commands in the format "install -m 644 -D source/firmware/firmware-file-path $RPM_BUILD_ROOT/lib/firmware/firmware-file-path" β€” which is exactly what you want for the %install section of the firmware sub-package.
  • spec_file.firmware_begin configuration option gets set to "%if 1" or "%if 0" depending on whether the spec_file.firmware_include configuration variable is true or not, respectively.
  • spec_file.firmware_end configuration variable gets set to "%endif".
  • spec_file.date gets set to the current date and time in "%a %b %d %Y" strftime(3) format β€” but only if this variable hasn't already been set.
  • spec_file.kernel_requires gets formatted like this (again, only if the variable hasn't been set already):
    • if the spec_file.kernel_version_min configuration option holds a non-empty value, it's set to
Requires:    kernel >= spec_file.kernel_version_min
  • otherwise, if the spec_file.kernel_version_dep configuration option holds a non-empty value, it's set to
Requires:    kernel = spec_file.kernel_version_dep
  • otherwise, it's set to
Requires:    kernel >= kernel_version-kernel_release.kernel_dist
Requires:    kernel <  kernel_version-(kernel_release + 1).kernel_dist

when the spec_file.kernel_version configuration option holds a Y-stream kernel version, or

Requires:    kernel = kernel_version-kernel_release.kernel_dist

when the spec_file.kernel_version configuration option holds a Z-stream kernel version (please head over to the Kernel package versioning scheme section for extra details about Y-stream and Z-stream versions).

If the spec_file.kernel_version configuration option isn't set and mock support is enabled, its value defaults to the latest version of the kernel-devel package available in the mock environment. Convenient πŸ’…

  • spec_file.module_requires gets set to the spec_file.dependencies value with the "Requires: " string prepended β€” if the variable hasn't been set already. Heads up though: this special configuration variable is deprecated, it's only still around for backward compatibility, and this special value generation may be removed in the future. πŸ‘‘

After all this configuration processing, parts of the spec template in the "%{[section_name.]key_name}" format (yes, notice the percent sign β€” that's different from the syntax used for configuration option substitution) get replaced with evaluated configuration values. If no appropriate configuration is found, no replacement happens. And if a configuration option evaluates to an empty string, %{nil} gets inserted into the resulting spec file instead. 🌈


πŸ”₯ ACTIONS πŸ”₯

prepare_sources

Prepare the initial file and directory structure. This action creates the directories where all the various files are expected to land, and it creates module configuration (into the file set in the --config option) from the template file β€” that template's path is determined by the defaults.template_dir and defaults.config_template configuration variables (peep the File path evaluation section for how the module configuration template path gets derived). 🌸

The action creates this directory hierarchy:

  • rpm β€” the directory for storing rpmbuild(1) artifacts.
    • BUILD β€” the build directory, used by rpmbuild(1).
    • BUILDROOT β€” the RPM build root.
    • RPMS β€” the directory where resulting binary RPMs are stored.
    • SOURCES β€” the directory where the source tarball and patches are stored.
    • SPECS β€” the directory where the generated spec file is placed.
    • SRPMS β€” the directory where the resulting source RPM is stored.
  • src β€” the directory where module sources are expected to be placed. There are no explicit constraints on the kernel module source file layout, but the expectation is that the main make file lives in a directory provided in the spec_file.module_build_dir configuration variable.
    • patches β€” the directory with patches that should be applied to the source.
    • firmware β€” firmware files.

On top of all that: if the defaults.git_src_directory configuration option is set (which itself defaults to the value of the spec_file.module_build_dir configuration option, as long as the latter is non-empty), then source files placed inside the directories listed in this whitespace-separated list get checked out (inside the src directory) from the repository pointed to by the defaults.git_dir configuration option, at the revision whose specification is set in the defaults.git_revision configuration option. And the actual revision that gets checked out is the output of the git rev-parse(1) command invocation with that aforementioned specification handed to it. πŸ¦‹

Before the configuration template gets processed, these configuration options are also set:

  • spec_file.module_build_dir β€” set to the value of the first element of the whitespace-separated list stored in the defaults.git_src_directory configuration option, or to "ENTER_MODULE_BUILD_DIR" if it's empty.
  • spec_file.git_hash β€” gets set to whatever the get rev-parse(1) call hands back, using the revision spec you gave in defaults.git_revision. ✨

generate_spec

Okay bestie, this one builds your spec file from the spec template β€” and which template that is gets decided by the defaults.template_dir and defaults.spec_template config variables (go peek at the File path evaluation section if you wanna know exactly how that path gets derived 🧚). The whole process is the one described in the Spec file generation section.

When it's done, you've got yourself a shiny "rpm/SPECS/{spec_file.module_name}.spec" file. πŸ’…

While it's generating, it also peeks at whether kernel headers exist for the target kernel version and architectures, and if some are missing it prints a warning message. That check is purely decorative though β€” it does not affect the spec file generation process. Iconic behaviour, honestly.

build_rpm

The RPM build action is a whole multi-step journey, so buckle up:

  • First it checks whether the module configuration file is even there (that's the one from the defaults.config configuration variable, handed over via the --config command line option). Some config values get pulled straight out of it, so if it's missing the entire operation is pointless β€” early bailout, no cap. πŸ’€
  • Then it either generates the spec file (if defaults.generate_spec_on_build is set to True) or checks it (if defaults.check_spec_on_build is set to a positive value). That check does different things depending on the level you gave in defaults.check_spec_on_build, ranging from a lil warning all the way to straight-up killing the action:
    1. Don't do the spec file check at all.
    2. Compare the spec files and throw a warning if the generated one and the existing one differ.
    3. Compare the spec files and throw a warning if they differ, then ask the user whether they wanna keep going.
    4. Compare the spec files and abort the action if there are any errors (during spec file generation or comparison).

Oh and btw, no spec file comparison happens at all β€” regardless of what defaults.check_spec_on_build says β€” if spec file generation is enabled. Obviously. πŸ™ˆ

  • Next: is there a Makefile? If a file named Makefile is chilling somewhere in the source tree, you pass this check. No Makefile? Early termination with a relevant exit code (the EXIT STATUS section has the deets).
  • If quilt integration is on (that's the default.quilt_support configuration option), quilt patches get de-applied.
  • Then we make the source tarball. A tar file named
"rpm/SOURCES/{spec_file.module_name}-{global.module_vendor}-{spec_file.module_version}.tar.bz2"

gets created, and the files in the src directory get added to it β€” with these exceptions:

  • The patches subdirectory is skipped.
  • All RPM files sitting in the top level of the src directory are skipped.
  • Files in the firmware source subdirectory are skipped if the boolean config option spec_file.firmware_include is False. And if there are files in that directory, you get a warning message about it.
  • Hidden files (the ones starting with a dot) are skipped, unless defaults.tar_all (controlled by the --tar-all action-specific command line option) is True.
  • Only files matching the pattern in defaults.src_patterns get added, if defaults.tar_strict (controlled by the --tar-strict action-specific command line option) is True. The default pattern only covers *.c, *.h, Makefile, Kbuild, and Kconfig files, which should work for most cases. 🌸
  • All the files from src/patches get copied over to rpm/SOURCES.
  • Now, if the current host architecture is among the architectures listed in spec_file.kernel_arch, and the rpmbuild check (rpmbuild -bc --nobuild) succeeded, and defaults.srpm (controlled via the --srpm action-specific command line option) isn't enabled β€” then it tries to build a binary RPM. Otherwise? Source RPM. That's the vibe check. πŸ‘‘
  • Finally, if quilt integration (default.quilt_support) is on, the quilt patches get applied back.

build_iso

This action takes a list of files and directories that should go onto the Driver Update Disk as non-option arguments. Then it does this:

  • Iterates over the files you gave it (recursing down into directories) and adds to the candidate list any file that satisfies all of these:
    • the file name ends with ".rpm",
    • rpmquery(1) successfully pulls architecture info out of the package,
    • the RPM is a binary package, or it's a source package and global.include_srpm is enabled,
    • the RPM is not a debug information package (its group isn't "Development/Debug"),
    • the RPM has a valid GPG signature (in case GPG signature checking is enabled β€” the RPM SIGNATURE VERIFICATION section has extra info on that).
  • All the qualifying candidates get copied into a temporary directory. Source RPMs land in the src subdirectory of the disk hierarchy, and binary RPMs land in rpms/arch β€” where arch is the architecture of the binary RPM, with all the i386, ..., i686 RPM architecture variants going into the i386 subdirectory. πŸŽ€
  • RPM repository metadata gets generated (with the createrepo(1) command) in each of those binary RPM directories.
  • An rhdd3 file containing the Driver Update Disk signature gets created in the temporary directory.
  • The ISO image gets created with the mkisofs(1) command. Its name comes from the defaults.isofile option (settable via the --isofile action-specific command line option). If you don't give an explicit ISO file name, it gets generated as
"dd-{spec_file.module_name}-{spec_file.module_version}-{spec_file.module_rpm_release}.{spec_file.rpm_dist}.iso"

or, if one of those configuration option values isn't a string, just plain old "dd.iso".

dump_config

Dumps the configuration dictionary exactly as it got evaluated by the process described in the Configuration generation section. The output file for the dump is set in the defaults.dump_config_name option. πŸ“

update_kabi

Generates files with kABI symbol information in the Red Hat Enterprise Linux kernel git repository, and optionally commits the changes. The action works through the kmods and RPMs you pass in the positional arguments, collecting versions (checksums) of symbols found in module information sections and/or "Requires" RPM tags, then adds files to the git repo for the symbols that aren't in there yet. ✨

The whole thing is controlled by these configuration options:

  • defaults.kabi_use_rpm_ko β€” whether to process the kmod files inside the provided RPMs instead of the RPM files themselves.
  • defaults.symvers_symbol_re (string) β€” the regular expression used for parsing lines in symvers files. It's expected to contain ver, symbol, file, and export groups.
  • defaults.kabi_files_overwrite (int) β€” whether to overwrite kABI files when files with the same name already exist. Can take these values:
    1. Don't overwrite existing kABI files.
    2. Ask the user on each possible overwrite.
    3. Overwrite existing kABI files.
  • defaults.kabi_check_symvers_conflicts (int) β€” whether to abort the action when a symbol version conflict is detected (between a newly added symbol and Module.symvers or the kABI white list). Can take these values:
    1. Continue processing.
    2. Ask the user on each occesion.
    3. Abort the action.
  • defaults.kabi_commit (bool) β€” whether to commit the changes at the end of a successful kABI files update.
  • defaults.symvers_path (string) [1] β€” path to the kernel's Module.symvers file.
  • defaults.kabi_whitelist (string) [1] β€” path to the kABI white list (in Module.symvers format).
  • defaults.git_dir (string) β€” path to the kernel's git repository.
  • defaults.kabi_dest_dir (string) [2] β€” path inside the kernel's repository to the directory with the kABI symbol files.
  • defaults.kabi_file_name_pattern (string) [2] β€” the name of a kABI symbol file.
  • defaults.kabi_file_template (string) [2] β€” the contents template of a kABI symbol file.
  • defaults.kabi_commit_log_template (string) [2] β€” template of a line in the commit log regarding a specific kABI symbol file.
  • defaults.kabi_commit_message (string) [2] β€” the commit message used for the git commit, if defaults.kabi_commit is enabled.

Notes, bestie, don't skip these:

[1] The arch variable gets overridden with the architecture of the kmod currently being processed ("x86_64" or "ppc64le", for example).

[2] Multiple overrides happen while those values are being evaluated:

  • sym β€” symbol being processed.
  • arch β€” architecture of the kmod being processed.
  • ver β€” version (checksum) of the symbol being processed.
  • kmod_file β€” file name of the kmod being processed.
  • kernel_file β€” path to the file inside the kernel tree that holds the definition of the symbol being processed.
  • kernel_export β€” type of the export ("EXPORT_SYMBOL", for example).

As of now, the rh-release profile comes with some genuinely useful default values for all those config options. 🫢


🧡 QUILT INTEGRATION 🧡

ddiskit supports the quilt(1) patch workflow. Specifically:

  • It de-applies quilt patches before building the source tarball, and applies them back after the build.
  • It uses the contents of the series file in the patches directory as the source of the patch list during the generate_spec action, and also during tarball creation in the build_rpm action. The series file itself is excluded from the list of files considered as patches.
  • It ignores hidden files during tarball creation in the build_rpm action, which conveniently avoids pulling in the .pc directory. πŸ’…

All of this behaviour β€” except that last part, which is controlled by defaults.tar_all β€” is controlled by the defaults.quilt_support configuration option, reachable through the --quilt-enable and --quilt-disable command line options. 🌈

🌈 MOCK INTEGRATION 🌈

okay so bestie, plot twist: ddiskit can hand off the whole RPM-building thing to mock(1) instead of doing it itself. this ✨magic✨ is turned on with the defaults.mock config option, which you flip via the --mock action-specific command line option (iykyk). once mock support is on, here's what changes, no cap:

  • mock(1) gets called instead of rpmbuild for both source and binary RPM creation. specifically: mock --buildsrpm does the source RPM, and then a little duo of mock --buildsrpm + mock --rebuild builds the binary RPM out of the source RPM that got created inside the mock environment 🫢
  • no build check (rpmbuild -bc --nobuild) happens at all β€” that whole "can we even build a binary RPM tho" step is skipped, because it's just assumed mock can handle it πŸ’…

πŸ” SOURCE CODE VERIFICATION πŸ”

so as part of the RPM build process (that's the build_rpm action, bestie), your source code can get vibe-checked against the git repository it supposedly came from πŸ‘€

here's the setup: the sources are assumed to live in the subdirectory given by the spec_file.module_build_dir config option, at the commit whose ID is in the spec_file.git_hash config option. the actual checking is done with the git-diff(1) command. and the path to the .git directory β€” the one holding the git repo your module's source should be checked against β€” goes in the defaults.git_repo config option.

whether the check even happens, and how serious it is, is decided by the default.check_git_src config option. its values mean:

  • 0 β€” the check is skipped, we're not doing this today πŸ’€
  • 1 β€” the check runs, and a warning gets issued if the sources differ from the ones in the repository
  • 2 β€” the check runs, and the build process gets aborted if the sources don't match or something else goes sideways during the check πŸ’€

oh and one lil detail: the source code verification happens when quilt patches are already de-applied. important.


πŸ” RPM GPG SIGNATURE VERIFICATION πŸ”

during the ISO build process (the build_iso action, iconic), the RPMs you're including can get checked for whether their GPG signature is present and correct. whether we check is controlled by the rpm_gpg_check.check_level config option, which takes one of these values:

  • 0 β€” check skipped, moving on πŸ™ˆ
  • 1 β€” check happens, and a warning is issued for every RPM that fails it
  • 2 β€” check happens, and RPMs that didn't pass get skipped
  • 3 β€” check happens, and ISO creation is aborted if even one RPM fails πŸ’€

the boolean config option rpm_gpg_check.use_keyring controls whether a specific keyring directory with a specific set of keys gets used, or whether we just roll with the GPG keys present in the host's RPM DB. if using a keyring directory is enabled, then the rpm_gpg_check.keyring_dir config option points to the directory holding the public GPG keys. and ngl, for those files to be usable, their names have to end with ".key" β€” which is, in fact, RPM's implicit assumption, not ours, we just live here πŸŽ€

this check is enabled by default in the rh-release profile, and it's what lets you verify that the RPMs added to the release ISO carry Red Hat's GPG signature. iconic behavior, fr fr.


πŸ› οΈ USAGE EXAMPLE πŸ› οΈ

here's the whole glow-up journey, step by step:

  1. create the initial directory structure and the module configuration.
$ ddiskit prepare_sources
Writing new config file (module.config)... OK
Creating directory structure for RPM build ... OK
Creating directory structure for source code ... OK
Put your module source code in src directory.
  1. copy your code into the src directory:
$ tree src
src
β”œβ”€β”€ drivers
β”‚   └── net
β”‚       └── ethernet
β”‚           └── broadcom
β”‚               β”œβ”€β”€ Makefile
β”‚               β”œβ”€β”€ tg3.c
β”‚               └── tg3.h
└── patches
    β”œβ”€β”€ 0001-test.patch
    └── 0002-test.patch
  • pls respect the directory hierarchy for drivers that are originally part of the kernel tree πŸ™
  • any additional patches for the code can go in the src/patches directory
  1. fill out the module.config and generate the spec file:
$ ddiskit generate_spec
Checking config ...
Config check ... OK
RPM spec file "rpm/SPECS/tg3.spec" exists!
Patches found, adding to the spec file:
  Patch0: 0001-test.patch
  Patch1: 0002-test.patch
Firmware directory is empty or nonexistent, skipping
Writing spec into rpm/SPECS/tg3.spec ... OK
  • the resulting spec file lands in the rpm/SPEC/ directory, and you can totally peek at it before moving on if you want πŸ‘€
  1. build the binary RPM:
$ ddiskit build_rpm
  1. build the Driver Update Disk ISO:
$ ddiskit build_iso

πŸ“ FILES πŸ“

  • /usr/share/ddiskit.config β€” the package default configuration.
  • /etc/ddiskit.config β€” the system-wide ("site") configuration.
  • ~/.ddiskitrc β€” the user configuration.
  • module.config β€” the default configuration name.
  • /usr/share/ddiskit/templates/spec β€” template for RPM spec file generation. the path to it can be overridden by changing the defaults.spec_template config option, or defaults.template_dir β€” pls refer to the File path evaluation section for the extra deets.
  • /usr/share/ddiskit/templates/config β€” template for module configuration. path can be overridden via the defaults.config_template config option, or defaults.template_dir β€” again, File path evaluation section, go look bestie.
  • /usr/share/ddiskit/profiles/default β€” the default profile. contains config options that are useful in non-specific cases (none, currently β€” yes, literally none, it's giving minimalist πŸ’…). the profile in use gets selected through the defaults.profile config option, and the path to the profile is configured by defaults.profile_dir, another config option β€” File path evaluation section, you know the drill.
  • /usr/share/ddiskit/profiles/rh-testing β€” profile holding config options used for testing DUP RPMs by Red Hat. this includes the disclaimer that the package is provided for testing purposes.
  • /usr/share/ddiskit/profiles/rh-release β€” profile holding config options used for the release DUP RPMs by Red Hat. this includes enablement of various strict checks (like Git commit ID and RPM GPG signature verification). very demure, very strict πŸ‘‘

🚦 EXIT STATUS 🚦

so when ddiskit bounces, here's what the number actually means:

  • 0 β€” successful execution πŸŽ‰
  • 1 β€” generic error (no additional information available, she's mysterious like that)
  • 2 β€” problems during command line argument parsing
  • 3 β€” problems during the configuration check phase (see the Configuration check section for more)
  • 4 β€” problem when trying to de-apply quilt patches (patches don't de-apply cleanly, for example)
  • 5 β€” problem when trying to apply quilt patches
  • 6 β€” problem during the sources verification
  • 7 β€” problem during RPM GPG signature verification
  • 8 β€” problem during git checkout of source files
  • 9 β€” problem during spec file comparison
  • 10 β€” symbol version (checksum) conflict occurred (update_kabi action)
  • 11 β€” problem during git add invocation (update_kabi action)
  • 12 β€” problem during git commit invocation (update_kabi action)
  • 32 β€” generic input/output error
  • 34 β€” error while reading configuration file
  • 35 β€” error while writing module configuration file (prepare_sources action)
  • 36 β€” spec template file read error (generate_spec action)
  • 38 β€” spec read error (unused currently)
  • 39 β€” spec write error (generate_spec action)
  • 41 β€” source archive write error (build_rpm action)
  • 42 β€” Makefile not found (build_rpm action)
  • 45 β€” Driver Update Disk signature write error
  • 47 β€” configuration dump file write error
  • 49 β€” directory structure creation error

πŸ› REPORTING BUGS πŸ›

if something's giving broken, problems with ddiskit should be reported to the ddiskit project bug tracker 🫢


πŸ“œ HISTORY πŸ“œ

story time, bestie πŸ“ the initial version of ddiskit was created by John W. Linville in the year 2005, for 2.6-based Red Hat Enterprise Linux and Fedora Core distributions (like 2.6.9-based RHEL 4). it filled "the same need which Doug Ledford's Device Driver Update Disk Devel Kit filled for prior generations of Red Hat distributions", which was used for the Linux 2.4-series based Red Hat distributions around the year 2003.

in 2007, the responsibility for maintaining and enhancing ddiskit was passed to Jon Masters, who then developed ddiskit during the RHEL 5 and RHEL 6 era. and the third incarnation of ddiskit was conceived in 2016 by Petr OroΕ‘, in an attempt to bring more automation to the process of Driver Update Disk creation, along with RHEL 7 support. three eras, one tool, main character energy fr fr πŸ‘‘


πŸ‘€ SEE ALSO πŸ‘€


and that's the whole page, bestie β€” every last drop πŸ’– the little footer at the bottom is just the ddiskit manual page signature (that DDISKIT(1) source/version line plus its date), so nothing more to read down there. go build something iconic, and remember: check your exit codes, respect your directory hierarchy, and never skip the vibe check βœ¨πŸ¦‹πŸ’…

πŸ“¦ where this page comes from