-- eric
#!/usr/bin/python3 # # ketchup 1.0.1 # http://github.com/psomas/ketchup # git://github.com/psomas/ketchup.git # # Orignial code by: # Copyright 2004 Matt Mackall <m...@selenic.com> # # Added code for new RT location and maintained until 2011 by: # Steven Rostedt <srost...@redhat.com> # # Now maintained by: # Stratos Psomadakis <pso...@cslab.ece.ntua.gr> # # Contributors: # Baruch Even <bar...@debian.org> # Pavel Machek <pa...@ucw.cz> # Johann Felix Soden <joh...@gmx.de> # Carsten Emde <c.e...@osadl.org> # # 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: # # ketchup <version> # # where version is a complete kernel version, or a branch name to grab # the latest version # # You can override some variables by creating a ~/.ketchuprc file. # The ~/.ketchuprc is just a Python script, eg. it might look like this: # # kernel_url = 'http://kernel.localdomain/pub/linux/kernel' # archive = os.environ["HOME"] + '/tmp/ketchup-archive' # gpg = '/weird/path/to/gpg' #
import re, sys, urllib, os, getopt, glob, shutil, subprocess def error(*args): sys.stderr.write("ketchup: ") for a in args: sys.stderr.write(str(a)) sys.stderr.write("\n") def qprint(*args): if not options["quiet"]: sys.stdout.write(" ".join(map(str, args))) sys.stdout.write("\n") def lprint(*args): sys.stdout.write(" ".join(map(str, args))) sys.stdout.write("\n") def fancyopts(args, options, state, syntax=''): long = [] short = '' map = {} dt = {} def help(state, opt, arg, options = options, syntax = syntax): lprint("Usage: ", syntax) for s, l, d, c in options: opt = ' ' if s: opt = opt + '-' + s + ' ' if l: opt = opt + '--' + l + ' ' if d: opt = opt + '(' + str(d) + ')' lprint(opt) if c: lprint(' %s' % c) sys.exit(0) options = [('h', 'help', help, 'Show usage info')] + options for s, l, d, c in options: map['-'+s] = map['--'+l]=l state[l] = d dt[l] = type(d) if not d is None and not type(d) is type(help): s, l = s + ':', l + '=' if s: short = short + s if l: long.append(l) if "KETCHUP_OPTS" in os.environ: args = os.environ["KETCHUP_OPTS"].split() + args try: opts, args = getopt.getopt(args, short, long) except getopt.GetoptError: help(state, None, args) sys.exit(-1) for opt, arg in opts: if dt[map[opt]] is type(help): state[map[opt]](state,map[opt],arg) elif dt[map[opt]] is type(1): state[map[opt]] = int(arg) elif dt[map[opt]] is type(''): state[map[opt]] = arg elif dt[map[opt]] is type([]): state[map[opt]].append(arg) elif dt[map[opt]] is type(None): state[map[opt]] = 1 return args # Default values kernel_url = 'https://cdn.kernel.org/pub/linux/kernel' archive = os.environ["HOME"] + "/.ketchup" rename_prefix = 'linux-' rename_with_localversion = False wget = "/usr/bin/wget" gpg = "/usr/bin/gpg" precommand = postcommand = None default_tree = None local_trees = {} # Functions to parse version strings def tree(ver): # every version must start with x.y, exit otherwise t = re.match(r'((\d+)\.\d+)', ver) if t == None: error("Invalid tree version: %s!" % ver) sys.exit(-1) # linux versions >=3.0 use only the first digit to define the 'tree' ret = int(t.group(2)) if ret >= 3: return ret # 2.x linux trees return float(t.group(1)) def rev(ver): p = pre(ver) # 3.x trees if tree(ver) >= 3: r = re.match(r'\d+\.(\d+)', ver) # 2.x trees else: r = re.match(r'\d+\.\d+\.(\d+)', ver) # if there's no match, the version is invalid try: r = int(r.group(1)) except: error("Invalid tree version!") sys.exit(-1) #for -rc versions return the previous stable version if p: r = r - 1 if r == -1 and tree(ver) == 3: r = 39 if r == -1 and tree(ver)>3: r = 19 return r def pre(ver): # 3.x trees if tree(ver) >= 3: p = re.match(r'\d+\.\d+(\.\d+)?-(rc\d+)', ver) # 2.x trees else: p = re.match(r'\d+\.\d+\.\d+(\.\d+)?-(rc\d+)', ver) try: return p.group(2) except: return None def post(ver): # 3.x trees if tree(ver) >= 3: p = re.match(r'\d+\.\d+\.(\d+)', ver) # 2.x trees else: p = re.match(r'\d+\.\d+\.\d+\.(\d+)', ver) try: return p.group(1) except: return None def prenum(ver): # 3.x trees if tree(ver) >= 3: p = re.match(r'\d+\.\d+(\.\d+)?-rc(\d+)', ver) # 2.x trees else: p = re.match(r'\d+\.\d+\.\d+(\.\d+)?-rc(\d+)', ver) try: return p.group(2) except: return None def prebase(ver): # 3.x trees if tree(ver) >= 3: p = re.match(r'(\d+\.\d+(\.\d+)?(-rc\d+)?)', ver) else: p = re.match(r'(\d+\.\d+\.\d+(\.\d+)?(-rc\d+)?)', ver) try: return p.group(1) except: return None def revbase(ver): t = tree(ver) r = rev(ver) # make sure 3.0-rcs are patched correctly against 2.6.39 if t == 3 and r == 39: t = 2.6 if t == 4 and r == 19: t = 3 return "%s.%s" % (t, r) def base(ver): v = revbase(ver) if post(ver): v += "." + post(ver) return v def forkname(ver): # 3.x trees if tree(ver) >= 3: f = re.match(r'\d+\.\d+(\.\d+)?(-(rc|pre)\d+)?(-([a-zA-Z]+)(-)?(\d+)?)?', ver) # 2.x trees else: f = re.match(r'\d+\.\d+\.\d+(\.\d+)?(-(rc|pre)\d+)?(-([a-zA-Z]+)(-)?(\d+)?)?', ver) try: return f.group(5) except: return None def forknum(ver): # 3.x trees if tree(ver) >=3: f = re.match(r'\d+\.\d+(\.\d+)?(-(rc|pre)\d+)?(-([a-zA-Z]+)(-)?(\d+)?)?', ver) #2.x trees else: f = re.match(r'\d+\.\d+\.\d+(\.\d+)?(-(rc|pre)\d+)?(-([a-zA-Z]+)(-)?(\d+)?)?', ver) try: return int(f.group(7)) except: return None def fork(ver): # 3.x trees if tree(ver) >= 3: f = re.match(r'\d+\.\d+(\.\d+)?(-(rc|pre)\d+)?((\.\d+)?-(\w+(-)?(\d+)?))?', ver) # 2.x trees else: f = re.match(r'\d+\.\d+\.\d+(\.\d+)?(-(rc|pre)\d+)?((\.\d+)?-(\w+(-)?(\d+)?))?', ver) try: return f.group(4) except: return None #FIXME: at some point the Makefile for 3.x kernels will change def get_ver(makefile): """ Read the version information from the specified makefile """ part = {} parts = "VERSION PATCHLEVEL SUBLEVEL EXTRAVERSION CKVERSION".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 # atm we use VERSION.PATCHLEVEL for Linus' releases and EXTRAVERSION for # the -stable releases if part['VERSION'] >= '3'and part['SUBLEVEL'] == '0': version = "%s.%s" % tuple([part[p] for p in parts[:2]]) else: version = "%s.%s.%s" % tuple([part[p] for p in parts[:3]]) version += part.get("EXTRAVERSION","") version += part.get("CKVERSION","") n = None try: n=open("localversion-rt") except IOError: pass if n: version += n.read()[:-1] # required for -next patchset try: n = open("localversion-next") except: return version version += n.read()[:-1] return version def get_localversion(): v = '' for name in glob.glob('localversion*'): try: v += open(name).readline().strip() except: pass try: c = open('.config').read() v += re.search(r'^CONFIG_LOCALVERSION="(.+)"', c, re.M).group(1) except: pass return v def compare_ver(a, b): """ Compare kernel versions a and b """ if a == b: return 0 c = cmp(float(tree(a)), float(tree(b))) if c: return c # compare correctly the 3.0-rc versions ra, rb = rev(a), rev(b) if (tree(a) == 3 and ra == 39): ra = -1 if (tree(a) > 3 and ra == 19): ra = -1 if (tree(b) == 3 and rb == 39): rb = -1 if (tree(b) > 3 and rb == 19): rb = -1 c = cmp(ra, rb) if c: return c c = cmp(int(post(a) or 0), int(post(b) or 0)) 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, pat="(.*/)"): print(url, pat) tokenize = re.compile(r'(\d+)|(\D+)').findall def natural_sortkey(string): return tuple(int(num) if num else alpha for num, alpha in tokenize(string)) n = None res = [] for l in urllib.urlopen(url).readlines(): search_res = re.finditer('(?i)<a\s+href="%s"\s*>' % pat, l) for m in search_res: if ".." not in m.group(0): res.append(m.group(1)) if res: res.sort(key=natural_sortkey) n=res[-1] return n def latest_mmotm(url, pat): for l in urllib.urlopen(url + "/broken-out/mm.patch").readlines(): m = re.search('-EXTRAVERSION = (\S+)', l) if m: n = m.group(1) latest = latest_dir(os.path.dirname(version_info['4-rc'][1]), ('patch-(.*%s).xz' % n)) return latest + "-mm1" def latest_ck(url, pat): url = "http://ck.kolivas.org/patches/3.0/" url += last(url,"(\d.*)/") part = fork(last(url,"(\d.*)/")) stable = latest_dir(os.path.dirname(version_info['3'][1][0]), version_info['3'][2]) return stable + part def latest_pf(url, pat): url = "http://pf.natalenko.name/sources/" url += last(url) part = last(url,"(.*patch.*)") print("Part: ", part) part = re.search('\d+(.\d+)+-pf\d+', part).group(0) return part def latest_dir_lt(url, pat): """Find the latest link to the stable release series that is used local""" cwd=os.getcwd() lv = None if os.path.isdir(options["directory"]): os.chdir(options["directory"]) try: lv = get_ver('Makefile') except: lv = None os.chdir(cwd) if not lv: qprint("No local version found. Use newest kernel release instead.") return latest_dir(url, pat) local_revbase = revbase(lv) p = [] url = url % { "kernel_url": kernel_url } for l in urllib.urlopen(url).readlines(): m = re.search('"%s"' % pat, l) if m and revbase(m.group(1))==local_revbase: p.append(m.group(1)) if not p: return None p.sort(compare_ver) return p[-1] def latest_rt(url, pat): """ Find the link to the latest rt kernel patch """ urls = [] url_parent = os.path.dirname(url) postfix = "" while '%' in url_parent[1:]: postfix = os.path.basename(url) + "/" + postfix url_parent = os.path.dirname(url_parent) if postfix: postfix = "/" + postfix url_parent = url_parent % { "kernel_url": kernel_url } if "2.6" in pat: m = last(url_parent, "(2\.6.*/)*") else: m = last(url_parent, "(\d.*/)") if not m: return None l = latest_dir(url_parent + '/' + m + postfix , pat) return l def latest_dir(url, pat): """Find the latest link matching pat at url after sorting""" p = [] url = url % { "kernel_url": kernel_url, "revbase":"" } for l in urllib.urlopen(url).readlines(): m = re.search('"%s"' % pat, l) if m: p.append(m.group(1)) if not p: return None p.sort(compare_ver) return p[-1] def find_info(ver): t = tree(ver) # 3.x and 2.x kernels must be handled differently if t >= 3: b = "%d" % t else: b = "%.1f" % t f = forkname(ver) p = pre(ver) s = b if f: s = "%s-%s" % (b, f) elif p: s = "%s-rc" % b return version_info[s] def version_urls(ver): """ Return the URL for the patch associated with the specified version """ i = find_info(ver)[1] if type(i) != type([]): i = [i] v = { 'full': ver, 'tree': tree(ver), 'base': base(ver), 'prebase': prebase(ver), 'fork': fork(ver), 'revbase': revbase(ver), 'kernel_url': kernel_url } l = [] for e in i: l.append(e % v) return l def patch_path(ver): patch_name = os.path.basename(version_urls(ver)[0]) if re.search(r'\d+\.\d+', patch_name) == None: suf = re.search(r'\.tar\.(gz|(bz(\d+)?))', patch_name).group(0) patch_name = re.sub(r'(\.tar\.(gz|(bz(\d+)?)))', "-%s%s" % (ver, suf), patch_name) return os.path.join(archive, patch_name) def download(url, f): qprint("Downloading %s" % os.path.basename(url)) if options["dry-run"]: return 1 if not options["wget"]: p = urllib.urlopen(url).read() if p.find("<title>404") != -1: return None open(f, 'w').write(p) else: e = os.system("%s -c -O %s %s" % (options["wget"], f + ".partial", url)) if e & 255: error("wget terminated by signal.") sys.exit(1) if e: try: # remove empty files if os.path.getsize(f+".partial")==0: os.unlink(f+".partial") except OSError: pass return None os.rename(f + ".partial", f) return 1 def check_if_gpg_key_available(url, f, sign): if options["no-gpg"] or options["dry-run"] or not options["gpg-path"]: return 1 qprint("Check if GPG key is available...") sf = f + sign if not download(url+sign, sf): sf = os.path.splitext(f)[0] + sign sign_url = os.path.splitext(url)[0] + sign if not download(sign_url, sf): error("signature download failed") return 0 process= subprocess.Popen([options["gpg-path"], "--no-tty", "--batch", "--verify",sf,sf], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output, unused_err = process.communicate() r = process.poll() if r == 2: # key is not available qprint(output) error("The GPG key seems not to be in the keyring. Please fix this and try again.") qprint("In the case potential malicious kernel code is not a problem,\n" "you can skip the verifying by using --no-gpg.") return -1 if r < 0: # killed by signal return -1 return 1 def verify(url, f, sign): if options["no-gpg"] or options["dry-run"] or not options["gpg-path"]: return 1 need_extract=False sf = f + sign sign_url = url + sign if not os.path.isfile(sf) and not download(sign_url, sf): sf = os.path.splitext(f)[0] + sign sign_url = os.path.splitext(url)[0] + sign need_extract=True if not os.path.isfile(sf) and not download(sign_url, sf): error("signature download failed") error("removing files...") os.unlink(sf) return 0 qprint("Verifying signature...") if need_extract: if f[-4:] == ".bz2": r = os.system("bzcat %s | %s --verify %s -" % (f,options["gpg-path"], sf)) elif f[-3:] == ".gz": r = os.system("zcat %s | %s --verify %s -" % (f,options["gpg-path"], sf)) elif f[-3:] == ".xz": r = os.system("xzcat %s | %s --verify %s -" % (f,options["gpg-path"], sf)) else: error("Cannot check signature: Unknown compression algorithm") error("removing files...") os.unlink(f) os.unlink(sf) return 1 else: r = os.system("%s --verify %s %s" % (options["gpg-path"], sf, f)) if r: error("gpg returned %d" % r) error("removing files...") os.unlink(f) os.unlink(sf) return 0 return 1 def trydownload(urls, f, sign): for url in urls: if sign: result=check_if_gpg_key_available(url, f, sign) if result < 0: # gpg key not available return None elif result==0: # download failed continue if os.path.isfile(f): if not sign or verify(url, f, sign): return f if url[-4:] == ".bz2": try_exts=['.xz', '.bz2', '.gz', ''] for ext in try_exts: f2=f[:-4]+ext if os.path.isfile(f2): if not sign or verify(url, f2, sign): return f2 for ext in try_exts: f2=f[:-4]+ext url2 = url[:-4] + ext if download(url2, f2): if not sign or verify(url2, f2, sign): return f2 elif download(url, f): if not sign or verify(url, f, sign): return f return None 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 if re.search(r'-mm1', ver) != None: if pre(find_ver('3-rc')) != pre(ver): error("-mm1 patchset can only be applied to the latest -rc release!") sys.exit(-1) if f[-4:] == ".bz2": f2 = f[:-4] + ".xz" if os.path.exists(f2): return f2 f2 = f[:-4] + ".gz" if os.path.exists(f2): return f2 f2 = f[:-4] if os.path.exists(f2): return f2 urls = version_urls(ver) sign = find_info(ver)[3] if sign == 1: sign = ".sign" f = trydownload(urls, f, sign) if not f: error("patch download failed") sys.exit(-1) return f def apply_mmotm(ver, reverse = 0): if reverse: err = os.system("quilt pop -a &> .patchdiag") if err: sys.stderr.write(open(".patchdiag").read()) os.unlink(".patchdiag") os.system("rm -rf patches .pc Next") else: old = os.getcwd() os.mkdir("patches") os.chdir("patches") err = os.system("zcat %s | tar -xf -" % patch_path(ver)) if err: error("Unpacking failed: ", err) os.chdir(old) os.system("rm -rf patches") sys.exit(-1) os.system("mv broken-out/*.patch ./") os.chdir(old) err = os.system("quilt push -a --leave-rejects &> .patchdiag") if err: sys.stderr.write(open(".patchdiag").read()) os.unlink(".patchdiag") return err 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" qprint("Applying %s%s" % (os.path.basename(p), r)) if options["dry-run"] or options["only-dl"]: return ver if forkname(ver) == "mm": return apply_mmotm(ver, reverse) def cmd(patch, reverse, dry): base = "patch -l -p1%s" % reverse if dry: base += " --dry-run" if p[-4:] == ".bz2": pipe = "bzcat %s | %s" % (patch, base) elif p[-3:] == ".gz": pipe = "zcat %s | %s" % (patch, base) elif p[-3:] == ".xz": pipe = "xzcat %s | %s" % (patch, base) else: pipe = "%s < %s" % (base, patch) err = os.system(pipe + " > .patchdiag") if err: sys.stderr.write(open(".patchdiag").read()) os.unlink(".patchdiag") return err err = cmd(p, r, 1) if err: if os.WIFSIGNALED(err): error("patch %s failed: killed with signal %d" % (p, os.WTERMSIG(err))) else: error("patch %s failed: return value %d" % (p, os.WEXITSTATUS(err))) sys.exit(-1) err = cmd(p, r, 0) if err: if os.WIFSIGNALED(err): error("patch %s failed while it was supposed to apply: signal: %d" % (p, os.WTERMSIG(err))) else: error("patch %s failed while it was supposed to apply: %d" % (p, os.WEXITSTATUS(err))) sys.exit(-1) def untar(tarfile): old = os.getcwd() if os.path.exists("ketchup-tmp"): error("Please remove the ketchup-tmp directory by hand.") sys.exit(-1) os.mkdir("ketchup-tmp") try: os.chdir("ketchup-tmp") if tarfile[-4:] == ".bz2": err = os.system("tar -jxf %s" % tarfile) elif tarfile[-3:] == ".gz": err = os.system("tar -zxf %s" % tarfile) elif tarfile[-3:] == ".xz": err = os.system("xzcat %s | tar -xf -" % tarfile) elif tarfile[-4:] == ".tar": err = os.system("tar -xf %s" % tarfile) else: err="Unknown compression algorithm." if err: error("Unpacking failed: ", err) sys.exit(-1) ldir = glob.glob("linux*")[0] for f in os.listdir(ldir): os.rename(ldir + "/" + f, "../" + f) finally: os.chdir(old) shutil.rmtree("ketchup-tmp") def install_nearest(ver): t = tree(ver) tarballs = glob.glob(archive + "/linux-%s.*.tar.xz" % t) list = [] for f in tarballs: m = re.match(r'.*/linux-(.*).tar.xz$', f) v = m.group(1) d = abs(rev(v) - rev(ver)) list.append((d, f, v)) list.sort() # ugly hack, upstream dir is /v3.x (not v3) # FIXME: we assume that this will be the case for linux-4.0 and later releases # if not, we'll have to fix it again :) if t >= 3: t = "%s.x" % t if not list or (options["full-tarball"] and list[0][0]): f = "linux-%s.tar.xz" % ver url = "%s/v%s/%s" % (kernel_url, t, f) url_longterm = "%s/v%s/longterm/v%s/%s" % (kernel_url, t, revbase(ver), f) f = archive + "/" + f sign = find_info(ver)[3] if sign == 1: sign = ".sign" f = trydownload([url, url_longterm], f, sign) if not f: error("Tarball download failed") sys.exit(-1) else: f = list[0][1] ver = list[0][2] qprint("Unpacking %s" % os.path.basename(f)) if options["dry-run"] or options["only-dl"]: return ver untar(f) return ver def find_ver(ver): if ver in version_info.keys(): v = version_info[ver] d = v[1] if not type(d) is type([]): d = [d] for n in d: r = v[0](os.path.dirname(n), v[2]) if r: return r else: return ver def transform(a, b): if a == b: qprint("Nothing to do!") return if not b: error("No version found.") sys.exit(-1) if not a: a = install_nearest(base(b)) t = tree(a) # 2.4 -> >=2.6 and >=2.6 -> 2.4 are not supported if (t ==2.4 and tree(b) != 2.4) or (tree(b) == 2.4 and t != 2.4): error("Can't patch %s to %s" % (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) if post(a) and (post(a) != post(b) or rev(a) != rev(b)): apply_patch(prebase(a), 1) # handle the transition from 2.6.39 to 3.0 ra, rb = rev(a), rev(b) if tree(a) == tree(b) and ra > rb: for r in range(ra, rb, -1): apply_patch("%s.%s" % (tree(a), r), -1) if tree(a) == tree(b) and ra < rb: for r in range(ra + 1, rb + 1): apply_patch("%s.%s" % (tree(b), r)) if tree(a) > tree(b): for r in range(ra, -1, -1): apply_patch("%s.%s" % (tree(a), r), -1) if tree(a)==2.6: for r in range(39, rb, -1): apply_patch("%s.%s" % (tree(b), r), -1) else: for r in range(19, rb, -1): apply_patch("%s.%s" % (tree(b), r), -1) if tree(b) > tree(a): if tree(a)==2.6: for r in range(ra + 1, 40): apply_patch("%s.%s" % (tree(a), r)) if rb != 39: for r in range(0, rb + 1, 1): apply_patch("%s.%s" % (tree(b), r)) else: for r in range(ra + 1, 20): apply_patch("%s.%s" % (tree(a), r)) if rb != 19: for r in range(0, rb + 1, 1): apply_patch("%s.%s" % (tree(b), r)) a = revbase(b) if post(b) and post(a) != post(b): apply_patch(prebase(b), 0) a = base(b) if pre(b): apply_patch(prebase(b)) a = prebase(b) if fork(b): a = apply_patch(b) def rename_dir(v): """Rename the current directory to linux-v, where v is the function arg""" if rename_with_localversion: v += get_localversion() cwd = os.getcwd() basedir = os.path.dirname(cwd) newdir = os.path.join(basedir, rename_prefix + v) if newdir == cwd: return if os.access(newdir, os.F_OK): error("Cannot rename directory, destination exists: %s", newdir); return os.rename(cwd, newdir) qprint('Current directory renamed to %s' % newdir) # latest lookup function, canonical urls, pattern for lookup function, # signature flag, description version_info = { '2.4': (latest_dir, "%(kernel_url)s" + "/v2.4" + "/patch-%(base)s.bz2", r'patch-(.*?).bz2', 1, "2.4 kernel series"), '2.6': (latest_dir, ["%(kernel_url)s" + "/v2.6" + "/patch-%(prebase)s.bz2", "%(kernel_url)s" + "/v2.6/longterm/v%(revbase)s/patch-%(prebase)s.bz2"], r'linux-(.*?).tar.bz2', 1, "2.6 kernel series"), '2.6-lt': (latest_dir_lt, ["%(kernel_url)s" + "/v2.6" + "/patch-%(prebase)s.bz2", "%(kernel_url)s" + "/v2.6/longterm/v%(revbase)s/patch-%(prebase)s.bz2"], r'patch-(.*?).bz2', 1, "2.6 kernel series - update (only) to newer longterm stable releases (fourth number of 2.6 kernels)"), '2.6-rc': (latest_dir, "%(kernel_url)s" + "/v2.6" + "/testing/patch-%(prebase)s.bz2", r'linux-(.*?).tar.xz', 1, "2.6 kernel series prereleases"), '2.6-rt': (latest_rt, ["%(kernel_url)s" + "/projects/rt/%(revbase)s/patch-%(full)s.bz2", "%(kernel_url)s" + "/projects/rt/%(revbase)s/older/patch-%(full)s.bz2"], r'patch-(2.6.*?).bz2', 1, "Ingo Molnar's realtime-preempt kernel"), '2.6-pf': (latest_pf, "http://pf.natalenko.name/sources" + "/%(revbase)s" + "/patch-%(full)s.bz2", r'patch-(.*?).bz2', 0, "-pf kernel patchset"), '3-pf': (latest_pf, "http://pf.natalenko.name/sources" + "/%(revbase)s" + "/patch-%(full)s.bz2", r'patch-(.*?).bz2', 0, "-pf kernel patchset"), '4-pf': (latest_pf, "http://pf.natalenko.name/sources" + "/%(revbase)s" + "/patch-%(full)s.bz2", r'patch-(.*?).bz2', 0, "-pf kernel patchset"), '3-next': (latest_dir, "%(kernel_url)s" + "/next" + "/patch-v%(prebase)s%(fork)s.xz", r'patch-v(.*?).xz', 1, "3 linux-next tree"), '3-rc': (latest_dir, "%(kernel_url)s" + "/v3.x" + "/testing/patch-%(prebase)s.xz", r'patch-(.*?).xz', 1, "3.x stable kernel series prereleases"), # current -next releases, for 4.x kernels '4-next': (latest_dir, "%(kernel_url)s" + "/next" + "/patch-v%(prebase)s%(fork)s.xz", r'patch-v(.*?).xz', 1, "4 linux-next tree"), '4-mm': (latest_mmotm, "http://ozlabs.org/~akpm/mmotm/broken-out.tar.gz", r'broken-out.tar.gz', 0, "-mmotm quilt patchset"), '4-rc': (latest_dir, "%(kernel_url)s" + "/v4.x" + "/testing/patch-%(prebase)s.xz", r'patch-(.*?).xz', 1, "current stable kernel series prereleases"), '3-rt': (latest_rt, ["%(kernel_url)s" + "/projects/rt/%(revbase)s/patch-%(full)s.patch.xz", "%(kernel_url)s" + "/projects/rt/%(revbase)s/older/patch-%(full)s.patch.xz", ], r'patch-(3(\.\d+)*-rt\d+).patch.xz', # TODO: add git-based releases 1, "PREEMPT_RT real-time kernel (only based directly on stable and -rc kernels.)"), '3-ck': (latest_ck, "http://ck.kolivas.org/patches/3.0/" + "/%(revbase)s/%(revbase)s%(fork)s/patch-%(revbase)s%(fork)s.xz", "", 0, "Con Kolivas' -ck patchset"), '4-rt': (latest_rt, ["%(kernel_url)s" + "/projects/rt/%(revbase)s/patch-%(full)s.patch.xz", "%(kernel_url)s" + "/projects/rt/%(revbase)s/older/patch-%(full)s.patch.xz", ], r'patch-([456](\.\d+)*-rt\d+).patch.xz', # TODO: add git-based releases 1, "PREEMPT_RT real-time kernel (only based directly on stable and -rc kernels.)"), '3-lt': (latest_dir_lt, "%(kernel_url)s" + "/v3.x" + "/patch-%(prebase)s.xz", r'patch-(.*?).xz', 1, "3.x kernel series - update (only) to newer longterm stable releases (third number of 3.x kernels)"), '4-lt': (latest_dir_lt, "%(kernel_url)s" + "/v4.x" + "/patch-%(prebase)s.xz", r'patch-(.*?).xz', 1, "4.x kernel series - update (only) to newer longterm stable releases (third number of 4.x kernels)"), '3': (latest_dir, ["%(kernel_url)s" + "/v3.x" + "/patch-%(prebase)s.xz"], r'patch-(.*?).xz', 1, "last stable kernel series"), '4': (latest_dir, ["%(kernel_url)s" + "/v4.x" + "/patch-%(prebase)s.xz"], r'patch-(.*?).xz', 1, "current stable kernel series"), '5': (latest_dir, ["%(kernel_url)s" + "/v5.x" + "/patch-%(prebase)s.xz"], r'patch-(.*?).xz', 1, "current stable kernel series"), } # Override defaults with ~/.ketchuprc which is just a Python script rcpath = os.path.expanduser('~/.ketchuprc') if os.path.isfile(rcpath): try: execfile(rcpath) except Exception as e: sys.exit('Failed parsing %s\nError was: %s' % (rcpath, e)) # Add local trees for k,v in local_trees.items(): version_info[k] = v # Environment variables override defaults and ketchuprc kernel_url = os.environ.get("KETCHUP_URL", kernel_url) archive = os.environ.get("KETCHUP_ARCH", archive) # And finally command line overrides everything if not os.path.exists(wget): wget = "" if not os.path.exists(gpg): gpg = "" options = {} opts = [ ('a', 'archive', archive, 'cache directory'), ('d', 'directory', '.', 'directory to update'), ('f', 'full-tarball', None, 'if unpacking a tarball, download the latest'), ('g', 'gpg-path', gpg, 'path for GnuPG'), ('G', 'no-gpg', None, 'disable GPG signature verification'), ('k', 'kernel-url', kernel_url, 'base url for kernel.org mirror'), ('l', 'list-trees', None, 'list supported trees'), ('m', 'show-makefile', None, 'output version in makefile <arg>'), ('n', 'dry-run', None, 'don\'t download or apply patches'), ('o', 'only-dl', None, 'don\'t apply patches'), ('p', 'show-previous', None, 'output version previous to <arg>'), ('q', 'quiet', None, 'reduce output'), ('r', 'rename-directory', None, 'rename updated directory to %s<v>' % rename_prefix), ('s', 'show-latest', None, 'output the latest version of <arg>'), ('u', 'show-url', None, 'output URL for <arg>'), ('w', 'wget', wget, 'command to use for wget'), ] args = fancyopts(sys.argv[1:], opts, options, 'ketchup [options] [ver]') archive = options["archive"] kernel_url = options["kernel-url"] if options["no-gpg"]: options["gpg-path"] = '' # Process args if not os.path.exists(options["directory"]): qprint("Creating target directory", options["directory"]) os.mkdir(options["directory"]) os.chdir(options["directory"]) if os.path.isfile(".ketchuprc"): try: execfile(".ketchuprc") except Exception as e: sys.exit('Failed parsing .ketchuprc\nError was: %s' % (e)) if options["list-trees"]: l = version_info.keys() l.sort() for tree in l: if version_info[tree][3] == 0: lprint(tree, "(unsigned)") else: lprint(tree, "(signed)") lprint(" " + version_info[tree][4]) sys.exit(0) if options["show-makefile"] and len(args) < 2: if not args: lprint(get_ver("Makefile")) else: lprint(get_ver(args[0])) sys.exit(0) if len(args) == 0 and default_tree: qprint("Using default tree \"%s\"" % (default_tree)) args.append(default_tree) if len(args) != 1: error("No version given on command line and no default in configuration") sys.exit(-1) if options["show-latest"]: lprint(find_ver(args[0])) sys.exit(0) if options["show-url"]: lprint(version_urls(find_ver(args[0]))[0]) sys.exit(0) if options["show-previous"]: v = find_ver(args[0]) p = prebase(v) if p == v: p = base(v) if p == v: if rev(v) > 0: p = "%.1f.%s" % (tree(v), rev(v) -1) else: p = "unknown" lprint(p) sys.exit(0) if not os.path.exists(options["archive"]): qprint("Creating cache directory", options["archive"]) os.mkdir(options["archive"]) if precommand and os.system(precommand): sys.exit('Precommand "%s" failed!' % precommand) try: a = get_ver('Makefile') except: a = None if not a and os.listdir("."): error("Can't find kernel version for non-empty directory") sys.exit(-1) b = find_ver(args[0]) qprint("%s -> %s" % (a, b)) transform(a, b) if options["rename-directory"] and not options["dry-run"] and not options["only-dl"] : rename_dir(b) if postcommand and os.system(postcommand): sys.exit('Postcommand "%s" failed!' % postcommand)