#!/usr/bin/env python # # grsup: this file is part of the GRS suite # Copyright (C) 2015 Anthony G. Basile # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import copy import glob import filecmp import os import re import shutil import signal import sys import urllib.request from getopt import gnu_getopt, GetoptError from html.parser import HTMLParser from grs import CONST from grs import Execute from grs import Log from grs import Synchronize from grs import WorldConf from _emerge.main import emerge_main, parse_opts from portage.exception import IsADirectory, ParseError, PermissionDenied from portage import settings def install_kernel(version = 'latest', logfile = CONST.LOGFILE): """ doc here more doc """ class MyHTMLParser(HTMLParser): def __init__(self, **kwargs): HTMLParser.__init__(self, **kwargs) self.kernels = [] def handle_starttag(self, tag, attrs): if tag != 'a': return for attr in attrs: if attr[0] == 'href' and re.match('linux-image-', attr[1]): self.kernels.append(attr[1]) def get_kernels(self): return self.kernels baseurl = settings['PORTAGE_BINHOST'] if baseurl == '': print('PORTAGE_BINHOST is not set. Install kernel manually.') return try: request = urllib.request.urlopen('%s/%s' % (baseurl,'linux-images')) dload = request.read().decode('utf-8') except urllib.error.HTTPError: print('Cannot open %s' % baseurl) return parser = MyHTMLParser() parser.feed(dload) kernels = parser.get_kernels() kernels.sort() if version == 'latest': try: kernel = kernels[-1] except IndexError: print('No linux-image available') return else: for k in kernels: m = re.search('linux-image-(.+).tar.xz', k) if m.group(1) == version: kernel = k break else: print('No linux-image %s available' % version) return # Download the linux-image tarball to packages/linux-image request = urllib.request.urlopen('%s/%s/%s' % (baseurl, 'linux-images', kernel)) package = '/usr/portage/packages/linux-images' os.makedirs(package, mode=0o755, exist_ok=True) kpath = os.path.join(package, kernel) with open(kpath, 'wb') as f: shutil.copyfileobj(request, f) # Try to mount /boot. Fail silently since it may not be mountable. if not os.path.ismount('/boot'): cmd = 'mount /boot' Execute(cmd, timeout=60, failok=True, logfile=logfile) # Untar it at '/'. tar will not clobber files. cwd = os.getcwd() os.chdir('/') cmd = 'tar --overwrite -Jxf %s' % kpath Execute(cmd, timeout=600, logfile=logfile) os.chdir(cwd) def usage(rc=1, extra=""): use = """ usage: grsup [-l] [pkg(s)] : update @world or pkg(s) if given : prefer binpkgs unless -l is given grsup [-l] -r pkg(s) : re-install pkg(s) : prefer binpkgs unless -l is given grsup -d pkg(s) : delete pkg(s) grsup -D : download all @world pkgs, don't install grsup -k : install kernel or 'latest' grsup -h : print this help """ if extra: print('\nInvalid combination of parameters: %s' % extra) print(use) sys.exit(rc) def sanitize(opts, x): lopt = ('-l','') singleflags = [ '-d', '-D', '-k', '-h' ] noargsflags = [ '-D', '-k', '-h' ] for o, a in opts: if o in singleflags and len(opts) > 1: usage(extra=' '.join(sys.argv[1:])) if o in noargsflags and len(x) > 0: usage(extra=' '.join(sys.argv[1:])) if ( o == '-r' or o == '-d') and len(x) == 0: usage(extra=' '.join(sys.argv[1:])) if o == '-r' and len(opts) > 2: usage(extra=' '.join(sys.argv[1:])) if o == '-r' and len(opts) == 2 and not lopt in opts: usage(extra=' '.join(sys.argv[1:])) def main(): myaction, myopts, myfiles = parse_opts(sys.argv[1:]) try: opts, x = gnu_getopt(sys.argv[1:], 'lDk:rdh') sanitize(opts, x) except GetoptError: usage() do_local = False lopt = ('-l','') if lopt in opts: do_local = True opts.remove(lopt) do_install_kernel = False if len(opts) == 0: args = ['-1', '-g', '-K', '-u', '-D', '-q'] if len(myfiles) == 0: myfiles = ['@world'] args.extend(myfiles) else: for o, a in opts: if o == '-h': usage(rc=0) elif o == '-r': args = ['-1', '-g', '-K', '-D', '-q'] args.extend(myfiles) elif o == '-d': args = ['-C', '-q'] args.extend(myfiles) elif o == '-D': args = ['-g', '-e', '-f', '-q', '@world'] elif o == '-k': version = a do_install_kernel = True if do_local: args.remove('-g') args.remove('-K') if len(CONST.names) > 1: sys.stderr.write('More than one GRS specified in systems.conf. Using the first one.\n') name = CONST.names[0] repo_uri = CONST.repo_uris[0] stage_uri = CONST.stage_uris[0] libdir = CONST.libdirs[0] logfile = CONST.logfiles[0] # Change the log name for the client. basename = os.path.basename(logfile) dirname = os.path.dirname(logfile) logfile = os.path.join(dirname, 'grsup-%s' % basename) Log(logfile).rotate_logs() Synchronize(repo_uri, name, libdir, logfile).sync() # Copy the new world.conf to CONST.WORLD_CONFIG newconf = '%s/core%s' % (libdir, CONST.WORLD_CONFIG) shutil.copy(newconf, CONST.WORLD_CONFIG) # Copy the new make.conf to CONST.PORTAGE_CONFIGDIR # If a raw new make.conf exists, pick it, else pick the highest cycle no. newmakeconf = os.path.join(libdir, 'core/etc/portage/make.conf') oldmakeconf = os.path.join(CONST.PORTAGE_CONFIGDIR, 'make.conf') do_copy = False if os.path.isfile(newmakeconf): do_copy = True else: cycled_files = {} for f in glob.glob('%s.*' % newmakeconf): m = re.search('^(.+)\.CYCLE\.(\d+)', f) if m: cycle_no = int(m.group(2)) cycled_files[cycle_no] = m.group(0) try: max_cycle_no = max(cycled_files) newmakeconf = cycled_files[max_cycle_no] do_copy = True except ValueError: # thrown by max() if cycled_files is empty pass if do_copy: if os.path.isfile(oldmakeconf): if not filecmp.cmp(newmakeconf, oldmakeconf): print('New make.conf differs from local version. Backing up as make.conf.old') shutil.copy(oldmakeconf, '%s.old' % oldmakeconf) shutil.copy(newmakeconf, oldmakeconf) # 1. Install all world.conf files. # 2. Do the emerge. # 3. Cleanup unused /etc/portage files. open(CONST.PORTAGE_DIRTYFILE, 'a').close() WorldConf.install() if do_install_kernel: install_kernel(version=version) else: try: emerge_main(args) except PermissionDenied as e: sys.stderr.write("Permission denied: '%s'\n" % str(e)) sys.stderr.flush() sys.exit(e.errno) except IsADirectory as e: sys.stderr.write("'%s' is a directory, but should be a file!\n" % sys.exit(e.errno)) sys.stderr.flush() except ParseError as e: sys.stderr.write("%s\n" % str(e)) sys.stderr.flush() sys.exit(1) WorldConf.clean() if os.path.exists(CONST.PORTAGE_DIRTYFILE): os.remove(CONST.PORTAGE_DIRTYFILE) if __name__ == "__main__": try: main() except KeyboardInterrupt: sys.stderr.write("Cleaning up /etc/portage. This make take some time.\n") WorldConf.clean() if os.path.exists(CONST.PORTAGE_DIRTYFILE): os.remove(CONST.PORTAGE_DIRTYFILE)