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

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

 



On Thu, Jan 12, 2023 at 10:55:45AM -0800 Nick Desaulniers wrote:
> 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

Hi Masahiro,

should it be "... seems to be the ..."?  

Reviewed-by: Nicolas Schier <nicolas@xxxxxxxxx>

> > 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

Attachment: signature.asc
Description: PGP signature


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

  Powered by Linux