#!/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()