#!/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 = '/var/cache/binpkgs/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 -hJxf %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', '--with-bdeps=y']
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', '--with-bdeps=y']
args.extend(myfiles)
elif o == '-d':
args = ['-C', '-q']
args.extend(myfiles)
elif o == '-D':
args = ['-g', '-e', '-f', '-q', '--with-bdeps=y', '@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)