text: #!/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
  
  import os, re
  from prjxray import util
  
  
  def noprefix(tag):
      n = tag.find('.')
      assert n > 0
      return tag[n + 1:]
  
  
  def getprefix(tag):
      n = tag.find('.')
      assert n > 0
      return tag[0:n]
  
  
  def load_pipfile(pipfile, verbose=False):
      '''Returns a set of tags containing real tile type prefixes (ex: INT_L)'''
      todos = set()
      tile_type = None
      with open(pipfile, "r") as f:
          # INT_L.WW2BEG0.SR1BEG_S0
          for line in f:
              tag = line.strip().split(' ')[0]
              prefix_line = getprefix(tag)
              if tile_type is None:
                  tile_type = prefix_line
              else:
                  assert tile_type == prefix_line
              todos.add(tag)
      return todos, tile_type
  
  
  def balance_todo_list(
          pipfile,
          todos,
          balance_wire_re,
          balance_wire_direction,
          balance_wire_cnt,
          verbose=False):
      """Balance the contents of the todo list
  
      Todo list balancing allows to specify the name, direction and minimal number of
      occurrences of a PIP wire in the final todo list.
      The mechanism should be used in cases where a fuzzer times out because of an
      unsolvable todo list, i.e. the netlist and resulting segdata generated from an
      iteration keep segmatch from properly disambiguating the bits for some features.
  
      When the balance wire name regexp is specified it's guaranteed that all PIPs
      with matching wire name (whether we want to match a src or dst wire has to be
      specified with the --balance-wire-direction switch) will have at least the number
      of entries specified with the --balance-wire-cnt switch in the final todo list.
      """
      orig_todos, tile_type = load_pipfile(pipfile, verbose=verbose)
      if balance_wire_re is not None:
          todo_wires = {}
          verbose and print("Start balancing the TODO list")
          for todo in todos:
              tile_type, dst, src = todo.split(".")
              wire = src
              other_wire = dst
              if balance_wire_direction not in "src":
                  wire = dst
                  other_wire = src
              balance_wire_match = re.match(balance_wire_re, wire)
              if balance_wire_match is None:
                  continue
              if wire not in todo_wires:
                  todo_wires[wire] = set()
              todo_wires[wire].add(other_wire)
          for wire, other_wires in todo_wires.items():
              if len(other_wires) >= balance_wire_cnt:
                  continue
              else:
                  for todo in orig_todos:
                      tile_type, dst, src = todo.split(".")
                      if balance_wire_direction in "src":
                          if wire in src and dst not in todo_wires[wire]:
                              todo_wires[wire].add(dst)
                      else:
                          if wire in dst and src not in todo_wires[wire]:
                              todo_wires[wire].add(src)
                      if len(todo_wires[wire]) == balance_wire_cnt:
                          break
          for wire, other_wires in todo_wires.items():
              if len(other_wires) < balance_wire_cnt:
                  verbose and print(
                      "Warning: failed to balance the todo list for wire {}, there are only {} PIPs which meet the requirement: {}"
                      .format(wire, len(other_wires), other_wires))
              for other_wire in other_wires:
                  line = tile_type + "."
                  if balance_wire_direction in "src":
                      line += other_wire + "." + wire
                  else:
                      line += wire + "." + other_wire
                  verbose and print("Adding {}".format(line))
                  todos.add(line)
          verbose and print("Finished balancing the TODO list")
  
  
  def maketodo(
          pipfile,
          dbfile,
          intre,
          exclude_re=None,
          balance_wire_re=None,
          balance_wire_direction=None,
          balance_wire_cnt=None,
          not_endswith=None,
          verbose=False):
      '''
      db files start with INT., but pipfile lines start with INT_L
      Normalize by removing before the first dot
      050-intpips doesn't care about contents, but most fuzzers use the tile type prefix
      '''
  
      todos, tile_type = load_pipfile(pipfile, verbose=verbose)
      verbose and print('%s: %u entries' % (pipfile, len(todos)))
      if not todos:
          verbose and print('%s: %u entries, done!' % (pipfile, len(todos)))
          return
  
      verbose and print("pipfile todo sample: %s" % list(todos)[0])
  
      if 0 and verbose:
          print("TODOs")
          for todo in sorted(list(todos)):
              print('  %s' % todo)
  
      verbose and print('Pre db %s: %u entries' % (dbfile, len(todos)))
      # Allow against empty db
      if os.path.exists(dbfile):
          verbose and print("Loading %s" % dbfile)
          with open(dbfile, "r") as f:
              # INT.BYP_ALT0.BYP_BOUNCE_N3_3 !22_07 !23_07 !25_07 21_07 24_07
              for line in f:
                  tag, _bits, mode, _ = util.parse_db_line(line.strip())
                  # Only count resolved entries
                  if mode:
                      continue
                  # INT.BLAH => INT_L.BLAH
                  tag = tile_type + '.' + noprefix(tag)
  
                  # bipips works on a subset
                  if tag in todos:
                      todos.remove(tag)
                  else:
                      verbose and print(
                          "WARNING: couldnt remove %s (line %s)" %
                          (tag, line.strip()))
      else:
          verbose and print("WARNING: dbfile doesnt exist: %s" % dbfile)
      verbose and print('Post db %s: %u entries' % (dbfile, len(todos)))
  
      drops = 0
      lines = 0
      filtered_todos = set()
      for line in todos:
          include = re.match(intre, line) is not None
  
          if include and not_endswith is not None:
              include = not line.endswith(not_endswith)
  
          if include and exclude_re is not None:
              include = re.match(exclude_re, line) is None
  
          if include:
              filtered_todos.add(line)
          else:
              drops += 1
          lines += 1
      verbose and print('Print %u entries w/ %u drops' % (lines, drops))
  
      balance_todo_list(
          pipfile, filtered_todos, balance_wire_re, balance_wire_direction,
          balance_wire_cnt, verbose)
      for todo in filtered_todos:
          print(todo)
  
  
  def run(
          build_dir,
          db_dir,
          pip_dir,
          intre,
          sides,
          l,
          r,
          pip_type,
          seg_type,
          exclude_re=None,
          balance_wire_re=None,
          balance_wire_direction=None,
          balance_wire_cnt=None,
          not_endswith=None,
          verbose=False):
      if db_dir is None:
          db_dir = "%s/%s" % (
              os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"))
  
      if pip_dir is None:
          pip_dir = "%s/piplist/build/%s" % (
              os.getenv("XRAY_FUZZERS_DIR"), pip_type)
  
      assert intre, "RE is required"
  
      for side in sides:
          if side == "l" and not l:
              continue
  
          if side == "r" and not r:
              continue
  
          if side == "xl":
              segfile = "l{}".format(seg_type)
              pipfile = "l{}".format(pip_type)
          elif side == "xr":
              segfile = "r{}".format(seg_type)
              pipfile = "r{}".format(pip_type)
          elif side != "":
              segfile = "{}_{}".format(seg_type, side)
              pipfile = "{}_{}".format(pip_type, side)
          else:
              segfile = "{}".format(seg_type)
              pipfile = "{}".format(pip_type)
  
          maketodo(
              "%s/%s.txt" % (pip_dir, pipfile),
              "%s/segbits_%s.db" % (db_dir, segfile),
              intre,
              exclude_re=exclude_re,
              balance_wire_re=balance_wire_re,
              balance_wire_direction=balance_wire_direction,
              balance_wire_cnt=balance_wire_cnt,
              not_endswith=not_endswith,
              verbose=verbose)
  
  
  def main():
      import argparse
  
      parser = argparse.ArgumentParser(
          description="Print list of known but unsolved PIPs")
  
      parser.add_argument('--verbose', action='store_true', help='')
      parser.add_argument('--build-dir', default="build", help='')
      parser.add_argument('--db-dir', default=None, help='')
      parser.add_argument('--pip-dir', default=None, help='')
      parser.add_argument('--re', required=True, help='')
      parser.add_argument('--exclude-re', required=False, default=None, help='')
      parser.add_argument(
          '--balance-wire-re', required=False, default=None, help='')
      parser.add_argument(
          '--balance-wire-direction', required=False, default="src", help='')
      parser.add_argument(
          '--balance-wire-cnt', required=False, default="1", help='')
      parser.add_argument(
          '--balance-re-wire', required=False, default="src", help='')
      parser.add_argument('--pip-type', default="pips_int", help='')
      parser.add_argument('--seg-type', default="int", help='')
      parser.add_argument('--sides', default="l,r", help='')
      util.add_bool_arg(parser, '--l', default=True, help='')
      util.add_bool_arg(parser, '--r', default=True, help='')
      parser.add_argument(
          '--not-endswith', help='Drop lines if they end with this')
      args = parser.parse_args()
  
      run(
          build_dir=args.build_dir,
          db_dir=args.db_dir,
          pip_dir=args.pip_dir,
          intre=args.re,
          exclude_re=args.exclude_re,
          balance_wire_re=args.balance_wire_re,
          balance_wire_direction=args.balance_wire_direction,
          balance_wire_cnt=int(args.balance_wire_cnt),
          sides=args.sides.split(','),
          l=args.l,
          r=args.r,
          pip_type=args.pip_type,
          seg_type=args.seg_type,
          not_endswith=args.not_endswith,
          verbose=args.verbose)
  
  
  if __name__ == '__main__':
      main()
  