Re: [PATCH v2] fixdep: check return value of printf() and putchar()

From: Masahiro Yamada
Date: Sat Jul 06 2019 - 10:41:03 EST


On Tue, Jun 25, 2019 at 3:54 PM Masahiro Yamada
<yamada.masahiro@xxxxxxxxxxxxx> wrote:
>
> 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 shortage 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 GNU 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 did not avoid the current issue; fixdep created a truncated tmp file
> reporting success, so the broken tmp would be renamed to a .*.cmd file.
>
> By propagating the error code to the build system, this problem should
> be solved 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@xxxxxxxxxxxxx>
> ---

Applied to linux-kbuild.


> Changes in v2:
> - Add prror()
>
> scripts/basic/fixdep.c | 51 +++++++++++++++++++++++++++++++++---------
> 1 file changed, 41 insertions(+), 10 deletions(-)
>
> diff --git a/scripts/basic/fixdep.c b/scripts/basic/fixdep.c
> index facbd603adf6..4ac973f2dc8c 100644
> --- a/scripts/basic/fixdep.c
> +++ b/scripts/basic/fixdep.c
> @@ -99,6 +99,7 @@
> #include <unistd.h>
> #include <fcntl.h>
> #include <string.h>
> +#include <stdarg.h>
> #include <stdlib.h>
> #include <stdio.h>
> #include <ctype.h>
> @@ -109,6 +110,36 @@ static void usage(void)
> exit(1);
> }
>
> +/*
> + * In the intended usage of this program, the stdout is redirected to .*.cmd
> + * The return value of printf() and putchar() must be checked to catch any
> + * error like "No space left on device".
> + */
> +static void xprintf(const char *format, ...)
> +{
> + va_list ap;
> + int ret;
> +
> + va_start(ap, format);
> + ret = vprintf(format, ap);
> + if (ret < 0) {
> + perror("fixdep");
> + exit(1);
> + }
> + va_end(ap);
> +}
> +
> +static void xputchar(int c)
> +{
> + int ret;
> +
> + ret = putchar(c);
> + if (ret == EOF) {
> + perror("fixdep");
> + exit(1);
> + }
> +}
> +
> /*
> * Print out a dependency path from a symbol name
> */
> @@ -116,7 +147,7 @@ static void print_dep(const char *m, int slen, const char *dir)
> {
> int c, prev_c = '/', i;
>
> - printf(" $(wildcard %s/", dir);
> + xprintf(" $(wildcard %s/", dir);
> for (i = 0; i < slen; i++) {
> c = m[i];
> if (c == '_')
> @@ -124,10 +155,10 @@ static void print_dep(const char *m, int slen, const char *dir)
> else
> c = tolower(c);
> if (c != '/' || prev_c != '/')
> - putchar(c);
> + xputchar(c);
> prev_c = c;
> }
> - printf(".h) \\\n");
> + xprintf(".h) \\\n");
> }
>
> struct item {
> @@ -324,13 +355,13 @@ static void parse_dep_file(char *m, const char *target)
> */
> if (!saw_any_target) {
> saw_any_target = 1;
> - printf("source_%s := %s\n\n",
> - target, m);
> - printf("deps_%s := \\\n", target);
> + xprintf("source_%s := %s\n\n",
> + target, m);
> + xprintf("deps_%s := \\\n", target);
> }
> is_first_dep = 0;
> } else {
> - printf(" %s \\\n", m);
> + xprintf(" %s \\\n", m);
> }
>
> buf = read_file(m);
> @@ -353,8 +384,8 @@ static void parse_dep_file(char *m, const char *target)
> exit(1);
> }
>
> - printf("\n%s: $(deps_%s)\n\n", target, target);
> - printf("$(deps_%s):\n", target);
> + xprintf("\n%s: $(deps_%s)\n\n", target, target);
> + xprintf("$(deps_%s):\n", target);
> }
>
> int main(int argc, char *argv[])
> @@ -369,7 +400,7 @@ int main(int argc, char *argv[])
> target = argv[2];
> cmdline = argv[3];
>
> - printf("cmd_%s := %s\n\n", target, cmdline);
> + xprintf("cmd_%s := %s\n\n", target, cmdline);
>
> buf = read_file(depfile);
> parse_dep_file(buf, target);
> --
> 2.17.1
>


--
Best Regards
Masahiro Yamada