Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

fixdep: check return value of printf() and putchar()

When there is not enough space on your storage device, the build will
fail with 'No space left on device' error message.

The reason is obvious from the message, so you will free up some disk
space, then you will resume the build.

However, sometimes you may still see a mysterious error message:

unterminated call to function 'wildcard': missing ')'.

If you run out of the disk space, fixdep may end up with generating
incomplete .*.cmd files.

For example, if the disk-full error occurs while fixdep is running
print_dep(), the .*.cmd might be truncated like this:

$(wildcard include/config/

When you run 'make' next time, this broken .*.cmd will be included,
then Make will terminate parsing since it is a wrong syntax.

Once this happens, you need to run 'make clean' or delete the broken
.*.cmd file manually.

Even if you do not see any error message, the .*.cmd files after any
error could be potentially incomplete, and unreliable. You may miss
the re-compilation due to missing header dependency.

If printf() cannot output the string for disk shortage or whatever
reason, it returns a negative value, but currently fixdep does not
check it at all. Consequently, fixdep *successfully* generates a
broken .*.cmd file. Make never notices that since fixdep exits with 0,
which means success.

Given the intended usage of fixdep, it must respect the return value
of not only malloc(), but also printf() and putchar().

This seems a long-standing issue since the introduction of fixdep.

In old days, Kbuild tried to provide an extra safety by letting fixdep
output to a temporary file and renaming it after everything is done:

scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).tmp;\
rm -f $(depfile); \
mv -f $(dot-target).tmp $(dot-target).cmd)

It was no help to avoid the current issue; fixdep successfully created
a truncated tmp file, which would be renamed to a .*.cmd file.

This problem should be fixed by propagating the error status to the
build system because:

[1] Since commit 9c2af1c7377a ("kbuild: add .DELETE_ON_ERROR special
target"), Make will delete the target automatically on any failure
in the recipe.

[2] Since commit 392885ee82d3 ("kbuild: let fixdep directly write to
.*.cmd files"), .*.cmd file is included only when the corresponding
target already exists.

Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>

+41 -10
+41 -10
scripts/basic/fixdep.c
··· 99 99 #include <unistd.h> 100 100 #include <fcntl.h> 101 101 #include <string.h> 102 + #include <stdarg.h> 102 103 #include <stdlib.h> 103 104 #include <stdio.h> 104 105 #include <ctype.h> ··· 111 110 } 112 111 113 112 /* 113 + * In the intended usage of this program, the stdout is redirected to .*.cmd 114 + * files. The return value of printf() and putchar() must be checked to catch 115 + * any error, e.g. "No space left on device". 116 + */ 117 + static void xprintf(const char *format, ...) 118 + { 119 + va_list ap; 120 + int ret; 121 + 122 + va_start(ap, format); 123 + ret = vprintf(format, ap); 124 + if (ret < 0) { 125 + perror("fixdep"); 126 + exit(1); 127 + } 128 + va_end(ap); 129 + } 130 + 131 + static void xputchar(int c) 132 + { 133 + int ret; 134 + 135 + ret = putchar(c); 136 + if (ret == EOF) { 137 + perror("fixdep"); 138 + exit(1); 139 + } 140 + } 141 + 142 + /* 114 143 * Print out a dependency path from a symbol name 115 144 */ 116 145 static void print_dep(const char *m, int slen, const char *dir) 117 146 { 118 147 int c, prev_c = '/', i; 119 148 120 - printf(" $(wildcard %s/", dir); 149 + xprintf(" $(wildcard %s/", dir); 121 150 for (i = 0; i < slen; i++) { 122 151 c = m[i]; 123 152 if (c == '_') ··· 155 124 else 156 125 c = tolower(c); 157 126 if (c != '/' || prev_c != '/') 158 - putchar(c); 127 + xputchar(c); 159 128 prev_c = c; 160 129 } 161 - printf(".h) \\\n"); 130 + xprintf(".h) \\\n"); 162 131 } 163 132 164 133 struct item { ··· 355 324 */ 356 325 if (!saw_any_target) { 357 326 saw_any_target = 1; 358 - printf("source_%s := %s\n\n", 359 - target, m); 360 - printf("deps_%s := \\\n", target); 327 + xprintf("source_%s := %s\n\n", 328 + target, m); 329 + xprintf("deps_%s := \\\n", target); 361 330 } 362 331 is_first_dep = 0; 363 332 } else { 364 - printf(" %s \\\n", m); 333 + xprintf(" %s \\\n", m); 365 334 } 366 335 367 336 buf = read_file(m); ··· 384 353 exit(1); 385 354 } 386 355 387 - printf("\n%s: $(deps_%s)\n\n", target, target); 388 - printf("$(deps_%s):\n", target); 356 + xprintf("\n%s: $(deps_%s)\n\n", target, target); 357 + xprintf("$(deps_%s):\n", target); 389 358 } 390 359 391 360 int main(int argc, char *argv[]) ··· 400 369 target = argv[2]; 401 370 cmdline = argv[3]; 402 371 403 - printf("cmd_%s := %s\n\n", target, cmdline); 372 + xprintf("cmd_%s := %s\n\n", target, cmdline); 404 373 405 374 buf = read_file(depfile); 406 375 parse_dep_file(buf, target);