mirror of
https://gitdl.cn/https://github.com/chakralinux/core.git
synced 2025-02-05 14:57:15 +08:00
460 lines
13 KiB
Python
460 lines
13 KiB
Python
# backend.py - for the cli modules: handles processes and io
|
|
#
|
|
# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
|
|
#
|
|
# This file is part of the larch project.
|
|
#
|
|
# larch 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 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
|
|
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
#
|
|
#----------------------------------------------------------------------------
|
|
# 2010.11.09
|
|
|
|
# There was also the vague idea of a web interface, using a sort of state-
|
|
# based approach. Connecting to a running larch process would then require
|
|
# the ability to get the logging history, but presumably not the whole
|
|
# history on every ui update, which would need to be incremental.
|
|
# The logging function would need to be modified to accommodate this.
|
|
|
|
import os, sys, signal, atexit, __builtin__
|
|
import traceback, pwd
|
|
from subprocess import Popen, PIPE, STDOUT
|
|
import pexpect
|
|
try:
|
|
import json as serialize
|
|
except:
|
|
import simplejson as serialize
|
|
from config import *
|
|
|
|
def debug(text):
|
|
sys.stderr.write("DEBUG: " + text.strip() + "\n")
|
|
sys.stderr.flush()
|
|
|
|
sys.path.append(os.path.dirname(base_dir))
|
|
from liblarch.translation import i18n_module, lang
|
|
__builtin__._ = i18n_module(base_dir, 'larch')
|
|
__builtin__.lang = lang
|
|
# Run subprocesses without i18n in case the output is parsed.
|
|
os.environ["LANGUAGE"] = "C"
|
|
|
|
|
|
def init(app, options, app_quit=None):
|
|
global _options, _quit_function, _log, _controlled, _dontask, _quiet
|
|
_options = options
|
|
_quit_function = app_quit
|
|
_controlled = options.slave
|
|
_dontask = options.force
|
|
_log = None
|
|
_quiet = False if _controlled else options.quiet
|
|
|
|
atexit.register(sys_quit)
|
|
if _controlled:
|
|
_out('>-_$$_%d' % os.getpid())
|
|
|
|
def sigint(num, frame):
|
|
"""A handler for SIGINT. Tidy up properly and quit.
|
|
"""
|
|
errout("INTERRUPTED - killing subprocesses", 0)
|
|
if _sub_process and _sub_process.pid:
|
|
Popen(["pkill", "-g", str(_sub_process.pid)],
|
|
stdout=PIPE).communicate()
|
|
errout("QUITTING", 2)
|
|
signal.signal(signal.SIGINT, sigint)
|
|
|
|
|
|
# Check no other instance of the script is running
|
|
if os.path.isfile(LOCKFILE):
|
|
app0 = readfile(LOCKFILE)
|
|
if not query_yn(_(
|
|
"larch (%s) seems to be running already."
|
|
"\nIf you are absolutely sure this is not the case,"
|
|
"\nyou may continue. Otherwise you should cancel."
|
|
"\n\nShall I continue?") % app0):
|
|
sys.exit(102)
|
|
writefile(app, LOCKFILE)
|
|
_log = open(LOGFILE + app, 'w')
|
|
|
|
# For systems without /sbin and /usr/sbin in the normal PATH
|
|
p = os.environ['PATH']
|
|
ps = p.split(':')
|
|
for px in ('/sbin', '/usr/sbin'):
|
|
if px not in ps:
|
|
p = px + ':' + p
|
|
os.environ['PATH'] = p
|
|
|
|
|
|
def _out(text, force=False):
|
|
"""Send the string to standard output.
|
|
How it is output depends on the '-s' command line option (whether the
|
|
script is being run on the console or as a subprocess of another script).
|
|
In the latter case the text will be slightly encoded - to avoid newline
|
|
characters - and sent as a single unit.
|
|
Otherwise output the lines as they are, but all lines except
|
|
the first get a '--' prefix.
|
|
"""
|
|
lines = text.encode('utf-8').splitlines()
|
|
if _log and not text.startswith('>-'):
|
|
# Don't log the progress report lines
|
|
_log.write(lines[0] + '\n')
|
|
for l in lines[1:]:
|
|
_log.write('--' + l + '\n')
|
|
|
|
if force or not _quiet:
|
|
if _controlled:
|
|
sys.stdout.write(serialize.dumps(text) + '\n')
|
|
else:
|
|
prefix = ''
|
|
for line in lines:
|
|
sys.stdout.write(prefix + line + '\n')
|
|
prefix = '--'
|
|
sys.stdout.flush()
|
|
|
|
|
|
def sys_quit():
|
|
unmount()
|
|
if _quit_function:
|
|
_quit_function()
|
|
if _errorcount:
|
|
_out('!! ' + (_("The backend reported %d failed calls,"
|
|
" you may want to investigate") % _errorcount))
|
|
if _log:
|
|
_log.close()
|
|
os.remove(LOCKFILE)
|
|
|
|
|
|
def comment(text):
|
|
_out('##' + text)
|
|
|
|
|
|
def query_yn(message):
|
|
_out('?>' + message)
|
|
if _dontask:
|
|
result = True
|
|
|
|
elif _controlled:
|
|
result = (raw_input().strip() == '??YES')
|
|
|
|
else:
|
|
prompt = _("Yes:y|No:n")
|
|
py, pn = prompt.split('|')
|
|
respy = py.lower().split(':')
|
|
respn = pn.lower().split(':')
|
|
while True:
|
|
resp = raw_input(" [ %s ]: " % prompt).strip().lower()
|
|
if resp in respy:
|
|
result = True
|
|
break
|
|
if resp in respn:
|
|
result = False
|
|
break
|
|
|
|
_out('#>%s' % ('Yes' if result else 'No'))
|
|
return result
|
|
|
|
|
|
def errout(message="ERROR", quit=1):
|
|
_out('!>' + message, True)
|
|
if quit:
|
|
sys_quit()
|
|
os._exit(quit)
|
|
|
|
|
|
def error0(message):
|
|
errout(message, 0)
|
|
__builtin__.error0 = error0
|
|
|
|
|
|
# Catch all unhandled errors.
|
|
def errortrap(type, value, tb):
|
|
etext = "".join(traceback.format_exception(type, value, tb))
|
|
errout(_("Something went wrong:\n") + etext, 100)
|
|
sys.excepthook = errortrap
|
|
|
|
|
|
_sub_process = None
|
|
_errorcount = 0
|
|
def runcmd(cmd, filter=None):
|
|
global _sub_process, _errorcount
|
|
_out('>>' + cmd)
|
|
_sub_process = pexpect.spawn(cmd)
|
|
result = []
|
|
line0 = ''
|
|
# A normal end-of-line is '\r\n', so split on '\r' but don't
|
|
# process a line until the next character is available.
|
|
while True:
|
|
try:
|
|
line0 += _sub_process.read_nonblocking(size=256, timeout=None)
|
|
except:
|
|
break
|
|
|
|
while True:
|
|
lines = line0.split('\r', 1)
|
|
if (len(lines) > 1) and lines[1]:
|
|
line = lines[0]
|
|
line0 = lines[1]
|
|
nl = (line0[0] == '\n')
|
|
if nl:
|
|
# Strip the '\n'
|
|
line0 = line0[1:]
|
|
if filter:
|
|
nl, line = filter(line, nl)
|
|
if line == '/*/':
|
|
continue
|
|
if nl:
|
|
line = line.rstrip()
|
|
_out('>_' + line)
|
|
result.append(line)
|
|
else:
|
|
# Probably a progress line
|
|
if _controlled:
|
|
_out('>-' + line)
|
|
else:
|
|
sys.stdout.write(line + '\r')
|
|
sys.stdout.flush()
|
|
|
|
else:
|
|
break
|
|
|
|
_sub_process.close()
|
|
rc = _sub_process.exitstatus
|
|
ok = (rc == 0)
|
|
if not ok:
|
|
_errorcount += 1
|
|
_out(('>?%s' % repr(rc)) + ('' if ok else (' $$$ %s $$$' % cmd)))
|
|
return (ok, result)
|
|
|
|
|
|
def script(cmd):
|
|
s = runcmd("%s/%s" % (script_dir, cmd))
|
|
if s[0]:
|
|
return ""
|
|
else:
|
|
return "SCRIPT ERROR: (%s)\n" % cmd + "".join(s[1])
|
|
|
|
|
|
def chroot(ip, cmd, mnts=[], filter=None):
|
|
if ip:
|
|
for m in mnts:
|
|
mdir = "%s/%s" % (ip, m)
|
|
if not os.path.isdir(mdir):
|
|
runcmd('mkdir -p %s' % mdir)
|
|
mount("/" + m, mdir, "--bind")
|
|
cmd = "chroot %s %s" % (ip, cmd)
|
|
|
|
s = runcmd(cmd, filter)
|
|
|
|
if ip:
|
|
unmount(["%s/%s" % (ip, m) for m in mnts])
|
|
|
|
if s[0]:
|
|
if s[1]:
|
|
return s[1]
|
|
else:
|
|
return True
|
|
return False
|
|
|
|
|
|
_mounts = []
|
|
def mount(src, dst, opts=""):
|
|
if runcmd("mount %s %s %s" % (opts, src, dst))[0]:
|
|
_mounts.append(dst)
|
|
return True
|
|
return False
|
|
|
|
|
|
def unmount(dst=None):
|
|
if dst == None:
|
|
mnts = list(_mounts)
|
|
elif type(dst) in (list, tuple):
|
|
mnts = list(dst)
|
|
else:
|
|
mnts = [dst]
|
|
|
|
r = True
|
|
for m in mnts:
|
|
if runcmd("umount %s" % m)[0]:
|
|
_mounts.remove(m)
|
|
else:
|
|
r = False
|
|
return r
|
|
|
|
|
|
def get_installation_dir():
|
|
return os.path.realpath(_options.idir if _options.idir
|
|
else INSTALLATION)
|
|
|
|
|
|
def get_profile():
|
|
"""Get the absolute path to the profile folder given its path in any
|
|
acceptable form, including 'user:profile-name'
|
|
"""
|
|
pd = (_options.profile if _options.profile
|
|
else base_dir + '/profiles/default')
|
|
p = pd.split(':')
|
|
if len(p) == 1:
|
|
pd = os.path.realpath(pd)
|
|
else:
|
|
try:
|
|
pd = (pwd.getpwnam(p[0])[5] + PROFILE_DIR
|
|
+ '/' + p[1])
|
|
except:
|
|
errout(_("Invalid profile: %s") % pd, quit=0)
|
|
raise
|
|
if not os.path.isfile(pd + '/addedpacks'):
|
|
errout(_("Invalid profile folder: %s") % pd)
|
|
return pd
|
|
|
|
|
|
|
|
#+++++++++++++++++++++++++++++++++++++++++
|
|
#Regular expression search strings for progress reports
|
|
import re
|
|
#lit: give []() a \-prefix
|
|
#grp: surround string in ()
|
|
#opt: surround string in []
|
|
|
|
def _lit(s):
|
|
for c in r'[()]':
|
|
s = s.replace(c, '\\' + c)
|
|
return s
|
|
|
|
def _grp(s, x=''):
|
|
return '(' + s + ')' + x
|
|
|
|
def _grp0(s, x=''):
|
|
return '(?:' + s + ')' + x
|
|
|
|
def _opt(s, x=''):
|
|
return '[' + s + ']' + x
|
|
|
|
re_psub = re.compile(r'\[[#-]+\]')
|
|
_re_pacman = re.compile( _grp0(_lit('(') +
|
|
_grp(_opt('^/', '+') + '/' + _opt('^)', '+')) +
|
|
_lit(')'), '?') +
|
|
_grp('.*?') +
|
|
_lit('[') + _grp(_opt('-#', '+')) + _lit(r']\s+') +
|
|
_grp(_opt('0-9', '+')) +
|
|
'%'
|
|
)
|
|
|
|
_re_mksquashfs = re.compile(_lit('[.*]') +
|
|
_grp('.* ' +
|
|
_grp(_opt('0-9', '+')) +
|
|
'%')
|
|
)
|
|
|
|
_re_mkisofs = re.compile(_opt(' 1') + _opt(' \d') + '\d\.\d\d%')
|
|
|
|
#-----------------------------------------
|
|
class pacman_filter_gen:
|
|
"""Return a function to detect and process the progress output of
|
|
pacman.
|
|
"""
|
|
def __init__(self):
|
|
self.progress = ''
|
|
|
|
def __call__(self, line, nl):
|
|
ms = _re_pacman.match(line)
|
|
if ms:
|
|
p = ms.group(3)
|
|
if (self.progress != p) or nl:
|
|
self.progress = p
|
|
xfromy = ms.group(1)
|
|
if _controlled:
|
|
if not xfromy:
|
|
xfromy = ''
|
|
line = 'pacman:%s|%s|%s%%' % (xfromy, ms.group(2),
|
|
ms.group(4))
|
|
elif ms.group(4) == '100':
|
|
line = re_psub.sub('[##########]', line)
|
|
if nl:
|
|
sys.stdout.write(' '*80 + '\r')
|
|
else:
|
|
line = '/*/'
|
|
return (nl, line)
|
|
|
|
|
|
class mksquashfs_filter_gen:
|
|
"""Return a function to detect and process the progress output of
|
|
mksquashfs.
|
|
"""
|
|
def __init__(self):
|
|
self.progress = ''
|
|
|
|
def __call__(self, line, nl):
|
|
ms = _re_mksquashfs.match(line)
|
|
if ms:
|
|
percent = ms.group(2)
|
|
if (self.progress != percent) or nl:
|
|
self.progress = percent
|
|
if _controlled:
|
|
line = 'mksquashfs:' + ms.group(1)
|
|
else:
|
|
line = re.sub(r'=[-\\/|]', '= ', line)
|
|
else:
|
|
line = '/*/'
|
|
return (nl, line)
|
|
|
|
|
|
class mkisofs_filter_gen:
|
|
"""Return a function to detect and process the progress output of
|
|
mkisofs.
|
|
"""
|
|
def __init__(self):
|
|
self.running = None
|
|
|
|
def __call__(self, line, nl):
|
|
ms = _re_mkisofs.match(line)
|
|
if ms:
|
|
if _controlled:
|
|
line = 'mkisofs:' + line
|
|
self.running = line
|
|
nl = False
|
|
elif self.running:
|
|
line = self.running + '\n' + line
|
|
self.running = None
|
|
return (nl, line)
|
|
|
|
|
|
def readdata(filename):
|
|
return readfile(base_dir + '/data/' + filename)
|
|
|
|
|
|
def readfile(fpath):
|
|
try:
|
|
fh = open(fpath)
|
|
text = fh.read()
|
|
fh.close()
|
|
except:
|
|
errout(_("Couldn't read file: %s") % fpath)
|
|
return None
|
|
return text
|
|
|
|
|
|
def writefile(text, path):
|
|
try:
|
|
pd = os.path.dirname(path)
|
|
if not os.path.isdir(pd):
|
|
os.makedirs(pd)
|
|
fh = None
|
|
fh = open(path, 'w')
|
|
fh.write(text)
|
|
return True
|
|
except:
|
|
return False
|
|
finally:
|
|
if fh:
|
|
fh.close()
|
|
|