#!/usr/bin/python # # kpatchup v0.02 # # Copyright 2004 Matt Mackall # # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. # # Usage: # # in an existing kernel directory, run: # # kpatchup # # where version is a complete kernel version, or a branch name to grab # the latest version # # Note: you must set KPATCHUP_ARCH to a directory to cache patches in import re, sys, urllib, os, optparse parser = optparse.OptionParser("%prog [options] version") parser.add_option("-q", "--quiet", action="store_true", dest="quiet") parser.add_option("-n", "--dry-run", action="store_true", dest="dryrun") parser.add_option("-s", "--show-latest", action="store_true", dest="show") parser.add_option("-u", "--show-url", action="store_true", dest="showurl") (options, args) = parser.parse_args() if len(args) != 1: parser.error("incorrect number of arguments") try: archive = os.environ["KPATCHUP_ARCH"] except: sys.stderr.write("Must set KPATCHUP_ARCH to patch archive directory\n") sys.exit(0) try: kernel_url = os.environ["KPATCHUP_URL"] except: kernel_url = 'http://www.kernel.org/pub/linux/kernel' # Functions to parse version strings def tree(ver): return float(re.match(r'(\d+\.\d+)', ver).group(1)) def rev(ver): p = pre(ver) r = int(re.match(r'\d+\.\d+\.(\d+)', ver).group(1)) if p: r = r - 1 return r def pre(ver): try: return re.match(r'\d+\.\d+\.\d+-((rc|pre)\d+)', ver).group(1) except: return None def pretype(ver): try: return re.match(r'\d+\.\d+\.\d+-((rc|pre)\d+)', ver).group(2) except: return None def prenum(ver): try: return int(re.match(r'\d+\.\d+\.\d+-((rc|pre)(\d+))', ver).group(3)) except: return None def prebase(ver): return re.match(r'(\d+\.\d+\.\d+(-(rc|pre)\d+)?)', ver).group(1) def base(ver): return "%s.%s" % (tree(ver), rev(ver)) def forkname(ver): try: return re.match(r'\d+.\d+.\d+(-(rc|pre)\d+)?(-(\w+?)\d+)?', ver).group(4) except: return None def forknum(ver): try: return int(re.match(r'\d+.\d+.\d+(-(rc|pre)\d+)?(-(\w+?)(\d+))?', ver).group(5)) except: return None def fork(ver): try: return re.match(r'\d+.\d+.\d+(-(rc|pre)\d+)?(-(\w+))?', ver).group(4) except: return None def get_ver(makefile): """ Read the version information from the specified makefile """ part = {} parts = "VERSION PATCHLEVEL SUBLEVEL EXTRAVERSION".split(' ') m = open(makefile) for l in m.readlines(): for p in parts: try: part[p] = re.match(r'%s\s*=\s*(\S+)' % p, l).group(1) except: pass version = "%s.%s.%s" % tuple([part[p] for p in parts[:3]]) version += part.get("EXTRAVERSION","") return version def compare_ver(a, b): """ Compare kernel versions a and b Note that -pre and -rc versions sort before the version they modify, -pre sorts before -rc, and -bk, -mm, etc. sort alphabetically. """ if a == b: return 0 c = cmp(float(tree(a)), float(tree(b))) if c: return c c = cmp(rev(a), rev(b)) if c: return c c = cmp(pretype(a), pretype(b)) # pre sorts before rc if c: return c c = cmp(prenum(a), prenum(b)) if c: return c c = cmp(forkname(a), forkname(b)) if c: return c return cmp(forknum(a), forknum(b)) def last(url): for l in urllib.urlopen(url).readlines(): m=re.search('(?i)', l) if m: n = m.group(1) return n def latest_mm(url, pat): url = kernel_url + '/people/akpm/patches/2.6/' url += last(url) part = last(url) return part[:-1] def latest_26(url, pat): for l in urllib.urlopen(url).readlines(): m = re.search('"LATEST-IS-(.*)"', l) if m: p = m.group(1) return p def latest_dir(url, pat): """Find the latest link matching pat at url after sorting""" p = [] for l in urllib.urlopen(url).readlines(): m = re.search('"%s"' % pat, l) if m: p.append(m.group(1)) p.sort(compare_ver) return p[-1] # latest lookup function, canonical url, pattern for lookup function version_info = { '2.4': (latest_dir, kernel_url + "/v2.4" + "/patch-%(base)s.bz2", r'patch-(.*?).bz2'), '2.4-pre': (latest_dir, kernel_url + "/v2.4" + "/testing/patch-%(prebase)s.bz2", r'patch-(.*?).bz2'), '2.6': (latest_26, kernel_url + "/v2.6" + "/patch-%(base)s.bz2", ""), '2.6-pre': (latest_dir, kernel_url + "/v2.6" + "/testing/patch-%(prebase)s.bz2", r'patch-(.*?).bz2'), '2.6-bk': (latest_dir, kernel_url + "/v2.6" + "/snapshots/patch-%(full)s.bz2", r'patch-(.*?).bz2'), '2.6-mm': (latest_mm, kernel_url + "/people/akpm/patches/" + "%(tree)s/%(prebase)s/%(full)s/%(full)s.bz2", ""), '2.6-tiny': (latest_dir, "http://www.selenic.com/tiny/%(full)s.patch.bz2", r'(2.6.*?).patch.bz2') } def version_url(ver): """ Return the URL for the patch associated with the specified version """ b = "%.1f" % tree(ver) f = forkname(ver) p = pre(ver) s = b if f: s = "%s-%s" % (b, f) elif p: s = "%s-pre" % b v = { 'full': ver, 'tree': tree(ver), 'base': base(ver), 'prebase': prebase(ver) } return version_info[s][1] % v def patch_path(ver): return os.path.join(archive, os.path.basename(version_url(ver))) def get_patch(ver): """Return the path to patch for given ver, downloading if necessary""" f = patch_path(ver) if os.path.exists(f): return f url = version_url(ver) if not options.quiet: print "Downloading %s" % os.path.basename(url) if options.dryrun: return f # to-do: check signatures open(f, 'w').write(urllib.urlopen(url).read()) return f def apply_patch(ver, reverse = 0): """Find the patch to upgrade from the predecessor of ver to ver and apply or reverse it.""" p = get_patch(ver) r = "" if reverse: r = "-R" if not options.quiet: print "Applying %s %s" % (os.path.basename(p), r) if options.dryrun: return ver if p[-4:] == ".bz2": err = os.system("bzcat %s | patch -l -p1 %s > .patchdiag" % (p, r)) elif p[-3:] == ".gz": err = os.system("zcat %s | patch -l -p1 %s > .patchdiag" % (p, r)) else: os.system("patch -l -p1 %s < %s > .patchdiag" % (r, p)) if err: sys.stderr.write(open(".patchdiag").read()) sys.stderr.write("patch %s failed: %d\n" % (p, err)) sys.exit(-1) def install(ver): print "Wanted to install %s", ver return ver def find_ver(ver): if ver in version_info: v = version_info[ver] for n in range(5): return v[0](os.path.dirname(v[1]), v[2]) sys.stderr.write('retrying version lookup for %s\n' % ver) else: return ver def transform(a, b): if not a: install(base(b)) a = base(b) if a == b: if not options.quiet: print "Nothing to do!" sys.exit(0) t = tree(a) if t != tree(b): sys.stderr.write("Can't patch %s to %s\n" % (tree(a), tree(b))) sys.exit(-1) if fork(a): apply_patch(a, 1) a = prebase(a) if prebase(a) != prebase(b): if pre(a): apply_patch(a, 1) a = base(a) ra, rb = rev(a), rev(b) if ra > rb: for r in range(ra, rb, -1): apply_patch("%s.%s" % (t, r), -1) if ra < rb: for r in range(ra + 1, rb + 1): apply_patch("%s.%s" % (t, r)) a = base(b) if pre(b): a = apply_patch(prebase(b)) a = prebase(b) if fork(b): a = apply_patch(b) if options.show: print find_ver(args[0]) elif options.showurl: print version_url(find_ver(args[0])) else: a = get_ver('Makefile') b = find_ver(args[0]) if not options.quiet: print "%s -> %s" % (a, b) if options.dryrun and not options.quiet: print "(simulated)" transform(a, b)