Browse Source
- Make Kconfig parse the input .config more precisely - Support W=c and W=e options for Kconfig - Set Kconfig int/hex symbols to zero if the 'default' property is missing - Add .editorconfig - Add scripts/git.orderFile - Add a script to detect backward-incompatible changes in UAPI headers - Resolve the symlink passed to O= option properly - Use the user-supplied mtime for all files in the builtin initramfs, which provides better reproducible builds - Fix the direct execution of debian/rules for Debian package builds - Use build ID instead of the .gnu_debuglink section for the Debian dbg package -----BEGIN PGP SIGNATURE----- iQJJBAABCgAzFiEEbmPs18K1szRHjPqEPYsBB53g2wYFAmWnEQ8VHG1hc2FoaXJv eUBrZXJuZWwub3JnAAoJED2LAQed4NsGbn8P/RpJ6f4eYAVG/Jnsf5xkkuoOCdWP ADA9I5VfgiUzEZV48tjjUOOhk9LO/QDwlxtbLZjlo9jC5TI+IVrXzCu4ShRhmE+4 eM/VXFur9RN6CuNWNmkf7yzd0dawiwL4QR/0L82ZNmwXGymeEUzzmFviD5KfJRY8 z6bgA4jLu9qsHNzX8eYA2LU+jpOoNiRQAlGzTE0oDgQnv/ZXJB/H+8tEhzH85oZk F087IQCct25yGAbZhEkuX2PHx5kus9ICF72Pkqxh075aOQzfKIO8S3PPkt4nAiHK Cb6sahRcO7QwxH7MJVWgmfbXNMbs9p8fOj9Aiudl2EEWVRav1mw9UuA5kCnTh6vi LpI4bYNChl8fNTX2gX+Dfkmbc5r2Yl65ufW23VlRdZfdrXbJWlQbkkdvJeb7NoEj u6z26b/2WMaTecxr0Bw50PbleHYZwWIscN5lGoK6rgUU04mr4t8g1ejpcxfj+79S MfbpEvPGKMJjelRBHf2x4qzzHQZHeqIbaItCNt8wGSVipgTvrWED2UaaEnW02SoL pwIcBjV9xiUo8UUVil/R8W6xr/Ybv0lWYcIBzQjibiCzhFgw4adPnzZ6eTlaV+6e ne527SqxQ0gF3xgDhxOz4VUF/b4TlnVycArIl80Kk/sFd8jX+AObkCtamZEPc0Rz GjsorSF/s+Fw7XMp =HXZB -----END PGP SIGNATURE----- Merge tag 'kbuild-v6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/masahiroy/linux-kbuild Pull Kbuild updates from Masahiro Yamada: - Make Kconfig parse the input .config more precisely - Support W=c and W=e options for Kconfig - Set Kconfig int/hex symbols to zero if the 'default' property is missing - Add .editorconfig - Add scripts/git.orderFile - Add a script to detect backward-incompatible changes in UAPI headers - Resolve the symlink passed to O= option properly - Use the user-supplied mtime for all files in the builtin initramfs, which provides better reproducible builds - Fix the direct execution of debian/rules for Debian package builds - Use build ID instead of the .gnu_debuglink section for the Debian dbg package * tag 'kbuild-v6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/masahiroy/linux-kbuild: (53 commits) kbuild: deb-pkg: use debian/<package> for tmpdir kbuild: deb-pkg: move 'make headers' to build-arch kbuild: deb-pkg: do not search for 'scripts' directory under arch/ kbuild: deb-pkg: use build ID instead of debug link for dbg package kbuild: deb-pkg: use more debhelper commands in builddeb kbuild: deb-pkg: remove unneeded '-f $srctree/Makefile' in debian/rules kbuild: deb-pkg: allow to run debian/rules from output directory kbuild: deb-pkg: set DEB_* variables if debian/rules is directly executed kbuild: deb-pkg: squash scripts/package/deb-build-option to debian/rules kbuild: deb-pkg: factor out common Make options in debian/rules kbuild: deb-pkg: hard-code Build-Depends kbuild: deb-pkg: split debian/copyright from the mkdebian script gen_init_cpio: Apply mtime supplied by user to all file types kbuild: resolve symlinks for O= properly docs: dev-tools: Add UAPI checker documentation check-uapi: Introduce check-uapi.sh scripts: Introduce a default git.orderFile kconfig: WERROR unmet symbol dependency Add .editorconfig file for basic formatting kconfig: Use KCONFIG_CONFIG instead of .config ...master
Linus Torvalds
4 months ago
46 changed files with 1487 additions and 506 deletions
@ -0,0 +1,32 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only |
||||
|
||||
root = true |
||||
|
||||
[{*.{awk,c,dts,dtsi,dtso,h,mk,s,S},Kconfig,Makefile,Makefile.*}] |
||||
charset = utf-8 |
||||
end_of_line = lf |
||||
trim_trailing_whitespace = true |
||||
insert_final_newline = true |
||||
indent_style = tab |
||||
indent_size = 8 |
||||
|
||||
[*.{json,py,rs}] |
||||
charset = utf-8 |
||||
end_of_line = lf |
||||
trim_trailing_whitespace = true |
||||
insert_final_newline = true |
||||
indent_style = space |
||||
indent_size = 4 |
||||
|
||||
# this must be below the general *.py to overwrite it |
||||
[tools/{perf,power,rcu,testing/kunit}/**.py,] |
||||
indent_style = tab |
||||
indent_size = 8 |
||||
|
||||
[*.yaml] |
||||
charset = utf-8 |
||||
end_of_line = lf |
||||
trim_trailing_whitespace = unset |
||||
insert_final_newline = true |
||||
indent_style = space |
||||
indent_size = 2 |
@ -0,0 +1,477 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0-only |
||||
|
||||
============ |
||||
UAPI Checker |
||||
============ |
||||
|
||||
The UAPI checker (``scripts/check-uapi.sh``) is a shell script which |
||||
checks UAPI header files for userspace backwards-compatibility across |
||||
the git tree. |
||||
|
||||
Options |
||||
======= |
||||
|
||||
This section will describe the options with which ``check-uapi.sh`` |
||||
can be run. |
||||
|
||||
Usage:: |
||||
|
||||
check-uapi.sh [-b BASE_REF] [-p PAST_REF] [-j N] [-l ERROR_LOG] [-i] [-q] [-v] |
||||
|
||||
Available options:: |
||||
|
||||
-b BASE_REF Base git reference to use for comparison. If unspecified or empty, |
||||
will use any dirty changes in tree to UAPI files. If there are no |
||||
dirty changes, HEAD will be used. |
||||
-p PAST_REF Compare BASE_REF to PAST_REF (e.g. -p v6.1). If unspecified or empty, |
||||
will use BASE_REF^1. Must be an ancestor of BASE_REF. Only headers |
||||
that exist on PAST_REF will be checked for compatibility. |
||||
-j JOBS Number of checks to run in parallel (default: number of CPU cores). |
||||
-l ERROR_LOG Write error log to file (default: no error log is generated). |
||||
-i Ignore ambiguous changes that may or may not break UAPI compatibility. |
||||
-q Quiet operation. |
||||
-v Verbose operation (print more information about each header being checked). |
||||
|
||||
Environmental args:: |
||||
|
||||
ABIDIFF Custom path to abidiff binary |
||||
CC C compiler (default is "gcc") |
||||
ARCH Target architecture of C compiler (default is host arch) |
||||
|
||||
Exit codes:: |
||||
|
||||
0) Success |
||||
1) ABI difference detected |
||||
2) Prerequisite not met |
||||
|
||||
Examples |
||||
======== |
||||
|
||||
Basic Usage |
||||
----------- |
||||
|
||||
First, let's try making a change to a UAPI header file that obviously |
||||
won't break userspace:: |
||||
|
||||
cat << 'EOF' | patch -l -p1 |
||||
--- a/include/uapi/linux/acct.h |
||||
+++ b/include/uapi/linux/acct.h |
||||
@@ -21,7 +21,9 @@ |
||||
#include <asm/param.h> |
||||
#include <asm/byteorder.h> |
||||
|
||||
-/* |
||||
+#define FOO |
||||
+ |
||||
+/* |
||||
* comp_t is a 16-bit "floating" point number with a 3-bit base 8 |
||||
* exponent and a 13-bit fraction. |
||||
* comp2_t is 24-bit with 5-bit base 2 exponent and 20 bit fraction |
||||
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h |
||||
EOF |
||||
|
||||
Now, let's use the script to validate:: |
||||
|
||||
% ./scripts/check-uapi.sh |
||||
Installing user-facing UAPI headers from dirty tree... OK |
||||
Installing user-facing UAPI headers from HEAD... OK |
||||
Checking changes to UAPI headers between HEAD and dirty tree... |
||||
All 912 UAPI headers compatible with x86 appear to be backwards compatible |
||||
|
||||
Let's add another change that *might* break userspace:: |
||||
|
||||
cat << 'EOF' | patch -l -p1 |
||||
--- a/include/uapi/linux/bpf.h |
||||
+++ b/include/uapi/linux/bpf.h |
||||
@@ -74,7 +74,7 @@ struct bpf_insn { |
||||
__u8 dst_reg:4; /* dest register */ |
||||
__u8 src_reg:4; /* source register */ |
||||
__s16 off; /* signed offset */ |
||||
- __s32 imm; /* signed immediate constant */ |
||||
+ __u32 imm; /* unsigned immediate constant */ |
||||
}; |
||||
|
||||
/* Key of an a BPF_MAP_TYPE_LPM_TRIE entry */ |
||||
EOF |
||||
|
||||
The script will catch this:: |
||||
|
||||
% ./scripts/check-uapi.sh |
||||
Installing user-facing UAPI headers from dirty tree... OK |
||||
Installing user-facing UAPI headers from HEAD... OK |
||||
Checking changes to UAPI headers between HEAD and dirty tree... |
||||
==== ABI differences detected in include/linux/bpf.h from HEAD -> dirty tree ==== |
||||
[C] 'struct bpf_insn' changed: |
||||
type size hasn't changed |
||||
1 data member change: |
||||
type of '__s32 imm' changed: |
||||
typedef name changed from __s32 to __u32 at int-ll64.h:27:1 |
||||
underlying type 'int' changed: |
||||
type name changed from 'int' to 'unsigned int' |
||||
type size hasn't changed |
||||
================================================================================== |
||||
|
||||
error - 1/912 UAPI headers compatible with x86 appear _not_ to be backwards compatible |
||||
|
||||
In this case, the script is reporting the type change because it could |
||||
break a userspace program that passes in a negative number. Now, let's |
||||
say you know that no userspace program could possibly be using a negative |
||||
value in ``imm``, so changing to an unsigned type there shouldn't hurt |
||||
anything. You can pass the ``-i`` flag to the script to ignore changes |
||||
in which the userspace backwards compatibility is ambiguous:: |
||||
|
||||
% ./scripts/check-uapi.sh -i |
||||
Installing user-facing UAPI headers from dirty tree... OK |
||||
Installing user-facing UAPI headers from HEAD... OK |
||||
Checking changes to UAPI headers between HEAD and dirty tree... |
||||
All 912 UAPI headers compatible with x86 appear to be backwards compatible |
||||
|
||||
Now, let's make a similar change that *will* break userspace:: |
||||
|
||||
cat << 'EOF' | patch -l -p1 |
||||
--- a/include/uapi/linux/bpf.h |
||||
+++ b/include/uapi/linux/bpf.h |
||||
@@ -71,8 +71,8 @@ enum { |
||||
|
||||
struct bpf_insn { |
||||
__u8 code; /* opcode */ |
||||
- __u8 dst_reg:4; /* dest register */ |
||||
__u8 src_reg:4; /* source register */ |
||||
+ __u8 dst_reg:4; /* dest register */ |
||||
__s16 off; /* signed offset */ |
||||
__s32 imm; /* signed immediate constant */ |
||||
}; |
||||
EOF |
||||
|
||||
Since we're re-ordering an existing struct member, there's no ambiguity, |
||||
and the script will report the breakage even if you pass ``-i``:: |
||||
|
||||
% ./scripts/check-uapi.sh -i |
||||
Installing user-facing UAPI headers from dirty tree... OK |
||||
Installing user-facing UAPI headers from HEAD... OK |
||||
Checking changes to UAPI headers between HEAD and dirty tree... |
||||
==== ABI differences detected in include/linux/bpf.h from HEAD -> dirty tree ==== |
||||
[C] 'struct bpf_insn' changed: |
||||
type size hasn't changed |
||||
2 data member changes: |
||||
'__u8 dst_reg' offset changed from 8 to 12 (in bits) (by +4 bits) |
||||
'__u8 src_reg' offset changed from 12 to 8 (in bits) (by -4 bits) |
||||
================================================================================== |
||||
|
||||
error - 1/912 UAPI headers compatible with x86 appear _not_ to be backwards compatible |
||||
|
||||
Let's commit the breaking change, then commit the innocuous change:: |
||||
|
||||
% git commit -m 'Breaking UAPI change' include/uapi/linux/bpf.h |
||||
[detached HEAD f758e574663a] Breaking UAPI change |
||||
1 file changed, 1 insertion(+), 1 deletion(-) |
||||
% git commit -m 'Innocuous UAPI change' include/uapi/linux/acct.h |
||||
[detached HEAD 2e87df769081] Innocuous UAPI change |
||||
1 file changed, 3 insertions(+), 1 deletion(-) |
||||
|
||||
Now, let's run the script again with no arguments:: |
||||
|
||||
% ./scripts/check-uapi.sh |
||||
Installing user-facing UAPI headers from HEAD... OK |
||||
Installing user-facing UAPI headers from HEAD^1... OK |
||||
Checking changes to UAPI headers between HEAD^1 and HEAD... |
||||
All 912 UAPI headers compatible with x86 appear to be backwards compatible |
||||
|
||||
It doesn't catch any breaking change because, by default, it only |
||||
compares ``HEAD`` to ``HEAD^1``. The breaking change was committed on |
||||
``HEAD~2``. If we wanted the search scope to go back further, we'd have to |
||||
use the ``-p`` option to pass a different past reference. In this case, |
||||
let's pass ``-p HEAD~2`` to the script so it checks UAPI changes between |
||||
``HEAD~2`` and ``HEAD``:: |
||||
|
||||
% ./scripts/check-uapi.sh -p HEAD~2 |
||||
Installing user-facing UAPI headers from HEAD... OK |
||||
Installing user-facing UAPI headers from HEAD~2... OK |
||||
Checking changes to UAPI headers between HEAD~2 and HEAD... |
||||
==== ABI differences detected in include/linux/bpf.h from HEAD~2 -> HEAD ==== |
||||
[C] 'struct bpf_insn' changed: |
||||
type size hasn't changed |
||||
2 data member changes: |
||||
'__u8 dst_reg' offset changed from 8 to 12 (in bits) (by +4 bits) |
||||
'__u8 src_reg' offset changed from 12 to 8 (in bits) (by -4 bits) |
||||
============================================================================== |
||||
|
||||
error - 1/912 UAPI headers compatible with x86 appear _not_ to be backwards compatible |
||||
|
||||
Alternatively, we could have also run with ``-b HEAD~``. This would set the |
||||
base reference to ``HEAD~`` so then the script would compare it to ``HEAD~^1``. |
||||
|
||||
Architecture-specific Headers |
||||
----------------------------- |
||||
|
||||
Consider this change:: |
||||
|
||||
cat << 'EOF' | patch -l -p1 |
||||
--- a/arch/arm64/include/uapi/asm/sigcontext.h |
||||
+++ b/arch/arm64/include/uapi/asm/sigcontext.h |
||||
@@ -70,6 +70,7 @@ struct sigcontext { |
||||
struct _aarch64_ctx { |
||||
__u32 magic; |
||||
__u32 size; |
||||
+ __u32 new_var; |
||||
}; |
||||
|
||||
#define FPSIMD_MAGIC 0x46508001 |
||||
EOF |
||||
|
||||
This is a change to an arm64-specific UAPI header file. In this example, I'm |
||||
running the script from an x86 machine with an x86 compiler, so, by default, |
||||
the script only checks x86-compatible UAPI header files:: |
||||
|
||||
% ./scripts/check-uapi.sh |
||||
Installing user-facing UAPI headers from dirty tree... OK |
||||
Installing user-facing UAPI headers from HEAD... OK |
||||
No changes to UAPI headers were applied between HEAD and dirty tree |
||||
|
||||
With an x86 compiler, we can't check header files in ``arch/arm64``, so the |
||||
script doesn't even try. |
||||
|
||||
If we want to check the header file, we'll have to use an arm64 compiler and |
||||
set ``ARCH`` accordingly:: |
||||
|
||||
% CC=aarch64-linux-gnu-gcc ARCH=arm64 ./scripts/check-uapi.sh |
||||
Installing user-facing UAPI headers from dirty tree... OK |
||||
Installing user-facing UAPI headers from HEAD... OK |
||||
Checking changes to UAPI headers between HEAD and dirty tree... |
||||
==== ABI differences detected in include/asm/sigcontext.h from HEAD -> dirty tree ==== |
||||
[C] 'struct _aarch64_ctx' changed: |
||||
type size changed from 64 to 96 (in bits) |
||||
1 data member insertion: |
||||
'__u32 new_var', at offset 64 (in bits) at sigcontext.h:73:1 |
||||
-- snip -- |
||||
[C] 'struct zt_context' changed: |
||||
type size changed from 128 to 160 (in bits) |
||||
2 data member changes (1 filtered): |
||||
'__u16 nregs' offset changed from 64 to 96 (in bits) (by +32 bits) |
||||
'__u16 __reserved[3]' offset changed from 80 to 112 (in bits) (by +32 bits) |
||||
======================================================================================= |
||||
|
||||
error - 1/884 UAPI headers compatible with arm64 appear _not_ to be backwards compatible |
||||
|
||||
We can see with ``ARCH`` and ``CC`` set properly for the file, the ABI |
||||
change is reported properly. Also notice that the total number of UAPI |
||||
header files checked by the script changes. This is because the number |
||||
of headers installed for arm64 platforms is different than x86. |
||||
|
||||
Cross-Dependency Breakages |
||||
-------------------------- |
||||
|
||||
Consider this change:: |
||||
|
||||
cat << 'EOF' | patch -l -p1 |
||||
--- a/include/uapi/linux/types.h |
||||
+++ b/include/uapi/linux/types.h |
||||
@@ -52,7 +52,7 @@ typedef __u32 __bitwise __wsum; |
||||
#define __aligned_be64 __be64 __attribute__((aligned(8))) |
||||
#define __aligned_le64 __le64 __attribute__((aligned(8))) |
||||
|
||||
-typedef unsigned __bitwise __poll_t; |
||||
+typedef unsigned short __bitwise __poll_t; |
||||
|
||||
#endif /* __ASSEMBLY__ */ |
||||
#endif /* _UAPI_LINUX_TYPES_H */ |
||||
EOF |
||||
|
||||
Here, we're changing a ``typedef`` in ``types.h``. This doesn't break |
||||
a UAPI in ``types.h``, but other UAPIs in the tree may break due to |
||||
this change:: |
||||
|
||||
% ./scripts/check-uapi.sh |
||||
Installing user-facing UAPI headers from dirty tree... OK |
||||
Installing user-facing UAPI headers from HEAD... OK |
||||
Checking changes to UAPI headers between HEAD and dirty tree... |
||||
==== ABI differences detected in include/linux/eventpoll.h from HEAD -> dirty tree ==== |
||||
[C] 'struct epoll_event' changed: |
||||
type size changed from 96 to 80 (in bits) |
||||
2 data member changes: |
||||
type of '__poll_t events' changed: |
||||
underlying type 'unsigned int' changed: |
||||
type name changed from 'unsigned int' to 'unsigned short int' |
||||
type size changed from 32 to 16 (in bits) |
||||
'__u64 data' offset changed from 32 to 16 (in bits) (by -16 bits) |
||||
======================================================================================== |
||||
include/linux/eventpoll.h did not change between HEAD and dirty tree... |
||||
It's possible a change to one of the headers it includes caused this error: |
||||
#include <linux/fcntl.h> |
||||
#include <linux/types.h> |
||||
|
||||
Note that the script noticed the failing header file did not change, |
||||
so it assumes one of its includes must have caused the breakage. Indeed, |
||||
we can see ``linux/types.h`` is used from ``eventpoll.h``. |
||||
|
||||
UAPI Header Removals |
||||
-------------------- |
||||
|
||||
Consider this change:: |
||||
|
||||
cat << 'EOF' | patch -l -p1 |
||||
diff --git a/include/uapi/asm-generic/Kbuild b/include/uapi/asm-generic/Kbuild |
||||
index ebb180aac74e..a9c88b0a8b3b 100644 |
||||
--- a/include/uapi/asm-generic/Kbuild |
||||
+++ b/include/uapi/asm-generic/Kbuild |
||||
@@ -31,6 +31,6 @@ mandatory-y += stat.h |
||||
mandatory-y += statfs.h |
||||
mandatory-y += swab.h |
||||
mandatory-y += termbits.h |
||||
-mandatory-y += termios.h |
||||
+#mandatory-y += termios.h |
||||
mandatory-y += types.h |
||||
mandatory-y += unistd.h |
||||
EOF |
||||
|
||||
This script removes a UAPI header file from the install list. Let's run |
||||
the script:: |
||||
|
||||
% ./scripts/check-uapi.sh |
||||
Installing user-facing UAPI headers from dirty tree... OK |
||||
Installing user-facing UAPI headers from HEAD... OK |
||||
Checking changes to UAPI headers between HEAD and dirty tree... |
||||
==== UAPI header include/asm/termios.h was removed between HEAD and dirty tree ==== |
||||
|
||||
error - 1/912 UAPI headers compatible with x86 appear _not_ to be backwards compatible |
||||
|
||||
Removing a UAPI header is considered a breaking change, and the script |
||||
will flag it as such. |
||||
|
||||
Checking Historic UAPI Compatibility |
||||
------------------------------------ |
||||
|
||||
You can use the ``-b`` and ``-p`` options to examine different chunks of your |
||||
git tree. For example, to check all changed UAPI header files between tags |
||||
v6.0 and v6.1, you'd run:: |
||||
|
||||
% ./scripts/check-uapi.sh -b v6.1 -p v6.0 |
||||
Installing user-facing UAPI headers from v6.1... OK |
||||
Installing user-facing UAPI headers from v6.0... OK |
||||
Checking changes to UAPI headers between v6.0 and v6.1... |
||||
|
||||
--- snip --- |
||||
error - 37/907 UAPI headers compatible with x86 appear _not_ to be backwards compatible |
||||
|
||||
Note: Before v5.3, a header file needed by the script is not present, |
||||
so the script is unable to check changes before then. |
||||
|
||||
You'll notice that the script detected many UAPI changes that are not |
||||
backwards compatible. Knowing that kernel UAPIs are supposed to be stable |
||||
forever, this is an alarming result. This brings us to the next section: |
||||
caveats. |
||||
|
||||
Caveats |
||||
======= |
||||
|
||||
The UAPI checker makes no assumptions about the author's intention, so some |
||||
types of changes may be flagged even though they intentionally break UAPI. |
||||
|
||||
Removals For Refactoring or Deprecation |
||||
--------------------------------------- |
||||
|
||||
Sometimes drivers for very old hardware are removed, such as in this example:: |
||||
|
||||
% ./scripts/check-uapi.sh -b ba47652ba655 |
||||
Installing user-facing UAPI headers from ba47652ba655... OK |
||||
Installing user-facing UAPI headers from ba47652ba655^1... OK |
||||
Checking changes to UAPI headers between ba47652ba655^1 and ba47652ba655... |
||||
==== UAPI header include/linux/meye.h was removed between ba47652ba655^1 and ba47652ba655 ==== |
||||
|
||||
error - 1/910 UAPI headers compatible with x86 appear _not_ to be backwards compatible |
||||
|
||||
The script will always flag removals (even if they're intentional). |
||||
|
||||
Struct Expansions |
||||
----------------- |
||||
|
||||
Depending on how a structure is handled in kernelspace, a change which |
||||
expands a struct could be non-breaking. |
||||
|
||||
If a struct is used as the argument to an ioctl, then the kernel driver |
||||
must be able to handle ioctl commands of any size. Beyond that, you need |
||||
to be careful when copying data from the user. Say, for example, that |
||||
``struct foo`` is changed like this:: |
||||
|
||||
struct foo { |
||||
__u64 a; /* added in version 1 */ |
||||
+ __u32 b; /* added in version 2 */ |
||||
+ __u32 c; /* added in version 2 */ |
||||
} |
||||
|
||||
By default, the script will flag this kind of change for further review:: |
||||
|
||||
[C] 'struct foo' changed: |
||||
type size changed from 64 to 128 (in bits) |
||||
2 data member insertions: |
||||
'__u32 b', at offset 64 (in bits) |
||||
'__u32 c', at offset 96 (in bits) |
||||
|
||||
However, it is possible that this change was made safely. |
||||
|
||||
If a userspace program was built with version 1, it will think |
||||
``sizeof(struct foo)`` is 8. That size will be encoded in the |
||||
ioctl value that gets sent to the kernel. If the kernel is built |
||||
with version 2, it will think the ``sizeof(struct foo)`` is 16. |
||||
|
||||
The kernel can use the ``_IOC_SIZE`` macro to get the size encoded |
||||
in the ioctl code that the user passed in and then use |
||||
``copy_struct_from_user()`` to safely copy the value:: |
||||
|
||||
int handle_ioctl(unsigned long cmd, unsigned long arg) |
||||
{ |
||||
switch _IOC_NR(cmd) { |
||||
0x01: { |
||||
struct foo my_cmd; /* size 16 in the kernel */ |
||||
|
||||
ret = copy_struct_from_user(&my_cmd, arg, sizeof(struct foo), _IOC_SIZE(cmd)); |
||||
... |
||||
|
||||
``copy_struct_from_user`` will zero the struct in the kernel and then copy |
||||
only the bytes passed in from the user (leaving new members zeroized). |
||||
If the user passed in a larger struct, the extra members are ignored. |
||||
|
||||
If you know this situation is accounted for in the kernel code, you can |
||||
pass ``-i`` to the script, and struct expansions like this will be ignored. |
||||
|
||||
Flex Array Migration |
||||
-------------------- |
||||
|
||||
While the script handles expansion into an existing flex array, it does |
||||
still flag initial migration to flex arrays from 1-element fake flex |
||||
arrays. For example:: |
||||
|
||||
struct foo { |
||||
__u32 x; |
||||
- __u32 flex[1]; /* fake flex */ |
||||
+ __u32 flex[]; /* real flex */ |
||||
}; |
||||
|
||||
This change would be flagged by the script:: |
||||
|
||||
[C] 'struct foo' changed: |
||||
type size changed from 64 to 32 (in bits) |
||||
1 data member change: |
||||
type of '__u32 flex[1]' changed: |
||||
type name changed from '__u32[1]' to '__u32[]' |
||||
array type size changed from 32 to 'unknown' |
||||
array type subrange 1 changed length from 1 to 'unknown' |
||||
|
||||
At this time, there's no way to filter these types of changes, so be |
||||
aware of this possible false positive. |
||||
|
||||
Summary |
||||
------- |
||||
|
||||
While many types of false positives are filtered out by the script, |
||||
it's possible there are some cases where the script flags a change |
||||
which does not break UAPI. It's also possible a change which *does* |
||||
break userspace would not be flagged by this script. While the script |
||||
has been run on much of the kernel history, there could still be corner |
||||
cases that are not accounted for. |
||||
|
||||
The intention is for this script to be used as a quick check for |
||||
maintainers or automated tooling, not as the end-all authority on |
||||
patch compatibility. It's best to remember: use your best judgment |
||||
(and ideally a unit test in userspace) to make sure your UAPI changes |
||||
are backwards-compatible! |
@ -0,0 +1,573 @@
|
||||
#!/bin/bash |
||||
# SPDX-License-Identifier: GPL-2.0-only |
||||
# Script to check commits for UAPI backwards compatibility |
||||
|
||||
set -o errexit |
||||
set -o pipefail |
||||
|
||||
print_usage() { |
||||
name=$(basename "$0") |
||||
cat << EOF |
||||
$name - check for UAPI header stability across Git commits |
||||
|
||||
By default, the script will check to make sure the latest commit (or current |
||||
dirty changes) did not introduce ABI changes when compared to HEAD^1. You can |
||||
check against additional commit ranges with the -b and -p options. |
||||
|
||||
The script will not check UAPI headers for architectures other than the one |
||||
defined in ARCH. |
||||
|
||||
Usage: $name [-b BASE_REF] [-p PAST_REF] [-j N] [-l ERROR_LOG] [-i] [-q] [-v] |
||||
|
||||
Options: |
||||
-b BASE_REF Base git reference to use for comparison. If unspecified or empty, |
||||
will use any dirty changes in tree to UAPI files. If there are no |
||||
dirty changes, HEAD will be used. |
||||
-p PAST_REF Compare BASE_REF to PAST_REF (e.g. -p v6.1). If unspecified or empty, |
||||
will use BASE_REF^1. Must be an ancestor of BASE_REF. Only headers |
||||
that exist on PAST_REF will be checked for compatibility. |
||||
-j JOBS Number of checks to run in parallel (default: number of CPU cores). |
||||
-l ERROR_LOG Write error log to file (default: no error log is generated). |
||||
-i Ignore ambiguous changes that may or may not break UAPI compatibility. |
||||
-q Quiet operation. |
||||
-v Verbose operation (print more information about each header being checked). |
||||
|
||||
Environmental args: |
||||
ABIDIFF Custom path to abidiff binary |
||||
CC C compiler (default is "gcc") |
||||
ARCH Target architecture for the UAPI check (default is host arch) |
||||
|
||||
Exit codes: |
||||
$SUCCESS) Success |
||||
$FAIL_ABI) ABI difference detected |
||||
$FAIL_PREREQ) Prerequisite not met |
||||
EOF |
||||
} |
||||
|
||||
readonly SUCCESS=0 |
||||
readonly FAIL_ABI=1 |
||||
readonly FAIL_PREREQ=2 |
||||
|
||||
# Print to stderr |
||||
eprintf() { |
||||
# shellcheck disable=SC2059 |
||||
printf "$@" >&2 |
||||
} |
||||
|
||||
# Expand an array with a specific character (similar to Python string.join()) |
||||
join() { |
||||
local IFS="$1" |
||||
shift |
||||
printf "%s" "$*" |
||||
} |
||||
|
||||
# Create abidiff suppressions |
||||
gen_suppressions() { |
||||
# Common enum variant names which we don't want to worry about |
||||
# being shifted when new variants are added. |
||||
local -a enum_regex=( |
||||
".*_AFTER_LAST$" |
||||
".*_CNT$" |
||||
".*_COUNT$" |
||||
".*_END$" |
||||
".*_LAST$" |
||||
".*_MASK$" |
||||
".*_MAX$" |
||||
".*_MAX_BIT$" |
||||
".*_MAX_BPF_ATTACH_TYPE$" |
||||
".*_MAX_ID$" |
||||
".*_MAX_SHIFT$" |
||||
".*_NBITS$" |
||||
".*_NETDEV_NUMHOOKS$" |
||||
".*_NFT_META_IIFTYPE$" |
||||
".*_NL80211_ATTR$" |
||||
".*_NLDEV_NUM_OPS$" |
||||
".*_NUM$" |
||||
".*_NUM_ELEMS$" |
||||
".*_NUM_IRQS$" |
||||
".*_SIZE$" |
||||
".*_TLSMAX$" |
||||
"^MAX_.*" |
||||
"^NUM_.*" |
||||
) |
||||
|
||||
# Common padding field names which can be expanded into |
||||
# without worrying about users. |
||||
local -a padding_regex=( |
||||
".*end$" |
||||
".*pad$" |
||||
".*pad[0-9]?$" |
||||
".*pad_[0-9]?$" |
||||
".*padding$" |
||||
".*padding[0-9]?$" |
||||
".*padding_[0-9]?$" |
||||
".*res$" |
||||
".*resv$" |
||||
".*resv[0-9]?$" |
||||
".*resv_[0-9]?$" |
||||
".*reserved$" |
||||
".*reserved[0-9]?$" |
||||
".*reserved_[0-9]?$" |
||||
".*rsvd[0-9]?$" |
||||
".*unused$" |
||||
) |
||||
|
||||
cat << EOF |
||||
[suppress_type] |
||||
type_kind = enum |
||||
changed_enumerators_regexp = $(join , "${enum_regex[@]}") |
||||
EOF |
||||
|
||||
for p in "${padding_regex[@]}"; do |
||||
cat << EOF |
||||
[suppress_type] |
||||
type_kind = struct |
||||
has_data_member_inserted_at = offset_of_first_data_member_regexp(${p}) |
||||
EOF |
||||
done |
||||
|
||||
if [ "$IGNORE_AMBIGUOUS_CHANGES" = "true" ]; then |
||||
cat << EOF |
||||
[suppress_type] |
||||
type_kind = struct |
||||
has_data_member_inserted_at = end |
||||
has_size_change = yes |
||||
EOF |
||||
fi |
||||
} |
||||
|
||||
# Check if git tree is dirty |
||||
tree_is_dirty() { |
||||
! git diff --quiet |
||||
} |
||||
|
||||
# Get list of files installed in $ref |
||||
get_file_list() { |
||||
local -r ref="$1" |
||||
local -r tree="$(get_header_tree "$ref")" |
||||
|
||||
# Print all installed headers, filtering out ones that can't be compiled |
||||
find "$tree" -type f -name '*.h' -printf '%P\n' | grep -v -f "$INCOMPAT_LIST" |
||||
} |
||||
|
||||
# Add to the list of incompatible headers |
||||
add_to_incompat_list() { |
||||
local -r ref="$1" |
||||
|
||||
# Start with the usr/include/Makefile to get a list of the headers |
||||
# that don't compile using this method. |
||||
if [ ! -f usr/include/Makefile ]; then |
||||
eprintf "error - no usr/include/Makefile present at %s\n" "$ref" |
||||
eprintf "Note: usr/include/Makefile was added in the v5.3 kernel release\n" |
||||
exit "$FAIL_PREREQ" |
||||
fi |
||||
{ |
||||
# shellcheck disable=SC2016 |
||||
printf 'all: ; @echo $(no-header-test)\n' |
||||
cat usr/include/Makefile |
||||
} | SRCARCH="$ARCH" make --always-make -f - | tr " " "\n" \ |
||||
| grep -v "asm-generic" >> "$INCOMPAT_LIST" |
||||
|
||||
# The makefile also skips all asm-generic files, but prints "asm-generic/%" |
||||
# which won't work for our grep match. Instead, print something grep will match. |
||||
printf "asm-generic/.*\.h\n" >> "$INCOMPAT_LIST" |
||||
} |
||||
|
||||
# Compile the simple test app |
||||
do_compile() { |
||||
local -r inc_dir="$1" |
||||
local -r header="$2" |
||||
local -r out="$3" |
||||
printf "int main(void) { return 0; }\n" | \ |
||||
"$CC" -c \ |
||||
-o "$out" \ |
||||
-x c \ |
||||
-O0 \ |
||||
-std=c90 \ |
||||
-fno-eliminate-unused-debug-types \ |
||||
-g \ |
||||
"-I${inc_dir}" \ |
||||
-include "$header" \ |
||||
- |
||||
} |
||||
|
||||
# Run make headers_install |
||||
run_make_headers_install() { |
||||
local -r ref="$1" |
||||
local -r install_dir="$(get_header_tree "$ref")" |
||||
make -j "$MAX_THREADS" ARCH="$ARCH" INSTALL_HDR_PATH="$install_dir" \ |
||||
headers_install > /dev/null |
||||
} |
||||
|
||||
# Install headers for both git refs |
||||
install_headers() { |
||||
local -r base_ref="$1" |
||||
local -r past_ref="$2" |
||||
|
||||
for ref in "$base_ref" "$past_ref"; do |
||||
printf "Installing user-facing UAPI headers from %s... " "${ref:-dirty tree}" |
||||
if [ -n "$ref" ]; then |
||||
git archive --format=tar --prefix="${ref}-archive/" "$ref" \ |
||||
| (cd "$TMP_DIR" && tar xf -) |
||||
( |
||||
cd "${TMP_DIR}/${ref}-archive" |
||||
run_make_headers_install "$ref" |
||||
add_to_incompat_list "$ref" "$INCOMPAT_LIST" |
||||
) |
||||
else |
||||
run_make_headers_install "$ref" |
||||
add_to_incompat_list "$ref" "$INCOMPAT_LIST" |
||||
fi |
||||
printf "OK\n" |
||||
done |
||||
sort -u -o "$INCOMPAT_LIST" "$INCOMPAT_LIST" |
||||
sed -i -e '/^$/d' "$INCOMPAT_LIST" |
||||
} |
||||
|
||||
# Print the path to the headers_install tree for a given ref |
||||
get_header_tree() { |
||||
local -r ref="$1" |
||||
printf "%s" "${TMP_DIR}/${ref}/usr" |
||||
} |
||||
|
||||
# Check file list for UAPI compatibility |
||||
check_uapi_files() { |
||||
local -r base_ref="$1" |
||||
local -r past_ref="$2" |
||||
local -r abi_error_log="$3" |
||||
|
||||
local passed=0; |
||||
local failed=0; |
||||
local -a threads=() |
||||
set -o errexit |
||||
|
||||
printf "Checking changes to UAPI headers between %s and %s...\n" "$past_ref" "${base_ref:-dirty tree}" |
||||
# Loop over all UAPI headers that were installed by $past_ref (if they only exist on $base_ref, |
||||
# there's no way they're broken and no way to compare anyway) |
||||
while read -r file; do |
||||
if [ "${#threads[@]}" -ge "$MAX_THREADS" ]; then |
||||
if wait "${threads[0]}"; then |
||||
passed=$((passed + 1)) |
||||
else |
||||
failed=$((failed + 1)) |
||||
fi |
||||
threads=("${threads[@]:1}") |
||||
fi |
||||
|
||||
check_individual_file "$base_ref" "$past_ref" "$file" & |
||||
threads+=("$!") |
||||
done < <(get_file_list "$past_ref") |
||||
|
||||
for t in "${threads[@]}"; do |
||||
if wait "$t"; then |
||||
passed=$((passed + 1)) |
||||
else |
||||
failed=$((failed + 1)) |
||||
fi |
||||
done |
||||
|
||||
if [ -n "$abi_error_log" ]; then |
||||
printf 'Generated by "%s %s" from git ref %s\n\n' \ |
||||
"$0" "$*" "$(git rev-parse HEAD)" > "$abi_error_log" |
||||
fi |
||||
|
||||
while read -r error_file; do |
||||
{ |
||||
cat "$error_file" |
||||
printf "\n\n" |
||||
} | tee -a "${abi_error_log:-/dev/null}" >&2 |
||||
done < <(find "$TMP_DIR" -type f -name '*.error' | sort) |
||||
|
||||
total="$((passed + failed))" |
||||
if [ "$failed" -gt 0 ]; then |
||||
eprintf "error - %d/%d UAPI headers compatible with %s appear _not_ to be backwards compatible\n" \ |
||||
"$failed" "$total" "$ARCH" |
||||
if [ -n "$abi_error_log" ]; then |
||||
eprintf "Failure summary saved to %s\n" "$abi_error_log" |
||||
fi |
||||
else |
||||
printf "All %d UAPI headers compatible with %s appear to be backwards compatible\n" \ |
||||
"$total" "$ARCH" |
||||
fi |
||||
|
||||
return "$failed" |
||||
} |
||||
|
||||
# Check an individual file for UAPI compatibility |
||||
check_individual_file() { |
||||
local -r base_ref="$1" |
||||
local -r past_ref="$2" |
||||
local -r file="$3" |
||||
|
||||
local -r base_header="$(get_header_tree "$base_ref")/${file}" |
||||
local -r past_header="$(get_header_tree "$past_ref")/${file}" |
||||
|
||||
if [ ! -f "$base_header" ]; then |
||||
mkdir -p "$(dirname "$base_header")" |
||||
printf "==== UAPI header %s was removed between %s and %s ====" \ |
||||
"$file" "$past_ref" "$base_ref" \ |
||||
> "${base_header}.error" |
||||
return 1 |
||||
fi |
||||
|
||||
compare_abi "$file" "$base_header" "$past_header" "$base_ref" "$past_ref" |
||||
} |
||||
|
||||
# Perform the A/B compilation and compare output ABI |
||||
compare_abi() { |
||||
local -r file="$1" |
||||
local -r base_header="$2" |
||||
local -r past_header="$3" |
||||
local -r base_ref="$4" |
||||
local -r past_ref="$5" |
||||
local -r log="${TMP_DIR}/log/${file}.log" |
||||
local -r error_log="${TMP_DIR}/log/${file}.error" |
||||
|
||||
mkdir -p "$(dirname "$log")" |
||||
|
||||
if ! do_compile "$(get_header_tree "$base_ref")/include" "$base_header" "${base_header}.bin" 2> "$log"; then |
||||
{ |
||||
warn_str=$(printf "==== Could not compile version of UAPI header %s at %s ====\n" \ |
||||
"$file" "$base_ref") |
||||
printf "%s\n" "$warn_str" |
||||
cat "$log" |
||||
printf -- "=%.0s" $(seq 0 ${#warn_str}) |
||||
} > "$error_log" |
||||
return 1 |
||||
fi |
||||
|
||||
if ! do_compile "$(get_header_tree "$past_ref")/include" "$past_header" "${past_header}.bin" 2> "$log"; then |
||||
{ |
||||
warn_str=$(printf "==== Could not compile version of UAPI header %s at %s ====\n" \ |
||||
"$file" "$past_ref") |
||||
printf "%s\n" "$warn_str" |
||||
cat "$log" |
||||
printf -- "=%.0s" $(seq 0 ${#warn_str}) |
||||
} > "$error_log" |
||||
return 1 |
||||
fi |
||||
|
||||
local ret=0 |
||||
"$ABIDIFF" --non-reachable-types \ |
||||
--suppressions "$SUPPRESSIONS" \ |
||||
"${past_header}.bin" "${base_header}.bin" > "$log" || ret="$?" |
||||
if [ "$ret" -eq 0 ]; then |
||||
if [ "$VERBOSE" = "true" ]; then |
||||
printf "No ABI differences detected in %s from %s -> %s\n" \ |
||||
"$file" "$past_ref" "$base_ref" |
||||
fi |
||||
else |
||||
# Bits in abidiff's return code can be used to determine the type of error |
||||
if [ $((ret & 0x2)) -gt 0 ]; then |
||||
eprintf "error - abidiff did not run properly\n" |
||||
exit 1 |
||||
fi |
||||
|
||||
if [ "$IGNORE_AMBIGUOUS_CHANGES" = "true" ] && [ "$ret" -eq 4 ]; then |
||||
return 0 |
||||
fi |
||||
|
||||
# If the only changes were additions (not modifications to existing APIs), then |
||||
# there's no problem. Ignore these diffs. |
||||
if grep "Unreachable types summary" "$log" | grep -q "0 removed" && |
||||
grep "Unreachable types summary" "$log" | grep -q "0 changed"; then |
||||
return 0 |
||||
fi |
||||
|
||||
{ |
||||
warn_str=$(printf "==== ABI differences detected in %s from %s -> %s ====" \ |
||||
"$file" "$past_ref" "$base_ref") |
||||
printf "%s\n" "$warn_str" |
||||
sed -e '/summary:/d' -e '/changed type/d' -e '/^$/d' -e 's/^/ /g' "$log" |
||||
printf -- "=%.0s" $(seq 0 ${#warn_str}) |
||||
if cmp "$past_header" "$base_header" > /dev/null 2>&1; then |
||||
printf "\n%s did not change between %s and %s...\n" "$file" "$past_ref" "${base_ref:-dirty tree}" |
||||
printf "It's possible a change to one of the headers it includes caused this error:\n" |
||||
grep '^#include' "$base_header" |
||||
printf "\n" |
||||
fi |
||||
} > "$error_log" |
||||
|
||||
return 1 |
||||
fi |
||||
} |
||||
|
||||
# Check that a minimum software version number is satisfied |
||||
min_version_is_satisfied() { |
||||
local -r min_version="$1" |
||||
local -r version_installed="$2" |
||||
|
||||
printf "%s\n%s\n" "$min_version" "$version_installed" \ |
||||
| sort -Vc > /dev/null 2>&1 |
||||
} |
||||
|
||||
# Make sure we have the tools we need and the arguments make sense |
||||
check_deps() { |
||||
ABIDIFF="${ABIDIFF:-abidiff}" |
||||
CC="${CC:-gcc}" |
||||
ARCH="${ARCH:-$(uname -m)}" |
||||
if [ "$ARCH" = "x86_64" ]; then |
||||
ARCH="x86" |
||||
fi |
||||
|
||||
local -r abidiff_min_version="2.4" |
||||
local -r libdw_min_version_if_clang="0.171" |
||||
|
||||
if ! command -v "$ABIDIFF" > /dev/null 2>&1; then |
||||
eprintf "error - abidiff not found!\n" |
||||
eprintf "Please install abigail-tools version %s or greater\n" "$abidiff_min_version" |
||||
eprintf "See: https://sourceware.org/libabigail/manual/libabigail-overview.html\n" |
||||
return 1 |
||||
fi |
||||
|
||||
local -r abidiff_version="$("$ABIDIFF" --version | cut -d ' ' -f 2)" |
||||
if ! min_version_is_satisfied "$abidiff_min_version" "$abidiff_version"; then |
||||
eprintf "error - abidiff version too old: %s\n" "$abidiff_version" |
||||
eprintf "Please install abigail-tools version %s or greater\n" "$abidiff_min_version" |
||||
eprintf "See: https://sourceware.org/libabigail/manual/libabigail-overview.html\n" |
||||
return 1 |
||||
fi |
||||
|
||||
if ! command -v "$CC" > /dev/null 2>&1; then |
||||
eprintf 'error - %s not found\n' "$CC" |
||||
return 1 |
||||
fi |
||||
|
||||
if "$CC" --version | grep -q clang; then |
||||
local -r libdw_version="$(ldconfig -v 2>/dev/null | grep -v SKIPPED | grep -m 1 -o 'libdw-[0-9]\+.[0-9]\+' | cut -c 7-)" |
||||
if ! min_version_is_satisfied "$libdw_min_version_if_clang" "$libdw_version"; then |
||||
eprintf "error - libdw version too old for use with clang: %s\n" "$libdw_version" |
||||
eprintf "Please install libdw from elfutils version %s or greater\n" "$libdw_min_version_if_clang" |
||||
eprintf "See: https://sourceware.org/elfutils/\n" |
||||
return 1 |
||||
fi |
||||
fi |
||||
|
||||
if [ ! -d "arch/${ARCH}" ]; then |
||||
eprintf 'error - ARCH "%s" is not a subdirectory under arch/\n' "$ARCH" |
||||
eprintf "Please set ARCH to one of:\n%s\n" "$(find arch -maxdepth 1 -mindepth 1 -type d -printf '%f ' | fmt)" |
||||
return 1 |
||||
fi |
||||
|
||||
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then |
||||
eprintf "error - this script requires the kernel tree to be initialized with Git\n" |
||||
return 1 |
||||
fi |
||||
|
||||
if ! git rev-parse --verify "$past_ref" > /dev/null 2>&1; then |
||||
printf 'error - invalid git reference "%s"\n' "$past_ref" |
||||
return 1 |
||||
fi |
||||
|
||||
if [ -n "$base_ref" ]; then |
||||
if ! git merge-base --is-ancestor "$past_ref" "$base_ref" > /dev/null 2>&1; then |
||||
printf 'error - "%s" is not an ancestor of base ref "%s"\n' "$past_ref" "$base_ref" |
||||
return 1 |
||||
fi |
||||
if [ "$(git rev-parse "$base_ref")" = "$(git rev-parse "$past_ref")" ]; then |
||||
printf 'error - "%s" and "%s" are the same reference\n' "$past_ref" "$base_ref" |
||||
return 1 |
||||
fi |
||||
fi |
||||
} |
||||
|
||||
run() { |
||||
local base_ref="$1" |
||||
local past_ref="$2" |
||||
local abi_error_log="$3" |
||||
shift 3 |
||||
|
||||
if [ -z "$KERNEL_SRC" ]; then |
||||
KERNEL_SRC="$(realpath "$(dirname "$0")"/..)" |
||||
fi |
||||
|
||||
cd "$KERNEL_SRC" |
||||
|
||||
if [ -z "$base_ref" ] && ! tree_is_dirty; then |
||||
base_ref=HEAD |
||||
fi |
||||
|
||||
if [ -z "$past_ref" ]; then |
||||
if [ -n "$base_ref" ]; then |
||||
past_ref="${base_ref}^1" |
||||
else |
||||
past_ref=HEAD |
||||
fi |
||||
fi |
||||
|
||||
if ! check_deps; then |
||||
exit "$FAIL_PREREQ" |
||||
fi |
||||
|
||||
TMP_DIR=$(mktemp -d) |
||||
readonly TMP_DIR |
||||
trap 'rm -rf "$TMP_DIR"' EXIT |
||||
|
||||
readonly INCOMPAT_LIST="${TMP_DIR}/incompat_list.txt" |
||||
touch "$INCOMPAT_LIST" |
||||
|
||||
readonly SUPPRESSIONS="${TMP_DIR}/suppressions.txt" |
||||
gen_suppressions > "$SUPPRESSIONS" |
||||
|
||||
# Run make install_headers for both refs |
||||
install_headers "$base_ref" "$past_ref" |
||||
|
||||
# Check for any differences in the installed header trees |
||||
if diff -r -q "$(get_header_tree "$base_ref")" "$(get_header_tree "$past_ref")" > /dev/null 2>&1; then |
||||
printf "No changes to UAPI headers were applied between %s and %s\n" "$past_ref" "${base_ref:-dirty tree}" |
||||
exit "$SUCCESS" |
||||
fi |
||||
|
||||
if ! check_uapi_files "$base_ref" "$past_ref" "$abi_error_log"; then |
||||
exit "$FAIL_ABI" |
||||
fi |
||||
} |
||||
|
||||
main() { |
||||
MAX_THREADS=$(nproc) |
||||
VERBOSE="false" |
||||
IGNORE_AMBIGUOUS_CHANGES="false" |
||||
quiet="false" |
||||
local base_ref="" |
||||
while getopts "hb:p:j:l:iqv" opt; do |
||||
case $opt in |
||||
h) |
||||
print_usage |
||||
exit "$SUCCESS" |
||||
;; |
||||
b) |
||||
base_ref="$OPTARG" |
||||
;; |
||||
p) |
||||
past_ref="$OPTARG" |
||||
;; |
||||
j) |
||||
MAX_THREADS="$OPTARG" |
||||
;; |
||||
l) |
||||
abi_error_log="$OPTARG" |
||||
;; |
||||
i) |
||||
IGNORE_AMBIGUOUS_CHANGES="true" |
||||
;; |
||||
q) |
||||
quiet="true" |
||||
VERBOSE="false" |
||||
;; |
||||
v) |
||||
VERBOSE="true" |
||||
quiet="false" |
||||
;; |
||||
*) |
||||
exit "$FAIL_PREREQ" |
||||
esac |
||||
done |
||||
|
||||
if [ "$quiet" = "true" ]; then |
||||
exec > /dev/null 2>&1 |
||||
fi |
||||
|
||||
run "$base_ref" "$past_ref" "$abi_error_log" "$@" |
||||
} |
||||
|
||||
main "$@" |
@ -0,0 +1,42 @@
|
||||
# SPDX-License-Identifier: GPL-2.0 |
||||
|
||||
# order file for git, to produce patches which are easier to review |
||||
# by diffing the important stuff like header changes first. |
||||
# |
||||
# one-off usage: |
||||
# git diff -O scripts/git.orderFile ... |
||||
# |
||||
# add to git config: |
||||
# git config diff.orderFile scripts/git.orderFile |
||||
# |
||||
|
||||
MAINTAINERS |
||||
|
||||
# Documentation |
||||
Documentation/* |
||||
*.rst |
||||
|
||||
# git-specific |
||||
.gitignore |
||||
scripts/git.orderFile |
||||
|
||||
# build system |
||||
Kconfig* |
||||
*/Kconfig* |
||||
Kbuild* |
||||
*/Kbuild* |
||||
Makefile* |
||||
*/Makefile* |
||||
*.mak |
||||
*.mk |
||||
scripts/* |
||||
|
||||
# semantic patches |
||||
*.cocci |
||||
|
||||
# headers |
||||
*types.h |
||||
*.h |
||||
|
||||
# code |
||||
*.c |
@ -0,0 +1,53 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
#include "expr.h" |
||||
#include "list.h" |
||||
#include "mnconf-common.h" |
||||
|
||||
int jump_key_char; |
||||
|
||||
int next_jump_key(int key) |
||||
{ |
||||
if (key < '1' || key > '9') |
||||
return '1'; |
||||
|
||||
key++; |
||||
|
||||
if (key > '9') |
||||
key = '1'; |
||||
|
||||
return key; |
||||
} |
||||
|
||||
int handle_search_keys(int key, size_t start, size_t end, void *_data) |
||||
{ |
||||
struct search_data *data = _data; |
||||
struct jump_key *pos; |
||||
int index = 0; |
||||
|
||||
if (key < '1' || key > '9') |
||||
return 0; |
||||
|
||||
list_for_each_entry(pos, data->head, entries) { |
||||
index = next_jump_key(index); |
||||
|
||||
if (pos->offset < start) |
||||
continue; |
||||
|
||||
if (pos->offset >= end) |
||||
break; |
||||
|
||||
if (key == index) { |
||||
data->target = pos->target; |
||||
return 1; |
||||
} |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int get_jump_key_char(void) |
||||
{ |
||||
jump_key_char = next_jump_key(jump_key_char); |
||||
|
||||
return jump_key_char; |
||||
} |
@ -0,0 +1,18 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */ |
||||
#ifndef MNCONF_COMMON_H |
||||
#define MNCONF_COMMON_H |
||||
|
||||
#include <stddef.h> |
||||
|
||||
struct search_data { |
||||
struct list_head *head; |
||||
struct menu *target; |
||||
}; |
||||
|
||||
extern int jump_key_char; |
||||
|
||||
int next_jump_key(int key); |
||||
int handle_search_keys(int key, size_t start, size_t end, void *_data); |
||||
int get_jump_key_char(void); |
||||
|
||||
#endif /* MNCONF_COMMON_H */ |
@ -1,14 +0,0 @@
|
||||
#!/bin/sh |
||||
# SPDX-License-Identifier: GPL-2.0-only |
||||
|
||||
# Set up CROSS_COMPILE if not defined yet |
||||
if [ "${CROSS_COMPILE+set}" != "set" -a "${DEB_HOST_ARCH}" != "${DEB_BUILD_ARCH}" ]; then |
||||
echo CROSS_COMPILE=${DEB_HOST_GNU_TYPE}- |
||||
fi |
||||
|
||||
version=$(dpkg-parsechangelog -S Version) |
||||
debian_revision="${version##*-}" |
||||
|
||||
if [ "${version}" != "${debian_revision}" ]; then |
||||
echo KBUILD_BUILD_VERSION=${debian_revision} |
||||
fi |
@ -0,0 +1,16 @@
|
||||
This is a packaged upstream version of the Linux kernel. |
||||
|
||||
The sources may be found at most Linux archive sites, including: |
||||
https://www.kernel.org/pub/linux/kernel |
||||
|
||||
Copyright: 1991 - 2023 Linus Torvalds and others. |
||||
|
||||
The git repository for mainline kernel development is at: |
||||
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git |
||||
|
||||
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; version 2 dated June, 1991. |
||||
|
||||
On Debian GNU/Linux systems, the complete text of the GNU General Public |
||||
License version 2 can be found in `/usr/share/common-licenses/GPL-2'. |
@ -1,33 +1,46 @@
|
||||
#!/usr/bin/make -f |
||||
# SPDX-License-Identifier: GPL-2.0-only |
||||
|
||||
include debian/rules.vars |
||||
# in case debian/rules is executed directly |
||||
export DEB_RULES_REQUIRES_ROOT := no |
||||
|
||||
srctree ?= . |
||||
include debian/rules.vars |
||||
|
||||
ifneq (,$(filter-out parallel=1,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))) |
||||
NUMJOBS = $(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) |
||||
MAKEFLAGS += -j$(NUMJOBS) |
||||
endif |
||||
|
||||
revision = $(lastword $(subst -, ,$(shell dpkg-parsechangelog -S Version))) |
||||
CROSS_COMPILE ?= $(filter-out $(DEB_BUILD_GNU_TYPE)-, $(DEB_HOST_GNU_TYPE)-) |
||||
make-opts = ARCH=$(ARCH) KERNELRELEASE=$(KERNELRELEASE) KBUILD_BUILD_VERSION=$(revision) $(addprefix CROSS_COMPILE=,$(CROSS_COMPILE)) |
||||
|
||||
.PHONY: binary binary-indep binary-arch |
||||
binary: binary-arch binary-indep |
||||
binary-indep: build-indep |
||||
binary-arch: build-arch |
||||
$(MAKE) -f $(srctree)/Makefile ARCH=$(ARCH) \ |
||||
KERNELRELEASE=$(KERNELRELEASE) \ |
||||
run-command KBUILD_RUN_COMMAND=+$(srctree)/scripts/package/builddeb |
||||
$(MAKE) $(make-opts) \ |
||||
run-command KBUILD_RUN_COMMAND='+$$(srctree)/scripts/package/builddeb' |
||||
|
||||
.PHONY: build build-indep build-arch |
||||
build: build-arch build-indep |
||||
build-indep: |
||||
build-arch: |
||||
$(MAKE) -f $(srctree)/Makefile ARCH=$(ARCH) \ |
||||
KERNELRELEASE=$(KERNELRELEASE) \ |
||||
$(shell $(srctree)/scripts/package/deb-build-option) \ |
||||
olddefconfig all |
||||
$(MAKE) $(make-opts) olddefconfig |
||||
$(MAKE) $(make-opts) $(if $(filter um,$(ARCH)),,headers) all |
||||
|
||||
.PHONY: clean |
||||
clean: |
||||
rm -rf debian/files debian/linux-* |
||||
$(MAKE) -f $(srctree)/Makefile ARCH=$(ARCH) clean |
||||
rm -rf debian/files debian/linux-* debian/deb-env.vars* |
||||
$(MAKE) ARCH=$(ARCH) clean |
||||
|
||||
# If DEB_HOST_ARCH is empty, it is likely that debian/rules was executed |
||||
# directly. Run 'dpkg-architecture --print-set --print-format=make' to |
||||
# generate a makefile construct that exports all DEB_* variables. |
||||
ifndef DEB_HOST_ARCH |
||||
include debian/deb-env.vars |
||||
|
||||
debian/deb-env.vars: |
||||
dpkg-architecture -a$$(cat debian/arch) --print-set --print-format=make > $@.tmp |
||||
mv $@.tmp $@ |
||||
endif |
||||
|
Loading…
Reference in new issue