• Masahiro Yamada's avatar
    kbuild: remove the target in signal traps when interrupted · a7f3257d
    Masahiro Yamada authored
    When receiving some signal, GNU Make automatically deletes the target if
    it has already been changed by the interrupted recipe.
    
    If the target is possibly incomplete due to interruption, it must be
    deleted so that it will be remade from scratch on the next run of make.
    Otherwise, the target would remain corrupted permanently because its
    timestamp had already been updated.
    
    Thanks to this behavior of Make, you can stop the build any time by
    pressing Ctrl-C, and just run 'make' to resume it.
    
    Kbuild also relies on this feature, but it is equivalently important
    for any build systems that make decisions based on timestamps (if you
    want to support Ctrl-C reliably).
    
    However, this does not always work as claimed; Make immediately dies
    with Ctrl-C if its stderr goes into a pipe.
    
      [Test Makefile]
    
        foo:
                echo hello > $@
                sleep 3
                echo world >> $@
    
      [Test Result]
    
        $ make                         # hit Ctrl-C
        echo hello > foo
        sleep 3
        ^Cmake: *** Deleting file 'foo'
        make: *** [Makefile:3: foo] Interrupt
    
        $ make 2>&1 | cat              # hit Ctrl-C
        echo hello > foo
        sleep 3
        ^C$                            # 'foo' is often left-over
    
    The reason is because SIGINT is sent to the entire process group.
    In this example, SIGINT kills 'cat', and 'make' writes the message to
    the closed pipe, then dies with SIGPIPE before cleaning the target.
    
    A typical bad scenario (as reported by [1], [2]) is to save build log
    by using the 'tee' command:
    
        $ make 2>&1 | tee log
    
    This can be problematic for any build systems based on Make, so I hope
    it will be fixed in GNU Make. The maintainer of GNU Make stated this is
    a long-standing issue and difficult to fix [3]. It has not been fixed
    yet as of writing.
    
    So, we cannot rely on Make cleaning the target. We can do it by
    ourselves, in signal traps.
    
    As far as I understand, Make takes care of SIGHUP, SIGINT, SIGQUIT, and
    SITERM for the target removal. I added the traps for them, and also for
    SIGPIPE just in case cmd_* rule prints something to stdout or stderr
    (but I did not observe an actual case where SIGPIPE was triggered).
    
    [Note 1]
    
    The trap handler might be worth explaining.
    
        rm -f $@; trap - $(sig); kill -s $(sig) $$
    
    This lets the shell kill itself by the signal it caught, so the parent
    process can tell the child has exited on the signal. Generally, this is
    a proper manner for handling signals, in case the calling program (like
    Bash) may monitor WIFSIGNALED() and WTERMSIG() for WCE although this may
    not be a big deal here because GNU Make handles SIGHUP, SIGINT, SIGQUIT
    in WUE and SIGTERM in IUE.
    
      IUE - Immediate Unconditional Exit
      WUE - Wait and Unconditional Exit
      WCE - Wait and Cooperative Exit
    
    For details, see "Proper handling of SIGINT/SIGQUIT" [4].
    
    [Note 2]
    
    Reverting 392885ee ("kbuild: let fixdep directly write to .*.cmd
    files") would directly address [1], but it only saves if_changed_dep.
    As reported in [2], all commands that use redirection can potentially
    leave an empty (i.e. broken) target.
    
    [Note 3]
    
    Another (even safer) approach might be to always write to a temporary
    file, and rename it to $@ at the end of the recipe.
    
       <command>  > $(tmp-target)
       mv $(tmp-target) $@
    
    It would require a lot of Makefile changes, and result in ugly code,
    so I did not take it.
    
    [Note 4]
    
    A little more thoughts about a pattern rule with multiple targets (or
    a grouped target).
    
        %.x %.y: %.z
                <recipe>
    
    When interrupted, GNU Make deletes both %.x and %.y, while this solution
    only deletes $@. Probably, this is not a big deal. The next run of make
    will execute the rule again to create $@ along with the other files.
    
    [1]: https://lore.kernel.org/all/YLeot94yAaM4xbMY@gmail.com/
    [2]: https://lore.kernel.org/all/20220510221333.2770571-1-robh@kernel.org/
    [3]: https://lists.gnu.org/archive/html/help-make/2021-06/msg00001.html
    [4]: https://www.cons.org/cracauer/sigint.html
    
    Fixes: 392885ee ("kbuild: let fixdep directly write to .*.cmd files")
    Reported-by: default avatarIngo Molnar <mingo@kernel.org>
    Reported-by: default avatarRob Herring <robh@kernel.org>
    Signed-off-by: default avatarMasahiro Yamada <masahiroy@kernel.org>
    Tested-by: default avatarIngo Molnar <mingo@kernel.org>
    Reviewed-by: default avatarNicolas Schier <nicolas@fjasle.eu>
    a7f3257d
Kbuild.include 8.29 KB