1

In my C code, I need to check my kernel version and act according to it. In the makefile I have the following:

KERNEL_MAJOR :=$(word 1, $(subst ., ,$(KERNEL_HEADERS)))
KERNEL_MINOR :=$(word 2, $(subst ., ,$(KERNEL_HEADERS)))
KERNEL_MICRO :=$(word 1, $(subst -, ,$(word 3, $(subst ., ,$(KERNEL_HEADERS)))))
KERNEL_PATCH_LEVEL :=$(word 1, $(subst ., ,$(word 2, $(subst -, ,$(KERNEL_HEADERS)))))
KARGS := KCPPFLAGS="-DKERNEL_MAJOR=$(KERNEL_MAJOR) -DKERNEL_MINOR=$(KERNEL_MINOR) -DKERNEL_MICRO=$(KERNEL_MICRO) -DKERNEL_PATCH_LEVEL=$(KERNEL_PATCH_LEVEL)"

Samples:
3.0.101-0.47.71-default looks like:

KCPPFLAGS="-DKERNEL_MAJOR=3 -DKERNEL_MINOR=0 -DKERNEL_MICRO=101 -DKERNEL_PATCH_LEVEL=0"

4.1.21.x86_64.1 (notice KERNEL_PATCH_LEVEL):

KCPPFLAGS="-DKERNEL_MAJOR=4 -DKERNEL_MINOR=1 -DKERNEL_MICRO=21 -DKERNEL_PATCH_LEVEL="

I have macro in my code to check if kernel is 3.0.101.0 or 3.0.76.0:

#if defined(KERNEL_MAJOR) && defined(KERNEL_MINOR) && defined(KERNEL_MICRO) && defined(KERNEL_PATCH_LEVEL) && \
    (KERNEL_VERSION(KERNEL_MAJOR,KERNEL_MINOR,KERNEL_MICRO) == KERNEL_VERSION(3,0,101) || KERNEL_VERSION(KERNEL_MAJOR,KERNEL_MINOR,KERNEL_MICRO) == KERNEL_VERSION(3,0,76)) \
    && (KERNEL_PATCH_LEVEL == 0)

There are 3 && boolean expressions, if expression #1 is ok, then i want to continue to expression #2, if expression #2 continue, i will continue to expression #3. When i try to compile (make..) on kernel version 4.1.21.x86_64.1 i receive:

error: operator '==' has no left operand

This is because -DKERNEL_PATCH_LEVEL=" - see the output.
I would expect the make to prevent me from getting to that && condition since expression #2 has failed (3.0.101 or 3.0.76)

ilansch
  • 4,784
  • 7
  • 47
  • 96
  • What does the `KERNEL_VERSION` macro look like? – Maxpm Mar 29 '17 at 11:40
  • 1
    Rather than passing `KERNEL_MAJOR`, `KERNEL_MINOR` and `KERNEL_MICRO` macros on the command line, the usual method of checking the kernel version in the preprocessor is to compare `LINUX_VERSION_CODE` with `KERNEL_VERSION(A,B,C)` in an `#if` directive. This doesn't work for the 4-level "stable" kernel version codes that used to be used in the 2.6 kernel days. – Ian Abbott Mar 29 '17 at 12:46
  • After macro expansion, your `#if` expression is no longer syntactically correct. You can't use `&&` and `||` operators to short-circuit bad syntax. – Ian Abbott Mar 29 '17 at 13:07
  • Hi, KERNEL_VERSION is not good for me, since kernel version 3.0.101 btrfs vfs driver is different between sub versions, and for each version i need to deal differently. – ilansch Mar 29 '17 at 13:43
  • Yes, Linux vendors such as Red Hat and Ubuntu sometimes do crap like backporting kernel ABI changes willy-nilly. – Ian Abbott Mar 29 '17 at 15:01
  • ... Or Suse in this case. – Ian Abbott Mar 29 '17 at 15:17
  • ... Though I should point out that I don't know whether SUSE have changed the _public_ interfaces to btrfs. I understand from your earlier questions that you're mucking around with the _private_ interfaces. – Ian Abbott Mar 29 '17 at 15:55
  • Yup i use the private api because performance and since i compile the kernel module on specific machine and version i can "enjoy" the benefit of calling thd private api instead of calling inode operation->getattr(...) – ilansch Mar 29 '17 at 18:36

3 Answers3

1

If you read: https://gcc.gnu.org/onlinedocs/gcc-3.0.2/cpp_4.html, you will find:

The `#if' directive allows you to test the value of an arithmetic expression, rather than the mere existence of one macro. Its syntax is

#if expression ...

expression is a C expression of integer type, subject to stringent restrictions. It may contain:

...

  • Macros. All macros in the expression are expanded before actual computation of the expression's value begins.

  • Identifiers that are not macros, which are all considered to be the number zero. ...

So if you had:

#if defined(FOO) && FOO == 1

And then didn't define foo, it would resolve the expression to:

0 && 0 == 1

which would then resolve to #if 0, which is valid. However, if you defined FOO to be blank, then it would resolve to:

1 && == 1

The precompiler would then try to parse the expression, and get a syntax error, and fail. Despite several comments that seem to be out on the web, the precompiler does not short-circuit the parsing of the expression, just the evaluation of the expression.

If you wanted to get around this, you can use some macro trickery as follows:

#define COMBINE1(W,Y,Z) W##Y##Z
#define COMBINE(W,Y,Z) COMBINE1(W,Y,Z)
#define ISEMPTY(val)  COMBINE(val,4,val) == 4

#if defined FOO
#if ISEMPTY(FOO)
#pragma message "FOO DEFINED AS EMPTY"
#else
#pragma message "FOO DEFINED (NOT EMPTY)"
#endif
#else
#pragma message "FOO NOT DEFINED"
#endif

which gives:

~/sandbox/tst6> gcc tst2.c
tst2.c:12: note: #pragma message: FOO NOT DEFINED
~/sandbox/tst6> gcc tst2.c -DFOO=
tst2.c:7: note: #pragma message: FOO DEFINED AS EMPTY
~/sandbox/tst6> gcc tst2.c -DFOO=1
tst2.c:9: note: #pragma message: FOO DEFINED (NOT EMPTY)
blackghost
  • 1,730
  • 11
  • 24
1

You are right in believing that the C preprocessor, like the C compiler, performs short-circuit evaluation of &&- and ||-expressions.

But you are wrong in believing the preprocessor, unlike the compiler, can perform "short-circuit" evaluation of nonsensical token sequences, provided that the nonsense is in one of the contexts:

TRUE || [nonsense]

or

FALSE && [nonsense]

Just like the compiler, any sequence the preprocessor can evaluate as an expression, short-circuiting or not, must be a (well-formed) expression, which

FALSE && ( == 0)

is not.

Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
0

All the #if conditions pass because KERNEL_PATCH_LEVEL is defined. It's just defined as an empty string. -DKERNEL_PATCH_LEVEL= is equivalent to

#define KERNEL_PATCH_LEVEL

Which, of course, will pass an #if (defined) test.

It's somewhat difficult to test if a macro is empty. It might be easier to modify your makefile to define KERNEL_PATCH_LEVEL as some special value (like 0 or -1) if it isn't present in the version string.

Maxpm
  • 24,113
  • 33
  • 111
  • 170
  • Hi, thanks for response. My question is not regarding to "defined but empty" issue - I am aware to it, what I dont undestand is why macro with AND && condition as: expression1 -> expression2 ->expression3, why if expression2 is false, the compiler will fail on expression 3 in compile time. it should not get there in the first place and go to the #else .. – ilansch Mar 29 '17 at 11:32
  • @ilansch, first of all you are doing some weird things (why in the first place you need such as above?). To your comment, on what ground you decide that expression2 is false? – 0andriy Mar 29 '17 at 11:43