#!/usr/bin/env python3 # Copyright 2015-2017 Obsidian Research Corp. # Licensed under BSD (MIT variant) or GPLv2. See COPYING. import argparse import subprocess import os import collections import re import itertools headers = { "bits/sysmacros.h", "endian.h", "netinet/in.h", "pthread.h", "stdatomic.h", "stdlib.h", "sys/socket.h", }; def norm_header(fn): for I in headers: flat = I.replace("/","-"); if fn.endswith(flat): return I; if fn.endswith(flat + ".diff"): return I; return None; def find_system_header(args,hdr): """/usr/include is not always where the include files are, particularly if we are running full multi-arch as the azure_pipeline container does. Get gcc to tell us where /usr/include is""" if "incpath" not in args: cpp = subprocess.check_output([args.cc, "-print-prog-name=cpp"],universal_newlines=True).strip() data = subprocess.check_output([cpp, "-v"],universal_newlines=True,stdin=subprocess.DEVNULL, stderr=subprocess.STDOUT) args.incpath = []; for incdir in re.finditer(r"^ (/\S+)$", data, re.MULTILINE): incdir = incdir.group(1) if "fixed" in incdir: continue; args.incpath.append(incdir) for incdir in args.incpath: fn = os.path.join(incdir,hdr) if os.path.exists(fn): return fn return None; def get_buildlib_patches(dfn): """Within the buildlib directory we store patches for the glibc headers. Each patch is in a numbered sub directory that indicates the order to try, the number should match the glibc version used to make the diff.""" ver_hdrs = []; all_hdrs = [] for d,_,files in os.walk(dfn): for I in files: if d != dfn: bn = int(os.path.basename(d)); else: bn = 0; if bn == 0: all_hdrs.append(os.path.join(d,I)); else: ver_hdrs.append((bn,os.path.join(d,I))); ver_hdrs.sort(reverse=True); def add_to_dict(d,lst): for I in lst: nh = norm_header(I) if nh is None: continue; assert nh not in d d[nh] = (I, find_system_header(args,nh)) ret = [] for k,g in itertools.groupby(ver_hdrs,key=lambda x:x[0]): dd = {} ret.append(dd) add_to_dict(dd,(I for _,I in g)) add_to_dict(dd,all_hdrs) return ret; def is_patch(fn): with open(fn) as F: return F.read(10).startswith("-- /"); def apply_patch(src,patch,dest): """Patch a single system header. The output goes into our include search path and takes precedence over the system version.""" if src is None: return False dfn = os.path.dirname(dest); if not os.path.isdir(dfn): os.makedirs(dfn); if not patch.endswith(".diff"): if not os.path.exists(dest): os.symlink(patch,dest); return True; try: if os.path.exists(dest + ".rej"): os.unlink(dest + ".rej"); subprocess.check_output(["patch","-f","--follow-symlinks","-V","never","-i",patch,"-o",dest,src]); if os.path.exists(dest + ".rej"): print("Patch from %r failed"%(patch)); return False; except subprocess.CalledProcessError: print("Patch from %r failed"%(patch)); return False; return True; def replace_headers(suite): # Local system does not have the reference system header, this suite is # not supported for fn,pfn in suite.items(): if pfn[1] is None: return False; for fn,pfn in suite.items(): if not apply_patch(pfn[1],pfn[0],os.path.join(args.INCLUDE,fn)): break; else: return True; for fn,_ in suite.items(): try: os.unlink(os.path.join(args.INCLUDE,fn)) except OSError: continue; return False; def save(fn,outdir): """Diff the header file in our include directory against the system header and store the diff into buildlib. This makes it fairly easy to maintain the replacement headers.""" if os.path.islink(os.path.join(args.INCLUDE,fn)): return; flatfn = fn.replace("/","-") + ".diff"; flatfn = os.path.join(outdir,flatfn); includefn = os.path.join(args.INCLUDE,fn) if not os.path.exists(includefn): return cwd = os.getcwd() with open(flatfn,"wt") as F: os.chdir(os.path.join(args.INCLUDE,"..")) try: subprocess.check_call(["diff","-u", find_system_header(args,fn), os.path.join("include",fn)], stdout=F); except subprocess.CalledProcessError as ex: if ex.returncode == 1: return; raise; finally: os.chdir(cwd) parser = argparse.ArgumentParser(description='Produce sparse shim header files') parser.add_argument("--out",dest="INCLUDE",required=True, help="Directory to write header files to"); parser.add_argument("--src",dest="SRC",required=True, help="Top of the source tree"); parser.add_argument("--cc",default="gcc", help="System compiler to use to locate the default system headers"); parser.add_argument("--save",action="store_true",default=False, help="Save mode will write the current content of the headers to buildlib as a diff."); args = parser.parse_args(); if args.save: # Get the glibc version string ver = subprocess.check_output(["ldd","--version"]).decode() ver = ver.splitlines()[0].split(' ')[-1]; ver = ver.partition(".")[-1]; outdir = os.path.join(args.SRC,"buildlib","sparse-include",ver); if not os.path.isdir(outdir): os.makedirs(outdir); for I in headers: save(I,outdir); else: failed = False; suites = get_buildlib_patches(os.path.join(args.SRC,"buildlib","sparse-include")); for I in suites: if replace_headers(I): break; else: raise ValueError("Patch applications failed");