Thursday, January 14, 2010

Add a new config patch to a Linux kernel RPM

Sometimes it is necessary to patch a kernel and rebuild the original rpm so that the installation of the new kernel and its modules is easier. I will not explain here how to patch a kernel, so I assume that we already have a patch available that has been tested and applies correctly.

We must have the source rpm of the kernel we intend to patch and rebuild. The following explanation has been tested on a kernel rpm from a Fedora 10 distro and, in particular, I will focus on patching the kernel config files rather than patching the source files.

First of all, an explanation of how kernel configuration is achieved by the spec file of a Fedora kernel rpm:

1. Some extra config files are provided along with the kernel source tarball. These files are named 'config-*' and are declared in the spec file using the 'sourceNN:' directives. The config files are hierchical, that is, the definitions add hierchichally one on top the other. For example, the configuration file for the i686 architecture is produced by adding the config-generic, config-x86-generic and config-i686 config files. The perl script, also provided as an extra source file to the rpm, is responsible for doing the merge of the config files.

2. The config-* files are copied into the buildroot directory of the RPM, that is under BUILD/kernel- directory.

3. A 'make configs' rule is executed so that the config-* files are merged and produce a set of kernel-*.config files also in the build root directory. All configs are generated, even if they are not intended for the architecture that is going to be built.

4. At this point, all patches are applied using the 'ApplyPatch' macro in the spec file. Note that any patch that changes the kernel configuration must change the kernel*.config files, Patching the config-* files would not work as these are not taken into account for the build process after the patches are applied. Similarly, patching the .config file is not an option since this file will be overwritten by each kernel-*.config file during the build process.

5. The config files not intended for the architecture that is being built are deleted.

6. 'make oldconfig' is run on all the remaining kernel-*.config files, and the resulting config file is saved in the configs/ directory and removed from the root build directory. This is why the kernel-*.config files are not present even if we only run the patch stage of rpmbuild (-bp).

So, to have a patch that changes kernel config we have two options:

A. Edit the SOURCE/config-* files provided by the kernel source RPM and rebuild the source rpm with the new ones.

B. Generate a patch that modifies the kernel*.config files.

There some pros and cons to each option. Option B is more modular, so in case we choose to remove some kernel configuration in the future, all we have to do is remove the patch from the spec file, whereas if we use option A, the original source file is modified and it makes more difficult to revert some config changes.

On the contrary, option A is easier than option B because the kernel*.config files that must be patched are autogenerated during the rpm build process and are not available before then.

In order to apply option B, we should have a pristine kernel build root containing the patched kernel and the kernel*.config files so that these can be patched. Then, make a copy of the entire root and edit the kernel config files for the intended architectures, generate a patch by diff'ing both directories, copy it into the SOURCE directory and declare it in the SPEC file. Here is an example:

1. Generate a pristine build root of the linux kernel rpm

$ rpm -i kernel-
$ cd ~/rpmbuild

Edit the SPEC file, by adding an 'exit 1' sentence after the last patch is applied (a call to the 'ApplyPatch' macro). This causes the patch process to be interrupted after the kernel*.config files are merged but before they are oldconfig'ed and moved to the configs/ directory. Perhaps there is a more elegant way to do this, but I don't know of it.

Extract the sources and apply the patches:

$ rpmbuild --target=i686 -bp SPECS/kernel.spec

Note the -bp option tells rpmbuild to stop after the source tarball is extracted and all patches are applied. Because we added the 'exit 1' statement, rpmbuild will return an error which of course can be ignored.

2. Make the changes in the kernel config files

$ cd BUILD/kernel-
$ cp -a linux-
$ cd

Edit each kernel-*.config files that applies to the intended architectures.

Changing the config files may be a bit painful, depending on the dependencies of the config items that we change. To make sure that the changes are ok, at the end we must run 'make nonint_oldconfig' with does some sort of check on our config file. Another option is to copy each kernel*.config file as .config and configure the kernel manually using 'make menuconfig', and then copying it back to the original file.

3. Generate the patch

$ cd ..
$ diff -urN linux- > newpatch

Edit newpatch and make sure that only the changes to the kernel-*.config files are included. Then, rename and move the new patch to the SOURCES directory:

$ mv newpatch ../../SOURCES/linux-

And declare the new patch in the SPEC file, by adding this line after the last call to the ApplyPatch macro:

ApplyPatch linux-


And, of course, remove the 'exit 1' statement.

4. Rebuild the RPM

As usual, run:

$ rpmbuild -ba --target=i686 SPECS/kernel.spec

After these steps, both a binary RPM and a source RPM will be present under the RPMS/i686 and SRPMS directories, respectively.

Of course, if we already have a patch for the kernel, all we have do is copy it to the SOURCES directory with a proper name, add it to the spec file by using the ApplyPatch macro, and rebuild the rpm (rpmbuild -ba).

Note the Fedora kernel spec file builds several flavours of the same kernel architecture, for instance: with or without PAE, debug, SMP, and several combinations of these. So many combinations may take a lot of time to build the rpms. If we are interested in only the base kernel without PAE, debug, etc, all we have to do is add the "--with baseonly" option to the rpmbuild command. Other variants are allowed, --with / --without pae, debug, etc....

No comments: