Re: [PATCH] scripts: handle BrokenPipeError for python scripts

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, Jan 11, 2023 at 6:30 PM Masahiro Yamada <masahiroy@xxxxxxxxxx> wrote:
>
> In the follow-up of commit fb3041d61f68 ("kbuild: fix SIGPIPE error
> message for AR=gcc-ar and AR=llvm-ar"), Kees Cook pointed out that
> tools should _not_ catch their own SIGPIPEs [1] [2].
>
> Based on his feedback, LLVM was fixed [3].
>
> However, Python's default behavior is to show noisy bracktrace when
> SIGPIPE is sent. So, scripts written in Python are basically in the
> same situation as the buggy llvm tools.
>
> Example:
>
>   $ make -s allnoconfig
>   $ make -s allmodconfig
>   $ scripts/diffconfig .config.old .config | head -n1
>   -ALIX n
>   Traceback (most recent call last):
>     File "/home/masahiro/linux/scripts/diffconfig", line 132, in <module>
>       main()
>     File "/home/masahiro/linux/scripts/diffconfig", line 130, in main
>       print_config("+", config, None, b[config])
>     File "/home/masahiro/linux/scripts/diffconfig", line 64, in print_config
>       print("+%s %s" % (config, new_value))
>   BrokenPipeError: [Errno 32] Broken pipe
>
> Python documentatin [4] notes how to make scripts die immediately and

typo: s/documentatin/documentation/

> silently:
>
>   """
>   Piping output of your program to tools like head(1) will cause a
>   SIGPIPE signal to be sent to your process when the receiver of its
>   standard output closes early. This results in an exception like
>   BrokenPipeError: [Errno 32] Broken pipe. To handle this case,
>   wrap your entry point to catch this exception as follows:
>
>     import os
>     import sys
>
>     def main():
>         try:
>             # simulate large output (your code replaces this loop)
>             for x in range(10000):
>                 print("y")
>             # flush output here to force SIGPIPE to be triggered
>             # while inside this try block.
>             sys.stdout.flush()
>         except BrokenPipeError:
>             # Python flushes standard streams on exit; redirect remaining output
>             # to devnull to avoid another BrokenPipeError at shutdown
>             devnull = os.open(os.devnull, os.O_WRONLY)
>             os.dup2(devnull, sys.stdout.fileno())
>             sys.exit(1)  # Python exits with error code 1 on EPIPE
>
>     if __name__ == '__main__':
>         main()
>
>   Do not set SIGPIPE’s disposition to SIG_DFL in order to avoid
>   BrokenPipeError. Doing that would cause your program to exit
>   unexpectedly whenever any socket connection is interrupted while
>   your program is still writing to it.
>   """
>
> Currently, tools/perf/scripts/python/intel-pt-events.py seems the
> only script that fixes the issue that way.
>
> tools/perf/scripts/python/compaction-times.py uses another approach
> signal.signal(signal.SIGPIPE, signal.SIG_DFL) but the Python
> documentation clearly says "Don't do it".
>
> I cannot fix all Python scripts since there are so many.
> I fixed some in the scripts/ directory.

That's ok; "Rome wasn't built in a day." This is a good start!
Thank you for the patch!
Reviewed-by: Nick Desaulniers <ndesaulniers@xxxxxxxxxx>

>
> [1]: https://lore.kernel.org/all/202211161056.1B9611A@keescook/
> [2]: https://github.com/llvm/llvm-project/issues/59037
> [3]: https://github.com/llvm/llvm-project/commit/4787efa38066adb51e2c049499d25b3610c0877b
> [4]: https://docs.python.org/3/library/signal.html#note-on-sigpipe
>
> Cc: Kees Cook <keescook@xxxxxxxxxxxx>
> Signed-off-by: Masahiro Yamada <masahiroy@xxxxxxxxxx>
> ---
>
>  scripts/checkkconfigsymbols.py         | 13 ++++++++++++-
>  scripts/clang-tools/run-clang-tools.py | 21 ++++++++++++++-------
>  scripts/diffconfig                     | 16 ++++++++++++++--
>  3 files changed, 40 insertions(+), 10 deletions(-)
>
> diff --git a/scripts/checkkconfigsymbols.py b/scripts/checkkconfigsymbols.py
> index 217d21abc86e..36c920e71313 100755
> --- a/scripts/checkkconfigsymbols.py
> +++ b/scripts/checkkconfigsymbols.py
> @@ -115,7 +115,7 @@ def parse_options():
>      return args
>
>
> -def main():
> +def print_undefined_symbols():
>      """Main function of this module."""
>      args = parse_options()
>
> @@ -467,5 +467,16 @@ def parse_kconfig_file(kfile):
>      return defined, references
>
>
> +def main():
> +    try:
> +        print_undefined_symbols()
> +    except BrokenPipeError:
> +        # Python flushes standard streams on exit; redirect remaining output
> +        # to devnull to avoid another BrokenPipeError at shutdown
> +        devnull = os.open(os.devnull, os.O_WRONLY)
> +        os.dup2(devnull, sys.stdout.fileno())
> +        sys.exit(1)  # Python exits with error code 1 on EPIPE
> +
> +
>  if __name__ == "__main__":
>      main()
> diff --git a/scripts/clang-tools/run-clang-tools.py b/scripts/clang-tools/run-clang-tools.py
> index 56f2ec8f0f40..3266708a8658 100755
> --- a/scripts/clang-tools/run-clang-tools.py
> +++ b/scripts/clang-tools/run-clang-tools.py
> @@ -61,14 +61,21 @@ def run_analysis(entry):
>
>
>  def main():
> -    args = parse_arguments()
> +    try:
> +        args = parse_arguments()
>
> -    lock = multiprocessing.Lock()
> -    pool = multiprocessing.Pool(initializer=init, initargs=(lock, args))
> -    # Read JSON data into the datastore variable
> -    with open(args.path, "r") as f:
> -        datastore = json.load(f)
> -        pool.map(run_analysis, datastore)
> +        lock = multiprocessing.Lock()
> +        pool = multiprocessing.Pool(initializer=init, initargs=(lock, args))
> +        # Read JSON data into the datastore variable
> +        with open(args.path, "r") as f:
> +            datastore = json.load(f)
> +            pool.map(run_analysis, datastore)
> +    except BrokenPipeError:
> +        # Python flushes standard streams on exit; redirect remaining output
> +        # to devnull to avoid another BrokenPipeError at shutdown
> +        devnull = os.open(os.devnull, os.O_WRONLY)
> +        os.dup2(devnull, sys.stdout.fileno())
> +        sys.exit(1)  # Python exits with error code 1 on EPIPE
>
>
>  if __name__ == "__main__":
> diff --git a/scripts/diffconfig b/scripts/diffconfig
> index d5da5fa05d1d..43f0f3d273ae 100755
> --- a/scripts/diffconfig
> +++ b/scripts/diffconfig
> @@ -65,7 +65,7 @@ def print_config(op, config, value, new_value):
>          else:
>              print(" %s %s -> %s" % (config, value, new_value))
>
> -def main():
> +def show_diff():
>      global merge_style
>
>      # parse command line args
> @@ -129,4 +129,16 @@ def main():
>      for config in new:
>          print_config("+", config, None, b[config])
>
> -main()
> +def main():
> +    try:
> +        show_diff()
> +    except BrokenPipeError:
> +        # Python flushes standard streams on exit; redirect remaining output
> +        # to devnull to avoid another BrokenPipeError at shutdown
> +        devnull = os.open(os.devnull, os.O_WRONLY)
> +        os.dup2(devnull, sys.stdout.fileno())
> +        sys.exit(1)  # Python exits with error code 1 on EPIPE
> +
> +
> +if __name__ == '__main__':
> +    main()
> --
> 2.34.1
>


-- 
Thanks,
~Nick Desaulniers




[Index of Archives]     [Linux&nblp;USB Development]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite Secrets]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux