| #!/usr/bin/env python3 | 
 | # -*- coding: utf-8 -*- | 
 | # | 
 | # Copyright (C) 2017-2020  The Project X-Ray Authors. | 
 | # | 
 | # Use of this source code is governed by a ISC-style | 
 | # license that can be found in the LICENSE file or at | 
 | # https://opensource.org/licenses/ISC | 
 | # | 
 | # SPDX-License-Identifier: ISC | 
 |  | 
 | from __future__ import print_function | 
 | import sys, re | 
 | import os | 
 | import glob | 
 | import hashlib | 
 |  | 
 |  | 
 | def bytehex(x): | 
 |     return ''.join('{:02x}'.format(x) for x in x) | 
 |  | 
 |  | 
 | def wc_for_iteration(todo_dir, fni): | 
 |     with open("%s/%u_all.txt" % (todo_dir, fni), "rb") as f: | 
 |         return sum(1 for _ in f) | 
 |  | 
 |  | 
 | def check_made_progress(todo_dir, max_iter, min_progress): | 
 |     """ Returns true if minimum progress is being made. """ | 
 |     if max_iter == 1: | 
 |         return True | 
 |  | 
 |     prev_iteration = wc_for_iteration(todo_dir, max_iter - 1) | 
 |     cur_iteration = wc_for_iteration(todo_dir, max_iter) | 
 |  | 
 |     made_progress = prev_iteration - cur_iteration > min_progress | 
 |     if not made_progress: | 
 |         print( | 
 |             "Between iteration {} and iteration {} only {} pips were solved.  Terminating iteration." | 
 |             .format(max_iter - 1, max_iter, prev_iteration - cur_iteration)) | 
 |  | 
 |     return made_progress | 
 |  | 
 |  | 
 | def run( | 
 |         todo_dir, | 
 |         min_iters=None, | 
 |         min_progress=None, | 
 |         timeout_iters=None, | 
 |         max_iters=None, | 
 |         zero_entries=None, | 
 |         zero_entries_filter=".*", | 
 |         verbose=False): | 
 |     timeout_fn = "%s/timeout" % todo_dir | 
 |     # make clean removes todo dir, but helps debugging | 
 |     if os.path.exists(timeout_fn): | 
 |         print("WARNING: removing %s" % timeout_fn) | 
 |         os.remove(timeout_fn) | 
 |  | 
 |     alls = glob.glob("%s/*_all.txt" % todo_dir) | 
 |     max_iter = 0 | 
 |     for fn in alls: | 
 |         n = int(re.match(r".*/([0-9]*)_all.txt", fn).group(1)) | 
 |         max_iter = max(max_iter, n) | 
 |  | 
 |     if max_iter == 0: | 
 |         print("Incomplete: no iters") | 
 |         sys.exit(1) | 
 |  | 
 |     verbose and print("Max iter: %u, need: %s" % (max_iter, min_iters)) | 
 |  | 
 |     # Don't allow early termination if below min_iters | 
 |     if min_iters is not None and max_iter < min_iters: | 
 |         print("Incomplete: not enough iters") | 
 |         sys.exit(1) | 
 |  | 
 |     # Force early termination if at or above max_iters. | 
 |     if max_iters is not None and max_iter >= max_iters: | 
 |         print( | 
 |             "Complete: reached max iters (want %u, got %u)" % | 
 |             (max_iters, max_iter)) | 
 |         sys.exit(0) | 
 |  | 
 |     # Mark timeout if above timeout_iters | 
 |     if timeout_iters is not None and max_iter > timeout_iters: | 
 |         print("ERROR: timeout (max %u, got %u)" % (timeout_iters, max_iter)) | 
 |         with open(timeout_fn, "w") as _f: | 
 |             pass | 
 |         sys.exit(1) | 
 |  | 
 |     # Check if zero entries criteria is not met. | 
 |     if zero_entries: | 
 |         filt = re.compile(zero_entries_filter) | 
 |         count = 0 | 
 |         fn = "%s/%u_all.txt" % (todo_dir, max_iter) | 
 |         with open(fn, 'r') as f: | 
 |             for l in f: | 
 |                 if filt.search(l): | 
 |                     count += 1 | 
 |  | 
 |         if count > 0: | 
 |             print("%s: %s lines" % (fn, count)) | 
 |             print( | 
 |                 "Incomplete: need zero entries (used filter: {})".format( | 
 |                     repr(zero_entries_filter))) | 
 |             sys.exit(1) | 
 |         else: | 
 |             # If there are zero entries, check if min_progress criteria is in | 
 |             # affect. If so, that becomes the new termination condition. | 
 |             if min_progress is None: | 
 |                 print( | 
 |                     "No unfiltered entries, done (used filter: {})!".format( | 
 |                         repr(zero_entries_filter))) | 
 |                 sys.exit(0) | 
 |             else: | 
 |                 # Even if there are 0 unfiltered entries, fuzzer may still be | 
 |                 # making progress with filtered entries. | 
 |                 print( | 
 |                     "No unfiltered entries (used filter: {}), checking if progress is being made" | 
 |                     .format(repr(zero_entries_filter))) | 
 |  | 
 |     # Check if minimum progress was achieved, continue iteration if so. | 
 |     if min_progress is not None and not check_made_progress(todo_dir, max_iter, | 
 |                                                             min_progress): | 
 |         sys.exit(0) | 
 |  | 
 |     print("No exit criteria met, keep going!") | 
 |     sys.exit(1) | 
 |  | 
 |  | 
 | def main(): | 
 |     import argparse | 
 |  | 
 |     parser = argparse.ArgumentParser( | 
 |         description= | 
 |         "Check int_loop completion. Exits 0 on done, 1 if more loops are needed" | 
 |     ) | 
 |  | 
 |     parser.add_argument('--verbose', action='store_true', help='') | 
 |     parser.add_argument('--todo-dir', default="build/todo", help='') | 
 |     parser.add_argument( | 
 |         '--min-iters', default=None, help='Minimum total number of iterations') | 
 |     parser.add_argument( | 
 |         '--min-progress', | 
 |         default=None, | 
 |         help= | 
 |         'Minimum amount of process between iterations.  If less progress is made, terminates immediately.' | 
 |     ) | 
 |     parser.add_argument( | 
 |         '--timeout-iters', | 
 |         default=None, | 
 |         help='Max number of entries before creating todo/timeout') | 
 |     parser.add_argument( | 
 |         '--max-iters', | 
 |         default=None, | 
 |         help='Max number of entries before declaring success') | 
 |     parser.add_argument( | 
 |         '--zero-entries', | 
 |         action="store_true", | 
 |         help='Must be no unsolved entries in latest') | 
 |     parser.add_argument( | 
 |         '--zero-entries-filter', | 
 |         default=".*", | 
 |         help= | 
 |         'When zero-entries is supplied, this filter is used to filter pips used for counting against zero entries termination condition.' | 
 |     ) | 
 |     args = parser.parse_args() | 
 |  | 
 |     def zint(x): | 
 |         return None if x is None else int(x) | 
 |  | 
 |     run( | 
 |         todo_dir=args.todo_dir, | 
 |         min_iters=zint(args.min_iters), | 
 |         min_progress=zint(args.min_progress), | 
 |         timeout_iters=zint(args.timeout_iters), | 
 |         max_iters=zint(args.max_iters), | 
 |         zero_entries=args.zero_entries, | 
 |         zero_entries_filter=args.zero_entries_filter, | 
 |         verbose=args.verbose) | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |     main() |