tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

add_c_file.py (11346B)


      1 #!/usr/bin/env python3
      2 
      3 """
      4   Add a C file with matching header to the Tor codebase.  Creates
      5   both files from templates, and adds them to the right include.am file.
      6 
      7   This script takes paths relative to the top-level tor directory. It
      8   expects to be run from that directory.
      9 
     10   This script creates files, and inserts them into include.am, also
     11   relative to the top-level tor directory.
     12 
     13   But the template content in those files is relative to tor's src
     14   directory. (This script strips "src" from the paths used to create
     15   templated comments and macros.)
     16 
     17   This script expects posix paths, so it should be run with a python
     18   where os.path is posixpath. (Rather than ntpath.) This probably means
     19   Linux, macOS, or BSD, although it might work on Windows if your python
     20   was compiled with mingw, MSYS, or cygwin.
     21 
     22   Example usage:
     23 
     24   % add_c_file.py ./src/feature/dirauth/ocelot.c
     25 """
     26 
     27 # Future imports for Python 2.7, mandatory in 3.0
     28 from __future__ import division
     29 from __future__ import print_function
     30 from __future__ import unicode_literals
     31 
     32 import os
     33 import re
     34 import time
     35 
     36 def tordir_file(fname):
     37    """Make fname relative to the current directory, which should be the
     38       top-level tor directory. Also performs basic path simplifications."""
     39    return os.path.normpath(os.path.relpath(fname))
     40 
     41 def srcdir_file(tor_fname):
     42    """Make tor_fname relative to tor's "src" directory.
     43       Also performs basic path simplifications.
     44       (This function takes paths relative to the top-level tor directory,
     45       but outputs a path that is relative to tor's src directory.)"""
     46    return os.path.normpath(os.path.relpath(tor_fname, 'src'))
     47 
     48 def guard_macro(src_fname):
     49    """Return the guard macro that should be used for the header file
     50       'src_fname'. This function takes paths relative to tor's src directory.
     51    """
     52    td = src_fname.replace(".", "_").replace("/", "_").upper()
     53    return "TOR_{}".format(td)
     54 
     55 def makeext(fname, new_extension):
     56    """Replace the extension for the file called 'fname' with 'new_extension'.
     57       This function takes and returns paths relative to either the top-level
     58       tor directory, or tor's src directory, and returns the same kind
     59       of path.
     60    """
     61    base = os.path.splitext(fname)[0]
     62    return base + "." + new_extension
     63 
     64 def instantiate_template(template, tor_fname):
     65    """
     66    Fill in a template with string using the fields that should be used
     67    for 'tor_fname'.
     68 
     69    This function takes paths relative to the top-level tor directory,
     70    but the paths in the completed template are relative to tor's src
     71    directory. (Except for one of the fields, which is just a basename).
     72    """
     73    src_fname = srcdir_file(tor_fname)
     74    names = {
     75        # The relative location of the header file.
     76        'header_path' : makeext(src_fname, "h"),
     77        # The relative location of the C file file.
     78        'c_file_path' : makeext(src_fname, "c"),
     79        # The truncated name of the file.
     80        'short_name' : os.path.basename(src_fname),
     81        # The current year, for the copyright notice
     82        'this_year' : time.localtime().tm_year,
     83        # An appropriate guard macro, for the header.
     84        'guard_macro' : guard_macro(src_fname),
     85    }
     86 
     87    return template.format(**names)
     88 
     89 # This template operates on paths relative to tor's src directory
     90 HEADER_TEMPLATE = """\
     91 /* Copyright (c) 2001 Matej Pfajfar.
     92 * Copyright (c) 2001-2004, Roger Dingledine.
     93 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
     94 * Copyright (c) 2007-{this_year}, The Tor Project, Inc. */
     95 /* See LICENSE for licensing information */
     96 
     97 /**
     98 * @file {short_name}
     99 * @brief Header for {c_file_path}
    100 **/
    101 
    102 #ifndef {guard_macro}
    103 #define {guard_macro}
    104 
    105 #endif /* !defined({guard_macro}) */
    106 """
    107 
    108 # This template operates on paths relative to the tor's src directory
    109 C_FILE_TEMPLATE = """\
    110 /* Copyright (c) 2001 Matej Pfajfar.
    111 * Copyright (c) 2001-2004, Roger Dingledine.
    112 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
    113 * Copyright (c) 2007-{this_year}, The Tor Project, Inc. */
    114 /* See LICENSE for licensing information */
    115 
    116 /**
    117 * @file {short_name}
    118 * @brief DOCDOC
    119 **/
    120 
    121 #include "orconfig.h"
    122 #include "{header_path}"
    123 """
    124 
    125 class AutomakeChunk:
    126    """
    127    Represents part of an automake file.  If it is decorated with
    128    an ADD_C_FILE comment, it has a "kind" based on what to add to it.
    129    Otherwise, it only has a bunch of lines in it.
    130 
    131    This class operates on paths relative to the top-level tor directory.
    132    """
    133    pat = re.compile(r'# ADD_C_FILE: INSERT (\S*) HERE', re.I)
    134 
    135    def __init__(self):
    136        self.lines = []
    137        self.kind = ""
    138        self.hasBlank = False # true if we end with a blank line.
    139 
    140    def addLine(self, line):
    141        """
    142        Insert a line into this chunk while parsing the automake file.
    143 
    144        Return True if we have just read the last line in the chunk, and
    145        False otherwise.
    146        """
    147        m = self.pat.match(line)
    148        if m:
    149            if self.lines:
    150                raise ValueError("control line not preceded by a blank line")
    151            self.kind = m.group(1)
    152 
    153        if line.strip() == "":
    154            self.hasBlank = True
    155            return True
    156 
    157        self.lines.append(line)
    158 
    159        return False
    160 
    161    def insertMember(self, new_tor_fname):
    162        """
    163        Add a new file name new_tor_fname to this chunk.  Try to insert it in
    164        alphabetical order with matching indentation, but don't freak out too
    165        much if the source isn't consistent.
    166 
    167        Assumes that this chunk is of the form:
    168           FOOBAR = \
    169              X     \
    170              Y     \
    171              Z
    172 
    173        This function operates on paths relative to the top-level tor
    174        directory.
    175        """
    176        prespace = "\t"
    177        postspace = "\t\t"
    178        for lineno, line in enumerate(self.lines):
    179            m = re.match(r'(\s+)(\S+)(\s+)\\', line)
    180            if not m:
    181                continue
    182            prespace, cur_tor_fname, postspace = m.groups()
    183            if cur_tor_fname > new_tor_fname:
    184                self.insert_before(lineno, new_tor_fname, prespace, postspace)
    185                return
    186        self.insert_at_end(new_tor_fname, prespace, postspace)
    187 
    188    def insert_before(self, lineno, new_tor_fname, prespace, postspace):
    189        self.lines.insert(lineno,
    190                          "{}{}{}\\\n".format(prespace, new_tor_fname,
    191                                              postspace))
    192 
    193    def insert_at_end(self, new_tor_fname, prespace, postspace):
    194        lastline = self.lines[-1].strip()
    195        self.lines[-1] = '{}{}{}\\\n'.format(prespace, lastline, postspace)
    196        self.lines.append("{}{}\n".format(prespace, new_tor_fname))
    197 
    198    def dump(self, f):
    199        """Write all the lines in this chunk to the file 'f'."""
    200        for line in self.lines:
    201            f.write(line)
    202            if not line.endswith("\n"):
    203                f.write("\n")
    204 
    205        if self.hasBlank:
    206            f.write("\n")
    207 
    208 class ParsedAutomake:
    209    """A sort-of-parsed automake file, with identified chunks into which
    210       headers and c files can be inserted.
    211 
    212       This class operates on paths relative to the top-level tor directory.
    213    """
    214    def __init__(self):
    215        self.chunks = []
    216        self.by_type = {}
    217 
    218    def addChunk(self, chunk):
    219        """Add a newly parsed AutomakeChunk to this file."""
    220        self.chunks.append(chunk)
    221        self.by_type[chunk.kind.lower()] = chunk
    222 
    223    def add_file(self, tor_fname, kind):
    224        """Insert a file tor_fname of kind 'kind' to the appropriate
    225           section of this file. Return True if we added it.
    226 
    227           This function operates on paths relative to the top-level tor
    228           directory.
    229        """
    230        if kind.lower() in self.by_type:
    231            self.by_type[kind.lower()].insertMember(tor_fname)
    232            return True
    233        else:
    234            return False
    235 
    236    def dump(self, f):
    237        """Write this file into a file 'f'."""
    238        for chunk in self.chunks:
    239            chunk.dump(f)
    240 
    241 def get_include_am_location(tor_fname):
    242    """Find the right include.am file for introducing a new file
    243       tor_fname.  Return None if we can't guess one.
    244 
    245       Note that this function is imperfect because our include.am layout is
    246       not (yet) consistent.
    247 
    248       This function operates on paths relative to the top-level tor directory.
    249    """
    250    # Strip src for pattern matching, but add it back when returning the path
    251    src_fname = srcdir_file(tor_fname)
    252    m = re.match(r'^(lib|core|feature|app)/([a-z0-9_]*)/', src_fname)
    253    if m:
    254        return "src/{}/{}/include.am".format(m.group(1),m.group(2))
    255 
    256    if re.match(r'^test/', src_fname):
    257        return "src/test/include.am"
    258 
    259    return None
    260 
    261 def run(fname):
    262    """
    263    Create a new C file and H file corresponding to the filename "fname",
    264    and add them to the corresponding include.am.
    265 
    266    This function operates on paths relative to the top-level tor directory.
    267    """
    268 
    269    # Make sure we're in the top-level tor directory,
    270    # which contains the src directory
    271    if not os.path.isdir("src"):
    272        raise RuntimeError("Could not find './src/'. "
    273                           "Run this script from the top-level tor source "
    274                           "directory.")
    275 
    276    # And it looks like a tor/src directory
    277    if not os.path.isfile("src/include.am"):
    278        raise RuntimeError("Could not find './src/include.am'. "
    279                           "Run this script from the top-level tor source "
    280                           "directory.")
    281 
    282    # Make the file name relative to the top-level tor directory
    283    tor_fname = tordir_file(fname)
    284    # And check that we're adding files to the "src" directory,
    285    # with canonical paths
    286    if tor_fname[:4] != "src/":
    287        raise ValueError("Requested file path '{}' canonicalized to '{}', "
    288                         "but the canonical path did not start with 'src/'. "
    289                         "Please add files to the src directory."
    290                         .format(fname, tor_fname))
    291 
    292    c_tor_fname = makeext(tor_fname, "c")
    293    h_tor_fname = makeext(tor_fname, "h")
    294 
    295    if os.path.exists(c_tor_fname):
    296        print("{} already exists".format(c_tor_fname))
    297        return 1
    298    if os.path.exists(h_tor_fname):
    299        print("{} already exists".format(h_tor_fname))
    300        return 1
    301 
    302    with open(c_tor_fname, 'w') as f:
    303        f.write(instantiate_template(C_FILE_TEMPLATE, c_tor_fname))
    304 
    305    with open(h_tor_fname, 'w') as f:
    306        f.write(instantiate_template(HEADER_TEMPLATE, h_tor_fname))
    307 
    308    iam = get_include_am_location(c_tor_fname)
    309    if iam is None or not os.path.exists(iam):
    310        print("Made files successfully but couldn't identify include.am for {}"
    311              .format(c_tor_fname))
    312        return 1
    313 
    314    amfile = ParsedAutomake()
    315    cur_chunk = AutomakeChunk()
    316    with open(iam) as f:
    317        for line in f:
    318            if cur_chunk.addLine(line):
    319                amfile.addChunk(cur_chunk)
    320                cur_chunk = AutomakeChunk()
    321        amfile.addChunk(cur_chunk)
    322 
    323    amfile.add_file(c_tor_fname, "sources")
    324    amfile.add_file(h_tor_fname, "headers")
    325 
    326    with open(iam+".tmp", 'w') as f:
    327        amfile.dump(f)
    328 
    329    os.rename(iam+".tmp", iam)
    330 
    331 if __name__ == '__main__':
    332    import sys
    333    sys.exit(run(sys.argv[1]))