#!/usr/bin/env python from __future__ import print_function # Copyright (c) 2014 Justus Winter <4winter@informatik.uni-hamburg.de> # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import difflib import logging as log import os import re import subprocess import string import sys log.basicConfig(format='%(levelname)s:%(message)s', level=log.DEBUG) base_path = os.path.dirname (os.path.dirname (os.path.abspath (sys.argv[0]))) template_path = os.path.join (base_path, "share", "portseal") state_path = os.path.abspath (".portseal") portseal_h = os.path.join (base_path, "libportseal", "portseal.h") portseal_L = os.path.join (base_path, "libportseal") def pkg_config (pkg, what): return subprocess.check_output (["pkg-config", "--"+what, pkg]).strip () ldflags = "-L{0} -Wl,-rpath={0} -lportseal -ldl {1} {2} {3}".format ( portseal_L, pkg_config ("liburcu", "libs"), pkg_config ("liburcu-cds", "libs"), pkg_config ("liburcu-mb", "libs"), ) def template (x): return os.path.join (template_path, x) def state (x): return os.path.join (state_path, x) def make_state_dir (): if not os.path.exists (state_path): os.mkdir (state_path) def is_patched (): return os.path.exists (state ("portseal.patch")) def template_apply (name, mapping, sink=None, **overlay): log.debug ("applying template {0}".format (name)) with open (template (name)) as source: t = source.read () m = mapping.copy () m.update (overlay) for key, value in sorted (m.items (), cmp=lambda a, b: cmp (len (a[0]), len (b[0])), reverse=True): t = t.replace (key, value) if sink: sink.write (t) else: with open (state (name), "w") as sink: sink.write (t) def spatch (cocci): includes = list () for i in (args.I or ["/usr/include"]): includes.extend (("-I", i)) c = ["spatch", "--no-loops", "--no-gotos", "--smpl-spacing", "--include-headers", "--sp-file", cocci, "-dir", ".", ] + includes log.debug ("executing {0}".format (c)) return subprocess.check_output (c) def do_wrap_hack(s, r = False): wraphack = 'PORTSEAL_WRAP_HACK' if r: wraphack += '_R' def walk (s, i, direction, p): while p != 0: if s[i] == "(": p += 1 elif s[i] == ")": p -= 1 i += direction return i def skip_ws (s, i, direction): while 0 < i < len (s) - 1: if string.strip (s[i]): break i += direction return i def skip_nws (s, i, direction): while 0 < i < len (s) - 1: if not string.strip (s[i]): break i += direction return i while wraphack in s: i = s.index(wraphack) start = skip_nws (s, skip_ws (s, walk (s, i, -1, -1), -1), -1) id_start = skip_ws (s, i + len (wraphack), 1) id_end = walk (s, id_start + 1, 1, 1) ident = s[id_start:id_end] end = walk (s, id_end, 1, 1) s = "{0} PORTSEAL_WRAP{1}({2}{3}, {4}){5}".format ( s[:start], "_R" if r else "", s[start:i], s[id_start:end], ident, s[end:]) return s def patch (args): if is_patched (): return make_state_dir () mapping = dict () with open (state ("portseal.cocci"), "w") as sink: template_apply ("portseal.cocci", mapping, sink) cocci_patch = spatch (state ("portseal.cocci")) patches = list () patches.append (cocci_patch) # add include to every file the cocci patch touched for l in cocci_patch.split ("\n"): if not l.startswith ("--- a/"): continue patches.append (make_patch ( l[6:], lambda a: "#include \"{0}\"\n#line 1\n{1}".format (portseal_h, a))) ldflags_re = re.compile (r"^(\s*LDFLAGS\s*=.*)(\\?)$", re.M) for dirpath, dirs, files in os.walk("."): if dirpath.endswith (".portseal"): continue for f in filter (lambda f: "Make" in f, files): patches.append ( make_patch ( os.path.join (dirpath, f), lambda m: ldflags_re.sub (r"\1 {0} \2".format (ldflags), m, 0))) with open(state ("portseal.patch"), "w") as h: h.write ("\n".join (patches)) subprocess.check_call ( ["patch", "-p1"], stdin=open (state ("portseal.patch")), ) # wraphack wraphack_patches = list () for dirpath, dirs, files in os.walk("."): if dirpath.endswith (".portseal"): continue #continue for f in filter (lambda f: f[-2:] in ('.c', '.h'), files): try: wraphack_patches.append ( make_patch ( os.path.join (dirpath, f), lambda m: do_wrap_hack (do_wrap_hack (m, True)))) except IOError: pass with open(state ("wraphack.patch"), "w") as h: h.write ("\n".join (wraphack_patches)) subprocess.check_call ( ["patch", "-p1"], stdin=open (state ("wraphack.patch")), ) def make_patch (filename, mutator): a = open (filename).read () b = mutator (a) p = os.path.join ("a", filename) def split(x): return [l+"\n" for l in x.split ("\n")] return "".join (difflib.unified_diff (split (a), split (b), fromfile=p, tofile=p)) def unpatch (args): if not is_patched (): return subprocess.check_call ( ["patch", "-p1", "-R"], stdin=open (state ("wraphack.patch")), ) subprocess.check_call ( ["patch", "-p1", "-R"], stdin=open (state ("portseal.patch")), ) os.remove (state ("portseal.patch")) import argparse parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(title='subcommands', description='valid subcommands', help='additional help') parser_patch = subparsers.add_parser('patch') parser_patch.set_defaults(func=patch) parser_patch.add_argument('-I', metavar='DIR', action="append", help='add include directory') parser_unpatch = subparsers.add_parser('unpatch') parser_unpatch.set_defaults(func=unpatch) args = parser.parse_args() sys.exit(args.func(args))