# -*- coding: utf-8 -*- # Copyright 2008-2016 Mir Calculate. http://www.calculate-linux.org # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys import os import stat import re import importlib from .utils.portage import getInstalledAtom, RepositoryPath, \ searchProfile, RepositorySubstituting from .cl_xml import xmlShare from .utils.system import SystemPath, FUser, emerge_running from functools import wraps import random import string import time import glob import hashlib import codecs import uuid from fnmatch import fnmatch from math import sqrt from itertools import * from collections import OrderedDict from operator import lt, le, eq, ne, ge, gt import shutil import errno from .utils.common import (_error, _warning, getTupleVersion, getPortageUidGid, isBootstrapDataOnly) from .utils.text import _u from .utils.portage import (isPkgInstalled, reVerSplitToPV, EmergeLog, getInstalledAtom, EmergeLogPackageTask, getPkgUses, RepositoryPath) from .utils.content import PkgContents, checkContents, getCfgFiles, fillContents from .utils.files import (getModeFile, listDirectory, removeDir, typeFile, scanDirectory, FilesError, dir_sync, find, getProgPath, pathJoin, readFile, readLinesFile, process) from .utils.mount import Mounts from .utils.tools import iterate_list, has_any, Locker from .datavars import DataVarsError, VariableError, CriticalError, SimpleDataVars from .configparser import (ConfigParser, NoSectionError, ParsingError) from .cl_lang import setLocalTranslate, RegexpLocalization _ = lambda x: x setLocalTranslate('cl_lib3', sys.modules[__name__]) PORTAGEUID, PORTAGEGID = getPortageUidGid() class TemplatesError(Exception): """ Error on templates appling """ def catch_no_space_left(f): def wrapper(*args, **kw): try: return f(*args, **kw) except IOError as e: if e.errno == 28: raise TemplatesError(_("No space left on device")) raise return wrapper def post_unlock_packages(f): def wrapper(self, *args, **kw): if not kw.get("rerun", True): return f(self, *args, **kw) else: try: return f(self, *args, **kw) except BaseException as e: raise finally: self.unlock_packages() return wrapper def try_decode_utf8(_bytes): try: return _bytes.decode("UTF-8"), True except UnicodeDecodeError as e: return _bytes, False class DataVarsConfig(SimpleDataVars): """ Получить профиль и emerge config из chroot системы """ def __init__(self, chroot_path='/'): from .variables import env SimpleDataVars.__init__( self, env.VariableClMakeProfile(systemRoot=chroot_path), env.VariableClEmergeConfig(systemRoot=chroot_path)) class LayeredIni(): _baseDir = None objVar = None class IniPath(): IniName = "ini.env" Grp = os.path.join('/var/lib/calculate/calculate-update', IniName) System = os.path.join('/var/lib/calculate/', IniName) Etc = os.path.join('/etc/calculate', IniName) Local = os.path.join('/var/calculate', IniName) Remote = os.path.join('/var/calculate/remote', IniName) Hardcode = os.path.join(RepositoryPath.CalculateProfiles, IniName) Work = System def __init__(self): # комплексное содержимое ini.env с приоритетом меньше, чем у # изменяемого self.lowerIni = None # комплексное содержимое ini.env с приоритетом выше, чем у # изменяемого self.upperIni = None def is_user(self): return self.objVar and self.objVar.Get('cl_action') == "desktop" def get_profile_path(self, dv): """ Получить путь до системного профиля :param dv: :return: """ if not dv: return "" try: make_profile = dv.Get('main.cl_make_profile') if os.path.exists(make_profile): profiledir = os.path.dirname(make_profile) return os.path.join(profiledir, os.readlink(make_profile)) return "" except VariableError: return "" def get_profiles_inienv(self, dv): """ Получить список ini.env находящихся в профиле с учётом их расположения в parent файлах :param dv: :return: """ if dv: profile_path = self.get_profile_path(dv) if profile_path: repos = RepositorySubstituting(dv, self._baseDir) return list(searchProfile( profile_path, self.IniPath.IniName, repository_sub=repos)) return [] def read_other_ini(self): """ Прочитать все необходимые файлы env :return: """ if not self.lowerIni: inifiles = self.get_profiles_inienv(self.objVar) inifiles.append(pathJoin(self._baseDir, self.IniPath.Grp)) try: if (self.objVar and (self.objVar.Get('core.ac_backup_restore') == 'on' or self.objVar.Get( 'core.ac_backup_service') == 'on')): backup_path = self.objVar.Get('cl_backup_ini_env') inifiles.append(backup_path) except DataVarsError as e: pass #print "lower:", inifiles self.lowerIni = ConfigParser(strict=False) for inifn in inifiles: try: self.lowerIni.read(inifn, encoding="utf-8") except ParsingError as e: sys.stderr.write("%s\n" % str(e)) sys.stderr.flush() if not self.upperIni: inifiles = [self.IniPath.Etc, self.IniPath.Local, self.IniPath.Remote] if self.is_user(): inifiles = [self.IniPath.Work] + inifiles inifiles = [pathJoin(self._baseDir, x) for x in inifiles] #print "upper:", inifiles self.upperIni = ConfigParser(strict=False) for inifn in inifiles: try: self.upperIni.read(inifn, encoding="utf-8") except ParsingError as e: sys.stderr.write("%s\n" % str(e)) sys.stderr.flush() class SystemIni(LayeredIni): _inifile = LayeredIni.IniPath.Work @property def inifile(self): if self.objVar: return pathJoin(self.objVar.Get('cl_chroot_path'), self._inifile) else: return self._inifile def is_user(self): return False def __init__(self, dv=None): self.objVar = dv if dv: self._baseDir = self.objVar.Get('cl_chroot_path') else: self._baseDir = '/' super().__init__() self.config = ConfigParser(strict=False) try: self.config.read(self.inifile, encoding="utf-8") except ParsingError as e: sys.stderr.write("%s\n" % str(e)) sys.stderr.flush() self.read_other_ini() def getVar(self, section, varname): value = self.upperIni.get(section, varname, raw=True, fallback=None) if value is None: value = self.config.get(section, varname, raw=True, fallback=None) if value is None: value = self.lowerIni.get(section, varname, raw=True, fallback="") return value def getKeys(self, section): skeys = [] for iniobj in (self.upperIni, self.config, self.lowerIni): if iniobj.has_section(section): skeys.extend(list(iniobj[section].keys())) return list(sorted(list(set(skeys)))) def delVar(self, section, varname): try: self.config.remove_option(section, varname) for section in (x for x in self.config.sections() if not self.config[x]): self.config.remove_section(section) self.__write() except NoSectionError: pass def __write(self): comment_block = "\n".join(takewhile(lambda x: x.startswith("#"), readLinesFile(self.inifile))) with open(self.inifile, 'w') as f: if comment_block: f.write(comment_block) f.write('\n\n') self.config.write(f) def setVar(self, section, var_dict): if not self.config.has_section(section): self.config.add_section(section) for k, v in var_dict.items(): self.config.set(section, k, v) self.__write() class _shareTemplate(): """Общие аттрибуты для классов шаблонов""" # Метка начала переменной varStart = "#-" # Метка конца переменной varEnd = "-#" _deltVarStart = len(varStart) _deltVarEnd = len(varEnd) objVar = None _reVar = re.compile( "%s(?:[a-z0-9_]+\.)?[a-zA-Z0-9_-]+%s" % (varStart, varEnd), re.M) def applyVarsTemplate(self, textTemplate, nameTemplate): """ Заменяет переменные на их значения """ resS = self._reVar.search(textTemplate) textTemplateTmp = textTemplate while resS: mark = textTemplateTmp[resS.start():resS.end()] varName = mark[self._deltVarStart:-self._deltVarEnd] try: t = self.objVar.getInfo(varName).type if "list" in t: varValue = self.objVar.serialize(t, self.objVar.Get(varName)) else: varValue = self.objVar.Get(varName) if not varValue: varValue = "" else: varValue = str(varValue) except DataVarsError as e: raise TemplatesError(_("error in template %s") % nameTemplate + "\n" + str(e)) textTemplateTmp = textTemplateTmp.replace(mark, varValue) resS = self._reVar.search(textTemplateTmp) return textTemplateTmp def getDataUser(self, groupsInfo=False): """Получить информацию о пользователе""" userName = self.objVar.Get("ur_login") if not userName: userName = "root" import pwd try: pwdObj = pwd.getpwnam(userName) uid = pwdObj.pw_uid gid = pwdObj.pw_gid homeDir = self.objVar.Get('ur_home_path') except Exception: raise TemplatesError(_("User %s not found") % str(userName)) if groupsInfo: import grp try: groupName = grp.getgrgid(gid).gr_name except Exception: raise TemplatesError(_("Group ID %s not found") % str(gid)) groupsNames = [x.gr_name for x in grp.getgrall() if userName in x.gr_mem] groupsNames = [groupName] + groupsNames return uid, gid, homeDir, groupsNames return uid, gid, homeDir class _shareTermsFunction(): """Общие аттрибуты для классов _terms и templateFunctions""" # Символы допустимые в скобках функции шаблона _reFunctionArgvInSquareBrackets = ( "a-zA-Z0-9_:;%@<>=\!\|\{\}\^\$\?\(\)\[\]\-" "\n\+\,\*\/\.\'\"~\\\\ ") _reFunctionArgvText = "[%s]" % _reFunctionArgvInSquareBrackets # регулярное выражение для поиска функции в шаблоне _reFunctionText = ("([a-zA-Z0-9\_-]+)\(((?:#-|-#|%s)+|)\)" % _reFunctionArgvText) class _terms(_error, _shareTermsFunction, _shareTemplate): """Вычисление условий применяемых в шаблонах """ # регулярное выражение для поиска функции в шаблоне _reFunction = re.compile(_shareTermsFunction._reFunctionText) # регулярное выражение для не версии _re_not_Version = re.compile("[^0-9\.]") # регулярное выражение не номер _re_not_Number = re.compile("[^0-9]") _suffixDict = {"pre": -2, "p": 0, "alpha": -4, "beta": -3, "rc": -1} _lenSuffixDict = len(_suffixDict) # Регулярное выражение для названия переменной _reRightName = re.compile("^(?:[a-z_\-]+\.)?(?:[a-zA-Z0-9_\-]+)$") # Регулярное выражение для сравниваемого значения _reDenyValue = re.compile("[^0-9a-zA-Z_/\.,-]") # латинские буквы в нижнем регистре _letters = list(string.ascii_lowercase) # использует из других объектов objVar = None def _splitVersion(self, strVersion): """ Split version. Version, addition letter, list suffixes with version, revision. Examples: 3.0.0_beta2 ("3.0.0_beta2","",[],"") 3.0.0_beta2-r1 ("3.0.0_beta2","",[],"r1") 3.0.0_beta2a-r1 ("3.0.0_beta2","a",[],"r1") 3.0.0_beta2a_rc1-r1 ("3.0.0_beta2","a",[("rc","1")],"r1") 3.0.0_beta2a_rc1_p20111212-r1 ("3.0.0_beta2","a",[("rc1","1"),("p","20111212")],"r1") """ # get revision from version strWorkVersion, spl, rVersion = strVersion.rpartition("-") if rVersion == strVersion: strWorkVersion = rVersion rVersion = "" suffixes = [] # get suffixes from version while "_" in strWorkVersion: # 2.3_p45 ('2.3','_','p43') # 2.3_rc4_p45 ('2.3_rc4','_','p43') strWorkVersion, spl, suffix = strWorkVersion.rpartition("_") suffSplList = [x for x in self._suffixDict.keys() if suffix.startswith(x)] if suffSplList: suffSpl = suffSplList[0] lenSuffSpl = len(suffSpl) suffixVersion = suffix[lenSuffSpl:] suffixes.append((suffSpl, suffixVersion)) letters = "" numberVersion = strWorkVersion if numberVersion and numberVersion[-1:] in self._letters: letters = numberVersion[-1:] numberVersion = numberVersion[:-1] return numberVersion, letters, suffixes, rVersion def _isVersion(self, strVersion): """strVersion is not version - True""" numberVersion, letters, suffixes, rVersion = \ self._splitVersion(strVersion) if not numberVersion.strip(): return False if self._re_not_Version.search(numberVersion): return False if letters and letters not in self._letters: return False for suffix, suffixVersion in suffixes: if suffixVersion and self._re_not_Number.search(suffixVersion): return False if rVersion: if rVersion[0] != "r" or len(rVersion) == 1: return False if self._re_not_Number.search(rVersion[1:]): return False return True def _isIntervalVersion(self, strVersion): if "," in strVersion and strVersion.count(',') == 1: version1, op, version2 = strVersion.partition(",") return self._isVersion(version1) and self._isVersion(version2) return False def _convertVers(self, verA, verB): """Конвертирование номеров версий для корректного сравнения """ def fillZero(elemA, elemB): # elemA, elemB = elemA[], elemB[] if len(elemA) > len(elemB): maxElemB = len(elemB) - 1 for i in range(len(elemA)): if i > maxElemB: elemB.append("0") else: maxElemA = len(elemA) - 1 for i in range(len(elemB)): if i > maxElemA: elemA.append("0") for i in range(len(elemB)): lenA = len(elemA[i]) lenB = len(elemB[i]) if lenA == lenB: pass elif lenA > lenB: res = lenA - lenB for z in range(res): elemB[i] = "0" + elemB[i] elif lenB > lenA: res = lenB - lenA for z in range(res): elemA[i] = "0" + elemA[i] def fillSuffix(elemA, elemB, sA, svA, sB, svB): if str(sA) or str(sB): svA, svB = [[x] if x else ['0'] for x in (svA, svB)] fillZero(svA, svB) sA, sB = [x if x else 0 for x in (sA, sB)] elemA.append(str(self._lenSuffixDict + sA)) elemA.extend(svA) elemB.append(str(self._lenSuffixDict + sB)) elemB.extend(svB) # Version, letters, suffix, suffixVersion, rVersion vA, lA, ssA, rvA = self._splitVersion(verA) vB, lB, ssB, rvB = self._splitVersion(verB) elemA = vA.split(".") elemB = vB.split(".") fillZero(elemA, elemB) if lA or lB: lA, lB = [x if x else '0' for x in (lA, lB)] elemA.append(lA) elemB.append(lB) # dereferencing suffix in suffixes list ssA = [(self._suffixDict.get(x[0], 0), x[1]) for x in ssA] ssB = [(self._suffixDict.get(x[0], 0), x[1]) for x in ssB] for suffix, sufVer in reversed(ssA): if ssB: sB, svB = ssB.pop() else: sB, svB = "", "" fillSuffix(elemA, elemB, suffix, sufVer, sB, svB) while ssB: sB, svB = ssB.pop() fillSuffix(elemA, elemB, "", "", sB, svB) if rvA or rvB: rvA, rvB = [[x[1:]] for x in (rvA, rvB)] fillZero(rvA, rvB) elemA += rvA elemB += rvB return ".".join(elemA), ".".join(elemB) def _checkInterval(self, val, op, interval): ver1, ver2 = interval.split(',') val1, ver1 = self._convertVers(val, ver1) val2, ver2 = self._convertVers(val, ver2) comparator = { '==': lambda a,b,c,d: a>=b and c <= d, '!=': lambda a,b,c,d: a d, '<=': lambda a,b,c,d: a>b and c <= d, '<>': lambda a,b,c,d: a>b and c < d, '=>': lambda a,b,c,d: a>=b and c < d } if op not in comparator: raise TemplatesError(_("Wrong interval operator")) return comparator[op](val1, ver1, val2, ver2) def _equalTerm(self, term, textError, function=None): """Вычисление логических выражений для условий Для корректной работы в классе который наследует этот класс должен быть объявлен аттрибут self.objVar (объект для работы с переменными) function - функция для для обработки функций в заголовке блока """ rpl = lambda x: x.replace("@@", " ") trm = {"&&": "@@and@@", "||": "@@or@@"} dictRuleFunc = OrderedDict((("==", eq), ("!=", ne), (">=", ge), ("<=", le), ("<>", ne), ("=>", ge), (">", gt), ("<", lt), )) rule = dictRuleFunc.keys() listEqual = [] for k in trm.keys(): if k in term: term = term.replace(k, trm[k]) trs = term.split("@@") listSplitOr = [] if "or" in trs: lst = [] for t in trs: if t != "or": lst.append(t) else: listSplitOr.append(lst) lst = [] if lst: listSplitOr.append(lst) else: listSplitOr = [trs] for trsAnd in listSplitOr: listEqual = [] for t in trsAnd: def search_rule(t, rule, prefix=""): for sepF in rule: if sepF in t: vals = list(t.partition(sepF)[::2]) if vals[0].endswith("\\"): return search_rule(vals[1], rule, prefix="%s%s%s" % ( prefix, vals[0], sepF)) return True, sepF, ["%s%s" % (prefix, vals[0]), vals[1]] return False, None, [] flagRule, sepF, vals = search_rule(t, rule) if flagRule: # проверка на допустимость названия переменной flagFunction = False if not self._reRightName.search(vals[0]): # проверка на допустимость функции flagError = True if callable(function): searchFunct = self._reFunction.search(vals[0]) if searchFunct: flagError = False flagFunction = True if flagError: self.setError( "'%s'" % rpl(term) + " " + _("incorrect")) self.setError(textError) return False # проверка на допустимость значения try: if "#-" in vals[1]: vals[1] = self.applyVarsTemplate(vals[1], "") vals[1] = function(vals[1]) except TemplatesError: pass if self._reDenyValue.search(vals[1]): self.setError("'%s'" % rpl(term) + " " + _("incorrect")) self.setError(textError) return False flagIntTypeVar = None if flagFunction and callable(function): valVars = function("#-%s-#" % vals[0]) if valVars is False: self.setError( "'%s'" % rpl(term) + " " + _("incorrect")) self.setError(textError) return False if "load" == searchFunct.group(1) and \ re.search("\(\s*num\s*,", vals[0]): if valVars: try: valVars = int(valVars) except ValueError: self.setError("'%s'" % rpl(term) + " " + _("incorrect")) self.setError(textError) return False flagIntTypeVar = True else: flagIntTypeVar = False else: if valVars == "" and \ (self._isVersion(vals[1]) or self._isIntervalVersion(vals[1])): valVars = "0" elif vals[1] == "" and self._isVersion(valVars): vals[1] = "0" else: try: valVars = self.objVar.Get(vals[0]) varTable = self.objVar.Get('cl_used_action') varTable.append((vals[0], vals[1])) if not valVars: valVars = "" except DataVarsError as e: raise TemplatesError("{header}\n{body}".format( header=textError, body=str(e))) # Номера версий для ini flagNotIniFunct = True # Два значения не пусты flagNotEmptyVals = not (valVars == "" and vals[1] == "") if flagFunction and flagNotEmptyVals and \ searchFunct.group(1) == "ini": # Проверка значения на версию if self._isVersion(valVars) and \ self._isVersion(vals[1]): verFile, verVar = self._convertVers(vals[1], valVars) res = dictRuleFunc[sepF](verVar, verFile) if res: listEqual.append(True) else: listEqual.append(False) break flagNotIniFunct = False if self._isVersion(valVars) and \ self._isIntervalVersion(vals[1]): res = False try: res = self._checkInterval( valVars, sepF, vals[1]) except TemplatesError: self.setError("'%s'" % rpl(term) + " " + \ _("incorrect")) self.setError( _("Wrong interval operator")) if res: listEqual.append(True) else: listEqual.append(False) break flagNotIniFunct = False # Cравниваем номера версий if flagNotIniFunct: if flagNotEmptyVals and \ ("_ver" in vals[0] or (flagFunction and searchFunct.group( 1) in ("pkg", "merge", "mergepkg")) or (flagFunction and searchFunct.group( 1) == "load" and re.search("\(\s*ver\s*,", vals[0]))): # Проверка значения на версию (или интервал) if (not self._isVersion(vals[1]) and not self._isIntervalVersion(vals[1])): self.setError("'%s'" % rpl(term) + " " + \ _("incorrect")) self.setError( _("This value is not a version")) return False # Проверка значения функции на версию if not self._isVersion(valVars): self.setError("'%s'" % rpl(term) + " " + \ _("incorrect")) self.setError( _("The function value is not a version")) return False if self._isIntervalVersion(vals[1]): res = False try: res = self._checkInterval( valVars, sepF, vals[1]) except TemplatesError: self.setError("'%s'" % rpl(term) + " " + \ _("incorrect")) self.setError( _("Wrong interval operator")) else: verFile, verVar = self._convertVers(vals[1], valVars) res = dictRuleFunc[sepF](verVar, verFile) if res: listEqual.append(True) else: listEqual.append(False) break else: if flagIntTypeVar is None: flagIntTypeVar = True try: valVars = int(valVars) except (TypeError, ValueError): flagIntTypeVar = False if flagIntTypeVar: if not vals[1].strip(): vals[1] = 0 try: valFile = int(vals[1]) valVar = valVars res = dictRuleFunc[sepF](valVar, valFile) if res: listEqual.append(True) else: listEqual.append(False) break except ValueError: flagIntTypeVar = False if not flagIntTypeVar: if sepF == "!=" or sepF == "==": if not vals[1].strip(): vals[1] = "" valFile = vals[1] valVar = valVars res = dictRuleFunc[sepF](valVar, valFile) if res: listEqual.append(True) else: listEqual.append(False) break else: if not flagNotEmptyVals: listEqual.append(False) break else: self.setError("'%s'" % rpl(term) + " " \ + _("incorrect")) self.setError(textError) return False else: if t == "and": if listEqual == [] or False in listEqual: listEqual = [False] break else: listEqual = [True] else: self.setError("'%s'" % rpl(term) + " " + _("incorrect")) self.setError(textError) return False if not (listEqual == [] or False in listEqual): break if listEqual == [] or False in listEqual: return False return True def splitParLine(self, linePar): """ Split params line """ def splitQuote(listPar, quoteSymbol): listTerm = [x + quoteSymbol for x in ("=", ">", "<")] flagQ = False mass = [] v = "" for i in listPar: if i.count(quoteSymbol) == 1: if flagQ and i.endswith(quoteSymbol): v = v + " " + i mass.append(v) v = "" flagQ = False elif [x for x in listTerm if x in i]: flagQ = True v = i else: mass.append(i) elif flagQ: v = v + " " + i else: mass.append(i) foundPar = list(set(mass) - set(listPar)) return not flagQ, [x for x in mass if not x in foundPar], foundPar listPar = re.split("\s+", linePar) flagFoundQ = "'" in linePar flagFoundQQ = '"' in linePar if flagFoundQ and flagFoundQQ: flagQ, listSplQPar, listFoundQPar = splitQuote(listPar, "'") if flagQ: flagQQ, listSplQQPar, listFoundQQPar = splitQuote(listSplQPar, '"') if flagQQ: listPar = listSplQQPar + listFoundQPar + listFoundQQPar elif flagFoundQQ: flagQQ, listSplQQPar, listFoundQQPar = splitQuote(listPar, '"') if flagQQ: listPar = listSplQQPar + listFoundQQPar elif flagFoundQ: flagQ, listSplQPar, listFoundQPar = splitQuote(listPar, "'") if flagQ: listPar = listSplQPar + listFoundQPar if flagFoundQ: listQPar = [] for par in listPar: if par.endswith("'") and par.count("'") > 1: listQPar.append(par[:-1].replace("='", "=")) else: listQPar.append(par) listPar = listQPar if flagFoundQQ: listQQPar = [] for par in listPar: if par.endswith('"') and par.count('"') > 1: listQQPar.append(par[:-1].replace('="', '=')) else: listQQPar.append(par) listPar = listQQPar return listPar class HParams(): Format = "format" DotAll = "dotall" Multiline = "multiline" Comment = "comment" Append = "append" Force = "force" DConf = "dconf" Convert = "convert" Link = "link" DirectoryLink = Link Mirror = "mirror" Symbolic = "symbolic" ChangeMode = "chmod" ChangeOwner = "chown" Name = "name" Path = "path" Autoupdate = "autoupdate" Protected = "protected" RunNow = "run" RunPost = "exec" Merge = "merge" PostMerge = "postmerge" Module = "module" Environ = "env" RestartService = "restart" StartService = "start" StopService = "stop" Rebuild = "rebuild" Stretch = "stretch" ServiceControl = (StopService, StartService, RestartService) _Single = (DotAll, Multiline, Force, Mirror, Symbolic, Autoupdate, Protected, Stretch) class AppendParams(): Join = "join" Before = "before" After = "after" Replace = "replace" Remove = "remove" Skip = "skip" Patch = "patch" Clear = "clear" LinkDirCompatible = (Replace, Join) class ActionType(): Merge = "merge" Patch = "patch" Profile = "profile" class ExecuteType(): Now = "run" Post = "exec" class Formats(): Executable = ("diff", "dconf", "ldif", "contents", "sqlite", "backgrounds") Meta = ("backgrounds",) Modificator = ("sqlite",) class OptDir(): Path = "path" Skip = "skip" Autoupdate = "autoupdate" @classmethod def single(cls, it): return [x for x in it if x in cls._Single] class fileHeader(HParams, _terms): """Обработка заголовков шаблонов и конфигурационных файлов """ # Допустимые параметры заголовка allowParam = ( HParams.Format, HParams.DotAll, HParams.Multiline, HParams.Comment, HParams.Append, HParams.Force, HParams.DConf, HParams.Convert, HParams.Link, HParams.Mirror, HParams.Symbolic, HParams.Stretch, HParams.ChangeMode, HParams.ChangeOwner, HParams.Name, HParams.Path, HParams.Autoupdate, HParams.Protected, HParams.RunNow, HParams.RunPost, HParams.Merge, HParams.PostMerge, HParams.Module, HParams.Environ, HParams.RestartService, HParams.StartService, HParams.StopService, HParams.Rebuild ) # Тип шаблона fileType = "" # Тип вставки шаблона typeAppend = "" # Возможные типы вставки шаблонов _fileAppend = ( HParams.AppendParams.Join, HParams.AppendParams.Before, HParams.AppendParams.After, HParams.AppendParams.Replace, HParams.AppendParams.Remove, HParams.AppendParams.Skip, HParams.AppendParams.Patch, HParams.AppendParams.Clear) # Интерпретатор (#!/bin/bash) (#!/usr/bin/python) execStr = "" # Символ комментария comment = False # Выражение для поиска строки интерпретатора reExecStr = re.compile("^(#!/.+[^#]\s)", re.M) # условные операторы terms = ('>', '<', '==', '!=', '>=', '<=', '<>', '=>') # параметры без значения listParNotVal = HParams.single(allowParam) # Результат вычисления условия в заголовке headerTerm = True def __init__(self, templateName, text, comment=None, fileType=False, objVar=False, function=None, templateObj=None): self.body = text # Объект с переменными self.objVar = objVar # Параметры описанные в заголовке файла шаблона self.params = {} # некорректные параметры incorrectParams = [] used_params = [] # Поиск строки запустка (#!/bin/bash и.т. д) if comment or fileType != "bin": reExecRes = self.reExecStr.search(self.body) if reExecRes: self.execStr = self.body[reExecRes.start():reExecRes.end()] self.body = self.body[:reExecRes.start()] + \ self.body[reExecRes.end():] # Удаление Заголовка Calculate if comment: titleFirst = "Modified" # В случае текста XML if isinstance(comment, tuple) and len(comment) == 2: reCalcHeader = \ re.compile("\s*%s\s+%s.+\s+(.+\n)+%s\s?" \ % (comment[0], titleFirst, comment[1]), re.M | re.I) reS = reCalcHeader.search(self.body) if reS: self.body = self.body[:reS.start()] + self.body[reS.end():] else: reCalcHeader = re.compile( "\s*%s\-+\s+%s\s+%s.+\s+(%s.+\s+)+%s\-+\s?" \ % (comment, comment, titleFirst, comment, comment), re.M | re.I) reS = reCalcHeader.search(self.body) if reS: self.body = self.body[reS.end():] if fileType is not False: if fileType == "bin": self.params[HParams.Format] = fileType self.fileType = self._getType() self.typeAppend = self._getAppend() else: textLines = self.body.splitlines() if textLines: textLine = textLines[0] #py3 regex problems rePar = re.compile( "\s*#\s*calculate\s+\\\\?|\s*#\s*calculate\\\\?$", re.I) reP = rePar.search(textLine) if reP: reLns = re.compile(r"\A([^\\\n]*\\\n)+[^\n]*\n*", re.M) reLs = reLns.search(self.body) if reLs: reL = reLs paramLine = self.body[reP.end():reLs.end()] paramLine = paramLine.replace("\\", " ") else: reLn = re.compile("\n") reL = reLn.search(self.body) paramLine = textLine[reP.end():] if reL: self.body = self.body[reL.end():] else: self.body = "" paramList = self.splitParLine(paramLine) if paramList: for i in paramList: for term in self.terms: if term in i: if self.headerTerm: errorMsg = ( _("Incorrect template") + _(": ") + templateName + "\n" + _("template header not valid") + _(": ") + i) if function: rezTerm = self._equalTerm( i, errorMsg, function) else: rezTerm = self._equalTerm( i, errorMsg) if not rezTerm: self.headerTerm = False break else: par = i.split("=") if len(par) == 1: if i in self.listParNotVal: self.params[i] = "True" used_params.append(i) else: if i.strip(): incorrectParams = {i} elif len(par) == 2: par[1] = self.applyVarsTemplate( par[1], "") par[1] = templateObj.applyFuncTemplate( par[1], templateName) self.params[par[0]] = par[1] used_params.append(par[0]) if par[0] == HParams.Environ: try: importlib.import_module( "calculate.%s.variables" % par[1]) except (ImportError, AttributeError): self.headerTerm = False self.comment = self._getComment() self.fileType = self._getType() typeAppend = self._getAppend() if typeAppend: self.typeAppend = typeAppend else: self.headerTerm = False self.setError( _("incorrect header parameter: '%s'") % "%s=%s" % (HParams.Append, self.params[HParams.Append])) if any(x in self.params for x in (HParams.RunPost, HParams.RunNow)): if HParams.RunPost in self.params: self.execStr = "#!%s\n" % self.params[HParams.RunPost] if HParams.RunNow in self.params: self.execStr = "#!%s\n" % self.params[HParams.RunNow] if "python" in self.execStr: self.execStr += "# -*- coding: utf-8 -*-\n" double_params = list(set([x for x in used_params if used_params.count(x) > 1])) if double_params: self.headerTerm = False self.setError(_("redefine header parameter: '%s'") % " ".join(double_params)) if not incorrectParams and self.params: incorrectParams = set(self.params.keys()) - set(self.allowParam) if incorrectParams: self.headerTerm = False self.setError(_("incorrect header parameter: '%s'") \ % " ".join(list(incorrectParams))) def _getType(self): """Выдать тип файла""" return self.params.get(HParams.Format, "raw") def _getAppend(self): """Выдать тип добавления файла""" if HParams.Append in self.params: if self.params[HParams.Append] in self._fileAppend: return self.params[HParams.Append] else: return False else: if self.fileType != "raw" and self.fileType != "bin" and \ self.fileType != "": if (HParams.Format in self.params and self.params[HParams.Format] in chain(("patch",), HParams.Formats.Executable)): self.params[HParams.Append] = HParams.AppendParams.Patch else: self.params[HParams.Append] = HParams.AppendParams.Join else: self.params[HParams.Append] = HParams.AppendParams.Replace return self.params[HParams.Append] def _getComment(self): """Выдать символ комментария файла""" if HParams.Comment in self.params: if self.params[HParams.Comment] in ("xml", "XML"): return "" else: return self.params[HParams.Comment] else: return False class dirHeader(HParams, _terms): """Обработка заголовков шаблонов директорий """ # Допустимые параметры заголовка allowParam = ( HParams.Append, HParams.ChangeMode, HParams.ChangeOwner, HParams.Name, HParams.Path, HParams.Autoupdate, HParams.Module, HParams.Environ, HParams.Merge, HParams.PostMerge, HParams.Rebuild, HParams.RestartService, HParams.StartService, HParams.StopService, HParams.DirectoryLink ) # Тип вставки шаблона typeAppend = "" # Возможные типы вставки шаблонов _fileAppend = ( HParams.AppendParams.Join, HParams.AppendParams.Remove, HParams.AppendParams.Skip, HParams.AppendParams.Clear, HParams.AppendParams.Replace ) # условные операторы terms = ('>', '<', '==', '!=', '>=', '<=', '<>', '=>') # параметры без значения listParNotVal = (HParams.Symbolic, HParams.Force, HParams.Autoupdate) # Результат вычисления условия в заголовке headerTerm = True def __init__(self, templateName, text, objVar=False, function=None, templateObj=None): self.body = text # Объект с переменными self.objVar = objVar # Параметры описанные в заголовке файла шаблона self.params = {} # некорректные параметры incorrectParams = set([]) used_params = [] textLines = text.splitlines() flagErrorBody = False if textLines: textLine = textLines[0] #py3 regex problems rePar = re.compile( "\s*#\s*calculate\s+\\\\?|\s*#\s*calculate\\\\?$", re.I) reP = rePar.search(textLine) if reP: reLns = re.compile(r"\A([^\\\n]*\\\n)+[^\n]*\n*", re.M) reLs = reLns.search(text) if reLs: reL = reLs paramLine = text[reP.end():reLs.end()] paramLine = paramLine.replace("\\", " ") else: reLn = re.compile("\n") reL = reLn.search(text) paramLine = textLine[reP.end():] if reL: self.body = text[reL.end():] else: self.body = "" if self.body.strip(): self.headerTerm = False self.setError(_("incorrect text in the template: '%s'") % self.body) flagErrorBody = True if not flagErrorBody: paramList = self.splitParLine(paramLine) if paramList: for i in paramList: for term in self.terms: if term in i: if self.headerTerm: errorMsg = ( _("Incorrect template") + _(": ") + templateName + "\n" + _("template header not valid") + _(": ") + i) if function: rezTerm = self._equalTerm( i, errorMsg, function) else: rezTerm = self._equalTerm( i, errorMsg) if not rezTerm: self.headerTerm = False break else: par = i.split("=") if len(par) == 1: if i in self.listParNotVal: self.params[i] = "True" used_params.append(i) else: if i.strip(): incorrectParams = {i} elif len(par) == 2: # self.params[par[0]] = par[1] par[1] = self.applyVarsTemplate( par[1], "") par[1] = templateObj.applyFuncTemplate( par[1], templateName) used_params.append(par[0]) self.params[par[0]] = par[1] if par[0] == HParams.Environ: try: importlib.import_module( "calculate.%s.variables" % par[1]) except (ImportError, AttributeError): self.headerTerm = False self.objVar.defaultModule = \ self.params[HParams.Environ] typeAppend = self._getAppend() if typeAppend: self.typeAppend = typeAppend else: self.headerTerm = False self.setError(_("incorrect header parameter: '%s'") \ % "%s=%s" % ( HParams.Append, self.params[HParams.Append])) double_params = list(set([x for x in used_params if used_params.count(x) > 1])) if double_params: self.headerTerm = False self.setError(_("redefine header parameter: '%s'") % " ".join(double_params)) if not flagErrorBody: if not incorrectParams: incorrectParams = set(self.params.keys()) - set(self.allowParam) if incorrectParams: self.headerTerm = False self.setError(_("incorrect header parameter: '%s'") \ % " ".join(list(incorrectParams))) def _getAppend(self): """Выдать тип добавления директории""" if HParams.Append in self.params: if (self.params[HParams.Append] == HParams.AppendParams.Replace and HParams.DirectoryLink not in self.params): return False if self.params[HParams.Append] in self._fileAppend: return self.params[HParams.Append] else: return False else: return HParams.AppendParams.Join class blocText(): """Разбиваем текст на блоки""" def splitTxtToBloc(self, text, openTxtBloc, closeTxtBloc, commentTxtBloc, sepField): """Делит текст на блоки (без заголовков) openTxtBloc - регулярное выражение для начала блока closeTxtBloc - регулярное выражение для конца блока commentTxtBloc - регулярное выражение - комментарий возвращает блоки текста """ blocs = [] level = 0 # Нахождение нескольких блоков в строке # разделители линий, разделителями могут быть ("","\n") sepsLines = [] # линии txtLines = [] # Исходные строки txtLinesSrc = text.splitlines() for line in txtLinesSrc: lineTmpA = line closeBl = False txtLinesTmp = [] commentSpl = commentTxtBloc.split(line) textLine = None commentLine = None if commentSpl[0].strip(): closeBl = True if len(commentSpl) > 1: commentBl = commentTxtBloc.search(line) textLine = commentSpl[0] commentLine = line[commentBl.start(0):] lineTmpA = textLine while closeBl: closeBl = sepField.search(lineTmpA) if closeBl: lineTmpB = lineTmpA[closeBl.end(0):] txtLinesTmp.append(lineTmpA[:closeBl.end(0)]) lineTmpA = lineTmpB if lineTmpA.strip(): txtLinesTmp.append(lineTmpA) # Если есть значение и комментарий в строке if textLine is not None: for l in txtLinesTmp: txtLines.append(l) sepsLines.append("") if not txtLinesTmp: txtLines.append(textLine) sepsLines.append("") txtLines.append(commentLine) sepsLines.append("\n") # Если есть несколько блоков в строке elif len(txtLinesTmp) > 1 and txtLinesTmp[1].strip(): lenTmpLines = len(txtLinesTmp) for l in range(lenTmpLines): txtLines.append(txtLinesTmp[l]) if l == lenTmpLines - 1: sepsLines.append("\n") else: sepsLines.append("") # Cтрока не преобразована else: txtLines.append(line) sepsLines.append("\n") # разбивание на блоки z = 0 bl = "" for i in txtLines: if commentTxtBloc.split(i)[0].strip() and openTxtBloc.search(i): level += len(openTxtBloc.split(i)) - 1 if commentTxtBloc.split(i)[0].strip() and closeTxtBloc.search(i): level -= len(closeTxtBloc.split(i)) - 1 bl += i + sepsLines[z] if level == 0: if bl: blocs.append(bl) bl = "" z += 1 # cоздание блоков с элементами не входящими в блоки realBlocs = [] z = 0 bl = "" for i in blocs: txtLines = i.splitlines() if len(txtLines) > 0: line = txtLines[0] else: line = i if commentTxtBloc.split(i)[0].strip() and openTxtBloc.search(line): if bl: realBlocs.append(bl) bl = "" realBlocs.append(i) else: bl += i z += 1 if bl: realBlocs.append(bl) if level == 0: if text and text[-1] != "\n": tmpBlocs = realBlocs.pop() tmpBlocs = tmpBlocs[:-1] realBlocs.append(tmpBlocs) return realBlocs else: return [] def findArea(self, text, reTextHeader, reTextArea, numGroupArea=0): """ Делит текст на области (с заголовками) reTextHeader - регулярное выражение для заголовка области reTextArea - регулярное выражение для всей области numGroupArea - номер групы результата поиска по регулярному выражению по всей области возвращает два списка: первый - заголовки, второй - тела областей без заголоков """ # Заголовки областей headersArea = [] # Тексты областей без заголовков textBodyArea = [] r = reTextArea.search(text) if not r: headersArea.append("") textBodyArea.append(text) return headersArea, textBodyArea txtWr = text while r: textArea = r.group(numGroupArea) txtSpl = txtWr.split(textArea) area = txtSpl[0] txtWr = txtSpl[1] if area: headersArea.append("") textBodyArea.append(area) res = reTextHeader.search(textArea) header = textArea[:res.end()] body = textArea[res.end():] headersArea.append(header) textBodyArea.append(body) if txtWr: r = reTextArea.search(txtWr) else: r = False if txtWr: headersArea.append("") textBodyArea.append(txtWr) return headersArea, textBodyArea def findBloc(self, text, captionTxtBloc, bodyTxtBloc): """ Делит текст на блоки (с заголовками) captionTxtBloc - регулярное выражение для заголовка блока bodyTxtBloc - регулярное выражение для тела блока возвращает два списка: первый - заголовки, второй - тела блоков """ # Заголовки блоков headersTxt = [] # Тексты блоков blocsTxt = [] r = captionTxtBloc.search(text) if r: headersTxt.append(r.group(0)) txtSpl = text.partition(r.group(0)) blocTxt = txtSpl[0] txtWr = txtSpl[2] rb = bodyTxtBloc.search(blocTxt) if not blocTxt: blocsTxt.append(blocTxt) if rb: blocsTxt.append(rb.group(0)) while r: r = captionTxtBloc.search(txtWr) if r: headersTxt.append(r.group(0)) txtSpl = txtWr.partition(r.group(0)) blocTxt = txtSpl[0] txtWr = txtSpl[2] rb = bodyTxtBloc.search(blocTxt) if rb: blocsTxt.append(rb.group(0)) else: blocsTxt.append(txtWr) if headersTxt and blocsTxt: if len(headersTxt) > len(blocsTxt): blocsTxt.insert(0, "") elif len(headersTxt) < len(blocsTxt): headersTxt.insert(0, "") if len(headersTxt) != len(blocsTxt): return False return headersTxt, blocsTxt else: return False class _file(_error): """ Класс для работы с файлами """ configMode = None def printWARNING(self, s): raise NotImplemented() def __init__(self): # Имя файла конфигурационного файла self.nameFileConfig = "" self.nameFileConfigOrig = "" # Содержимое конфигурационного файла self.textConfig = "" # Имя файла шаблона self.nameFileTemplate = "" # Содержимое шаблона self.textTemplate = "" # Дескриптор файла шаблона self.F_TEMPL = None # Дескриптор файла конфигурационного файла self.F_CONF = None # тип запускаемого шаблона self.executeType = None # список скриптов на запуск self.queueExecute = [] def saveConfFile(self): """Записать конфигурацию""" if not self.textConfig: self.textConfig = self.textTemplate if self.F_CONF: try: self.F_CONF.truncate(0) self.F_CONF.seek(0) if isinstance(self.textConfig, str): self.F_CONF.write(self.textConfig.encode("UTF-8")) elif isinstance(self.textConfig, bytes): self.F_CONF.write(self.textConfig) else: #TODO if after testing this doesn't pop up, replace elif above with else raise Exception(f"self.textConfig is not str or bytes: type == {type(self.textConfig)}") except IOError: self.setError(_("unable to open the file:") + self.nameFileConfig) return False self.F_CONF.flush() return True elif self.executeType == HParams.ExecuteType.Post: processor = self.textConfig.partition("\n")[0] if processor.startswith("#!"): self.queueExecute.append((processor[2:], self.textConfig, self.nameFileTemplate)) else: self.setError(_("unable to execute '%s'") + self.textConfig) return False def openTemplFile(self, nameFileTemplate): """Открыть файл шаблона""" try: F_TEMPL = open(nameFileTemplate, "rb") except IOError: self.setError(_("unable to open the file:") + nameFileTemplate) return False return F_TEMPL def closeTemplFile(self): if self.F_TEMPL: self.F_TEMPL.close() self.F_TEMPL = None def __closeOldFile(self): if self.F_CONF: self.F_CONF.close() self.F_CONF = None def __openConfFile(self, nameFileConfig): """Отктрыть конфигурационный файл""" try: if os.path.islink(nameFileConfig): # если ссылка то удаляем её os.unlink(nameFileConfig) F_CONF = open(nameFileConfig, "rb+") except (IOError, OSError): try: if os.path.isdir(nameFileConfig): self.printWARNING(_("unable to open the directory as file:") + nameFileConfig) return False F_CONF = open(nameFileConfig, "wb+") except (IOError, OSError): self.setError(_("unable to open the file:") + nameFileConfig) return False return F_CONF def openFiles(self, nameFileTemplate, nameFileConfig, typeFormat=None, newBuffer=None): """Открывает шаблон и конфигурационный файл""" self.textConfig = "" self.textTemplate = "" self.closeFiles() self.F_TEMPL = None self.F_CONF = None self.nameFileConfig = os.path.abspath(nameFileConfig) self.nameFileTemplate = os.path.abspath(nameFileTemplate) self.F_TEMPL = self.openTemplFile(self.nameFileTemplate) copy_stat = not os.path.exists(self.nameFileConfig) if (not self.executeType and typeFormat not in HParams.Formats.Executable): self.F_CONF = self.__openConfFile(self.nameFileConfig) if self.F_TEMPL and self.F_CONF: self.textTemplate, ___ = try_decode_utf8(self.F_TEMPL.read()) self.closeTemplFile() if self.configMode == T_NEWCFG: origConfigName = re.sub(r'/._cfg\d{4}_([^/]+)$', '/\\1', self.nameFileConfig) if newBuffer is None: self.textConfig = readFile(origConfigName) if copy_stat: self.copy_mod_own(origConfigName, self.nameFileConfig) else: self.textConfig = newBuffer else: self.textConfig, ___ = try_decode_utf8(self.F_CONF.read()) def copy_mod_own(self, source, target): try: statdata = os.stat(source) statdata_old = os.stat(target) if statdata.st_mode != statdata_old.st_mode: os.chmod(target, statdata.st_mode) if (statdata.st_uid != statdata_old.st_uid or statdata.st_gid != statdata_old.st_gid): os.chown(target, statdata.st_uid, statdata.st_gid) except OSError: pass def __del__(self): self.closeFiles() def closeFiles(self): """Закрытие файлов""" self.closeTemplFile() self.__closeOldFile() class utfBin(): """Класс для преобразования в utf-8 преобразование бинарного или смеси бинарного и utf-8 кода в utf-8 и обратное преобразование методы класса encode и decode """ def _retUTF(self, char): byte = ord(char) if byte <= 127: return '_ch_', 1 elif byte <= 191: return '_nb_', 1 elif byte <= 223: return '_fb_', 2 elif byte <= 239: return '_fb_', 3 elif byte <= 247: return '_fb_', 4 else: return '_er_', 1 def _sumbUtf(self, symbols, lenTail): if not symbols: return False, 0 lenSymb = len(symbols) if lenSymb >= 4: l = 4 elif lenSymb >= 3: l = 3 elif lenSymb >= 2: l = 2 else: if symbols[0] == '_ch_': return True, 1 else: return False, 1 result = False i_ = 0 for i in range(l): i_ = i if i == 0 and symbols[i] != '_fb_': break elif i > 0 and symbols[i] != '_nb_': break if lenTail > 1 and lenTail != i_: return False, 1 if i_ > 0: result = True return result, i_ def _intToChar(self, x): he = hex(x)[2:] return chr(int(he, 16)) def _hexToChar(self, he): return chr(int(he, 16)) def encode(self, text): """Кодирует смешанный формат в UTF-8""" ind = 0 utf = [] lenUtf = [] indErr = [] i = 0 for ch in text: r, l = self._retUTF(ch) utf.append(r) lenUtf.append(l) i += 1 while 1: if utf[ind] == '_fb_': res, l = self._sumbUtf(utf[ind:], lenUtf[ind]) if res is False: indErr.append(ind) if l > 0: ind += l if ind >= len(utf): break else: if utf[ind] != '_ch_': indErr.append(ind) ind += 1 if ind >= len(utf): break if indErr: lenIndErr = len(indErr) block = [] blocks = [] if lenIndErr > 1: i = 1 while 1: if i == 1: block.append(indErr[i - 1]) if indErr[i] - indErr[i - 1] == 1: block.append(indErr[i]) else: if block: blocks.append(block) block = [indErr[i]] i += 1 if i >= lenIndErr: break else: block.append(indErr[0]) if block: blocks.append(block) listErr = [] for block in blocks: string = "" last_elem = None for elem in block: string += hex(ord(text[elem]))[-2:] last_elem = elem if last_elem is not None: listErr.append((block[0], "__hex__?%s?__hex__" % string, last_elem)) textOut = text deltaInd = 0 for erEl in listErr: startInd = erEl[0] + deltaInd endInd = erEl[2] + 1 + deltaInd textOut = textOut[:startInd] + erEl[1] + textOut[endInd:] deltaInd += len(erEl[1]) - (erEl[2] - erEl[0] + 1) # if i == 1: # break # i += 1 return textOut def decode(self, text): """Декодирует UTF-8 в смешанный формат""" varStart = "__hex__\?" varEnd = "\?__hex__" # -1 Это экранирование '?' которое тоже считается deltVarStart = len(varStart) - 1 deltVarEnd = len(varEnd) - 1 reVar = re.compile("%s[a-f0-9]+%s" % (varStart, varEnd), re.M) resS = reVar.search(text) textTemplateTmp = text while resS: mark = textTemplateTmp[resS.start():resS.end()] hexString = mark[deltVarStart:-deltVarEnd] i = 0 stringInsert = "" hexCode = "" for ch in hexString: if i >= 1: hexCode += ch stringInsert += self._hexToChar(hexCode) hexCode = "" i = 0 else: hexCode += ch i += 1 textTemplateTmp = textTemplateTmp.replace(mark, stringInsert) resS = reVar.search(textTemplateTmp) return textTemplateTmp class TemplateFormat(_error): """ Формат шаблон """ def __init__(self, text, parent=None): self.text = text self.changed_files = [] self.set_parent(parent) self.prepare() def prepare(self): pass def setError(self, error): super().setError(error) if hasattr(self.parent, "bHasError"): self.parent.bHasError = True def set_parent(self, parent): self.parent = parent @property def template_name(self): return self.parent.nameFileTemplate def getIni(self, key, nameFile=""): return self.parent.functObj.getIni(key, nameFile) def setIni(self, key, value, nameFile=""): return self.parent.functObj.setIni(key, value, nameFile) @property def objVar(self): return self.parent.objVar class FormatFactory(): """ Фабрика классов форматов шаблонов """ # Импортированные классы поддерживаемых форматов шаблонов importFormats = {} newObjProt = {} def __init__(self, parent): self.parent = parent def createNewClass(self, name, bases, attrs=None): raise NotImplemented() def getClassObj(self, nameClassTemplate): """Создает класс шаблона по имени""" if nameClassTemplate in self.importFormats: classFormat = self.importFormats[nameClassTemplate] else: try: classFormat = getattr(__import__("calculate.lib.format.%s" % nameClassTemplate, globals(), locals(), [nameClassTemplate]), nameClassTemplate) except (ImportError, AttributeError) as e: # Создаем объект из self.newObjProt с помощью # метаклассов if nameClassTemplate in self.newObjProt: # Прототип класса nameProt = self.newObjProt[nameClassTemplate] if nameProt in self.importFormats: classProt = self.importFormats[nameProt] else: try: classProt = getattr( __import__("calculate.lib.format.%s" % nameProt, globals(), locals(), [nameProt]), nameProt) except (ImportError, AttributeError) as e: return False self.importFormats[nameProt] = classProt classFormat = self.createNewClass(nameClassTemplate, (classProt,)) else: return False self.importFormats[nameClassTemplate] = classFormat return classFormat def createObject(self, formatTemplate, textTemplate): """Создание объекта формата шаблона. Объект создается на основании формата шаблона и текста шаблона""" classFormat = self.getClassObj(formatTemplate) if callable(classFormat): obj = classFormat(textTemplate, self.parent) return obj else: return False class TemplateFunctionError(Exception): pass def template_function(lastall=False): """ Подготовить метод для использования в качестве функции lastall: поволяет поделить строку аргументов на указанное число, при этом последний аргумент получит все данные, которые могут содержать разделитель параметров """ def decor(f): @wraps(f) def wrapper(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): def pretty_num(num): if num > 1: return _("%d argumens") % num else: return _("1 argument") funArgv = funArgv.strip() # поиск всех служебных переменных spec_vars = [x for x in f.__code__.co_varnames[:f.__code__.co_argcount] if x in ("self", "nameTemp", "localVars")] varnum = f.__code__.co_argcount - len(spec_vars) defnum = len(f.__defaults__) if f.__defaults__ else 0 # число обязательных параметров reqnum = varnum - defnum if funArgv: terms = [x.strip() for x in funArgv.split(",")] else: terms = [] if not varnum and len(terms) != varnum: raise self.raiseErrTemplate(_("Function takes no arguments")) if len(terms) < reqnum: if defnum: raise self.raiseErrTemplate( _("Function takes at least {num}").format( num=pretty_num(reqnum))) else: raise self.raiseErrTemplate( _("Function takes exactly {num}").format( num=pretty_num(reqnum))) if not lastall: if len(terms) > varnum: if defnum: raise self.raiseErrTemplate( _("Function takes at most {num}").format( num=pretty_num(varnum))) else: raise self.raiseErrTemplate( _("Function takes exactly {num}").format( num=pretty_num(varnum))) else: terms = terms[:varnum-1] + [",".join(terms[varnum-1:])] args = [self] if "nameTemp" in spec_vars: args.append(nameTemp) if "localVars" in spec_vars: args.append(localVars) args.extend(terms) try: replace = f(*args) except TemplateFunctionError as e: raise self.raiseErrTemplate(str(e)) textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp return wrapper return decor class templateFunction(_error, _warning, _shareTemplate, _shareTermsFunction, LayeredIni): """Класс для функций шаблонов""" # Словарь установленных программ {"имя программы":[версии]} installProg = {} # Cписок просканированных категорий установленных программ installCategory = [] # Флаг сканирования всех установленных программ flagAllPkgScan = False # Список названий функций шаблона namesTemplateFunction = [] # Словарь {название функции шаблона: функция шаблона, ...} templateFunction = {} # Регулярное выражение для сложения sNum = re.compile("\-[^\-\+]+|[^\-\+]+") # Регулярное выражение для умножениея и деления sMD = re.compile("[^\-\+\*/]+") # директория установленных программ _basePkgDir = "/var/db/pkg" basePkgDir = _basePkgDir # кэш для проверки наличия пакета в портежах cachePortdir = {} # стек глобальных переменных stackGlobalVars = [] # регулярное выражение для поиска версии reFindVer = re.compile("(?<=-)(?:\d+)(?:(?:\.\d+)*)" "(?:[a-z]?)(?:(?:_(?:pre|p|beta|alpha|rc)\d*)*)" "(?:-r\d+)?$") reEmptyLoad = re.compile("^\s*$|^\s*;|^\s*#") # Имя обрабатываемого шаблона nameTemplate = "" # Текст функции шаблона functText = "" # regular for discard sort number and version reData = re.compile(r"^(?:\d+-)?(.+?)(?:-(?:|always|\d+|\d(?:\d|\.|pre|_" "|-always|alpha|beta|pre|rc|[a-z][^a-z])*[a-z]?)(?:" "-r\d+)?)?$", re.S) currentAction = HParams.ActionType.Merge def printSUCCESS(self, s): raise NotImplemented() def printWARNING(self, s): raise NotImplemented() def printERROR(self, s): raise NotImplemented() @classmethod def get_pkgname_by_filename(cls, fn): fileName = os.path.split(fn)[1] if fileName == '.calculate_directory': parentDir = os.path.dirname(fn) parentDir, pkgName = os.path.split(parentDir) else: parentDir, pkgName = os.path.split(fn) category = os.path.split(parentDir)[1] # reg for discard version and sort number pkgName = cls.reData.search(pkgName).group(1) category = cls.reData.search(category).group(1) return "%s/%s" % (category, pkgName) currentBelong = "" currentBelongSlot = "" alreadyInformed = [] def __init__(self, objVar): # Если не определен словарь функций шаблона # import services api LayeredIni.__init__(self) if not self.templateFunction: # префикс функций шаблона pref = "func" # cписок [(название функции, функция), ...] # удаляем у названия функции префикс и переводим остаток названия # в нижний регистр dictFunc = [(x[0][len(pref):].lower(), x[1]) for x in self.__class__.__dict__.items() if x[0].startswith(pref) and hasattr(x[1], "__call__")] # Формируем словарь функций шаблона self.templateFunction.update(dictFunc) # Формируем список функций шаблона for nameFunction in self.templateFunction.keys(): self.namesTemplateFunction.append(nameFunction) # Объект хранения переменных self.objVar = objVar self._reFunc = re.compile("%s%s%s" % (self.varStart, self._reFunctionText, self.varEnd), re.M) self._rePrePattern = "%s.{%%d,}?%s" % (self.varStart, self.varEnd) self._rePreFuncPattern = "%s.{%%d,}?\)%s" % (self.varStart, self.varEnd) # Аттрибуты для функции шаблона ini() # Первоначальный словарь переменных для ini() self.prevDictIni = {} # Текущий словарь переменных для ini() self.currDictIni = {} # Время модификации конфигурационного файла для ini() self.timeIni = -1 self.recalculateBaseDir() # Словарь времен модификации env файлов self.timeConfigsIni = {} # Словарь хранения переменых полученных функцией env() из env файлов self.valuesVarEnv = {} # Словарь хранения опций для функции info() self.optionsInfo = {} # файл параметров сервисов envFile = self.objVar.Get("cl_env_server_path") # объект конвертирования из старого remote env файла self.convObj = False if os.access(envFile, os.R_OK): self.convObj = False elif os.access("/var/calculate/remote/calculate.env", os.R_OK): from .convertenv import convertEnv self.convObj = convertEnv() def recalculateBaseDir(self): """Recalculate basedir and homedir""" # Директория другой системы self._chrootDir = self.objVar.Get("cl_chroot_path") # Изменение директории к базе пакетов self.basePkgDir = pathJoin(self._chrootDir, self._basePkgDir) self.basePkgDir = os.path.normpath(self.basePkgDir) # Базовая директория переноса шаблонов "/mnt/calculate" или "/" и.т.д self._baseDir = pathJoin(self._chrootDir, self.objVar.Get("cl_root_path")) self._baseDir = os.path.normpath(self._baseDir) self.uid, self.gid, self.homeDir, self.groups = \ self.getDataUser(groupsInfo=True) # Домашняя директория, плюс базовая директория self.homeDir = pathJoin(self._baseDir, self.homeDir) # path to configuration file for ini() function # if action is desktop configuration, then path in user directory # else config file place in /etc/calculate if self.objVar.Get('cl_action') == "desktop": self.pathConfigIni = os.path.join(self.homeDir, ".calculate") self.fileConfigIni = os.path.join(self.pathConfigIni, LayeredIni.IniPath.IniName) self.modeConfigIni = 0o640 else: self.fileConfigIni = pathJoin(self._chrootDir, LayeredIni.IniPath.Work) self.pathConfigIni = os.path.dirname(self.fileConfigIni) self.modeConfigIni = 0o644 def equalTerm(self, term, localVars): """Метод для вычисления выражения""" terms = self.sNum.findall(term) if terms: strNumers = [] for n in terms: strNum = n.strip() if "*" in strNum or "/" in strNum: strNum = self.multAndDiv(strNum, localVars) num = 0 try: num = int(strNum) except ValueError: minus = False if strNum[:1] == "-": minus = True strNum = strNum[1:] if strNum in localVars: try: num = int(localVars[strNum]) except ValueError: raise self.raiseErrTemplate( _("error: variable %s is not integer") % str(strNum)) elif self.objVar.exists(strNum): try: num = int(self.objVar.Get(strNum)) except ValueError: raise self.raiseErrTemplate( _("error: variable %s is not integer") % str(strNum)) else: raise self.raiseErrTemplate( _("error: local variable %s not defined") % str(strNum)) if minus: num = -num strNumers.append(num) return sum(strNumers) raise self.raiseErrTemplate(_("error: template term %s, incorrect data") \ % str(term)) def multAndDiv(self, term, localVars): """Метод для умножения и деления""" termTmp = term varsLocal = self.sMD.findall(term) for var in varsLocal: flagVarTxt = True try: int(var) except ValueError: flagVarTxt = False if flagVarTxt: continue varReplace = str(self.equalTerm(var, localVars)) termTmp = termTmp.replace(var, varReplace) ret = eval(termTmp) return ret def getIni(self, key, nameTemp=""): class FakeMatch(): def start(self): return 0 def end(self): return 0 return self.funcIni(key, FakeMatch(), None, "", nameTemp) def setIni(self, key, value, nameTemp=""): class FakeMatch(): def start(self): return 0 def end(self): return 0 self.funcIni("%s,%s" % (key, value), FakeMatch(), None, "", nameTemp) def funcProfile(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Функция проверят состояние пользовательского профиля: configured - профиль настраивался утилитами calculate empty - профиль пустой, либо содержит skel, либо сертификат утилит custom - профиль настроен и он настраивался не утилитами """ ini_value = self.funcIni("main.update", resS, localVars, "", nameTemp) if ini_value: replace = "configured" else: user_dir = self.objVar.Get('ur_home_path') if isBootstrapDataOnly(user_dir): replace = "empty" else: replace = "custom" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def check_command(self, command, prefix="/"): cmd = getProgPath(command, prefix=prefix) if not cmd: raise self.raiseErrTemplate( _("Command not found '%s'")%command) return cmd def warning_message(self, message): if callable(self.printWARNING): self.printWARNING(message) @template_function() def funcWorld(self, category): """ Функция выполняет eix и возвращает список пакетов """ prefix = self.objVar.Get('cl_chroot_path') nfenv = dict(os.environ) nfenv["NOFOUND_STATUS"]="0" kwargs = {'lang':'C', 'envdict': nfenv} if prefix == "/": args = [self.check_command("/usr/bin/eix", prefix=prefix)] else: args = ["/bin/chroot", prefix, self.check_command("/usr/bin/eix", prefix=prefix)] args.extend(["-*", "--format", ""]) if "/" in category: args.extend(["-e", category]) else: args.extend(["--category", "-e", category]) p = process(*args, **kwargs) if p.success(): atoms = [x for x in p.read().split() if x.strip()] if not atoms: self.warning_message(_("No packages in %s category")%category) return "\n".join(atoms) else: raise TemplateFunctionError(_("Failed to execute") + _(": ") + "eix") def funcLs(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Функция получения списка файлов из директории """ globpath, o, pattern = funArgv.partition(',') if not pattern: pattern = r" " pattern = pattern.replace(r"\1", "{0}") pattern = re.sub(r"(^|[^\\])\\n", "\\1\n", pattern) pattern = pattern.replace(r"\n", "\n") pattern = pattern.replace(r"\t", "\t") chroot_path = os.path.normpath(self.objVar.Get('cl_chroot_path')) globpath = pathJoin(chroot_path, globpath) if "*" in globpath: files = glob.glob(globpath) else: files = listDirectory(globpath, fullPath=True) if files: files = (x for x in files if not os.path.isdir(x)) if chroot_path != '/': l = len(chroot_path) files = (x[l:] for x in files) if r"{0}" not in pattern: replace = pattern.join(files) else: replace = "".join(pattern.format(x) for x in files) else: replace = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcForeach(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Функция получения списка файлов из директории """ varname, o, pattern = funArgv.partition(',') values = "" try: values = self.objVar.Get(varname) except DataVarsError as e: raise TemplatesError(_("error: variable %s does not exist") % varname) if not pattern: pattern = r" " if values: pattern = pattern.replace(r"\1", "{0}") pattern = re.sub(r"(^|[^\\])\\n", "\\1\n", pattern) pattern = pattern.replace(r"\n", "\n") pattern = pattern.replace(r"\t", "\t") if r"{0}" not in pattern: replace = pattern.join(values) else: replace = "".join(pattern.format(x) for x in values) else: replace = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcSum(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона, вычисляет функцию sum()""" terms = funArgv.replace(" ", "").split(",") # Название локальной переменной nameLocVar = terms[0] if nameLocVar not in localVars: localVars[nameLocVar] = 0 if len(terms) == 2: if terms[1].strip(): localVars[nameLocVar] = self.equalTerm(terms[1], localVars) replace = str(localVars[nameLocVar]) else: replace = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] elif len(terms) == 3: if terms[1].strip(): replaceInt = self.equalTerm(terms[1], localVars) replace = str(replaceInt) else: replace = "" localVars[nameLocVar] = self.equalTerm(terms[2], localVars) textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] else: raise self.raiseErrTemplate() return textTemplateTmp def funcExists(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона exists(), проверяет существование файла, если существует выдает '1' если второй параметр root, то проверка осуществляется от корня. """ if funArgv.strip(): terms = [x.strip() for x in funArgv.split(",")] if len(terms) > 2: raise self.raiseErrTemplate() fileName = terms[0] flagNotRootFS = True if len(terms) == 2: if terms[1] == "root": flagNotRootFS = False else: raise self.raiseErrTemplate( _("The second argument of the function is not 'root'")) if fileName[0] == "~": # Получаем директорию пользователя fileName = os.path.join(self.homeDir, fileName.partition("/")[2], "")[:-1] elif fileName[0] != "/": raise self.raiseErrTemplate(_("wrong path '%s'") % fileName) else: if flagNotRootFS: fileName = pathJoin(self._baseDir, fileName) replace = "" if os.path.exists(fileName): check_map = ( ('f', stat.S_ISREG), ('d', stat.S_ISDIR), ('l', stat.S_ISLNK), ('b', stat.S_ISBLK), ('c', stat.S_ISCHR), ('p', stat.S_ISFIFO), ('s', stat.S_ISSOCK)) fmode = os.lstat(fileName) for t, func in check_map: if func(fmode.st_mode): replace = t break else: replace = "1" else: replace = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcLoad(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона load(), если файл существует читает из файла локальную переменную если один параметр - выводит значение локальной переменной """ terms = funArgv.split(",") if terms: lenTerms = len(terms) if not terms[0].strip() or \ (lenTerms == 2 and not terms[1].strip()) or \ (lenTerms == 3 and not terms[2].strip()) or \ lenTerms > 3: raise self.raiseErrTemplate() else: raise self.raiseErrTemplate() flagNotRootFS = True if lenTerms == 3: if terms[2] == "root": flagNotRootFS = False else: raise self.raiseErrTemplate( _("The third argument of the function is not 'root'")) if lenTerms >= 2: if not terms[0] in ["ver", "num", "char", "key", "empty"]: raise self.raiseErrTemplate( _("the first argument of the function is neither 'ver'" " or 'num' or 'char' or 'empty'")) if lenTerms == 1: fileName = terms[0].strip() else: fileName = terms[1].strip() # Если домашняя директория if fileName[0] == "~": # Получаем директорию пользователя fileName = os.path.join(self.homeDir, fileName.partition("/")[2], "")[:-1] elif fileName[0] != "/": raise self.raiseErrTemplate(_("wrong path '%s'") % fileName) else: if flagNotRootFS: fileName = pathJoin(self._baseDir, fileName) replace = "" if os.path.exists(fileName): replace = readFile(fileName).strip() if replace and lenTerms >= 2 and terms[0] == "empty": replace = "\n".join((x for x in replace.split("\n") if not self.reEmptyLoad.search(x))) if not replace and lenTerms >= 2 and terms[0] in ["ver", "num"]: replace = "0" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def sharePkg(self, pkgs, force=False): """ Update packages from pkgs list """ for pkgname, category, ver, slot in pkgs: fullname = "%s/%s" % (category, pkgname) if not fullname in self.installProg or \ type(self.installProg[fullname]) != dict: self.installProg[fullname] = self.installProg[pkgname] = {} if force or not slot in self.installProg[fullname]: self.installProg[fullname][slot] = ver return self.installProg # os.path.walk is deprecated in py3 def getInstallPkgGentoo(self, category=""): pkgs = [] filterFunc = lambda x: "SLOT" == x for dirname, dirs, files in os.walk(os.path.join(self.basePkgDir, category)): for nameFile in filter(filterFunc, files+dirs): absNameFile = os.path.join(dirname, nameFile) category, spl, pkgname = dirname.rpartition('/') dbpkg, spl, category = category.rpartition('/') slot = readFile(absNameFile).strip().partition('/')[0] pkgname, spl, rev = pkgname.rpartition("-") if rev.startswith('r'): pkgname, spl, ver = pkgname.rpartition("-") ver = "%s-%s" % (ver, rev) else: ver = rev pkgs.append((pkgname, category, ver, slot)) return self.sharePkg(pkgs) def pkg(self, nameProg, slot=None): if len(self.installProg) > 0: if type(list(self.installProg.values())[0]) != dict: self.installProg.clear() self.getInstallPkgGentoo() if nameProg in self.installProg: versions = self.installProg[nameProg] if slot: return versions.get(slot, "") if len(versions) == 1: return list(versions.values())[0] else: return versions[max(versions.keys(), key=getTupleVersion)] else: return "" def funcPkg(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона pkg(), выдает номер версии программы""" # Название программы nameProg = funArgv.replace(" ", "") if not nameProg: nameProg = self.get_pkgname_by_filename(self.nameTemplate) # Замена функции в тексте шаблона if "/" in nameProg: category, spl, nameProg = nameProg.partition("/") nameProg, spl, uses = nameProg.partition('[') nameProg, spl, slot = nameProg.partition(":") if uses: uses = uses.rstrip("]") if not category in self.installCategory: self.getInstallPkgGentoo(category=category) self.installCategory.append(category) replace = self.pkg(nameProg, slot=slot or None) if replace and uses: pkg_use, pkg_iuse = getPkgUses("%s/%s" % (category, nameProg), replace, prefix=self.objVar.Get( 'cl_chroot_path')) for use in (x for x in uses.split(',') if x): if (use[0] == "-" and use[1:] in pkg_use or use[0] != "-" and use not in pkg_use): replace = "" break else: if not self.flagAllPkgScan: self.getInstallPkgGentoo() templateFunction.flagAllPkgScan = True nameProg, spl, slot = nameProg.partition(":") replace = self.pkg(nameProg, slot=slot) textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcKernel(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Функция kernel(...), выдает значение опции конфига ядра (y,m,) """ terms = funArgv.replace(" ", "").split(",") if not terms[0].strip() or len(terms) != 1: raise self.raiseErrTemplate() kernel_opt = terms[0].upper() if kernel_opt.startswith("CONFIG_"): raise self.raiseErrTemplate( _("the option name should not starts with CONFIG_")) kernel_config = self.objVar.Get('install.os_install_kernel_config') find_str = "CONFIG_%s" % kernel_opt replace = "" for line in kernel_config: if find_str in line: if "%s=" % find_str in line: key, op, value = line.partition("=") replace = value.strip("'\"") break elif "%s is not set" % find_str in line: break textTemplateTmp = (textTemplateTmp[:resS.start()] + replace + textTemplateTmp[resS.end():]) return textTemplateTmp def funcGrep(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Функция grep (...), выдает значение из файла по регулярному выражению """ fname, op, regpattern = funArgv.replace(" ", "").partition(",") regpattern = regpattern.replace("(?\<", "(?<") regpattern = self._replace_hex(regpattern) if not fname or not regpattern: raise self.raiseErrTemplate() try: reg = re.compile(regpattern) except re.error: raise self.raiseErrTemplate(_("Wrong regular expression")) if fname[0] == "~": # Получаем директорию пользователя fname = os.path.join(self.homeDir, fname.partition("/")[2], "")[:-1] fname = pathJoin(self.objVar.Get('cl_chroot_path'), fname) fileContent = readFile(fname) match_data = reg.search(fileContent) if match_data: md_groups = match_data.groups() if md_groups: replace = md_groups[0] or "" else: replace = match_data.group() else: replace = "" return ( textTemplateTmp[:resS.start()] + replace + textTemplateTmp[ resS.end():]) def funcCut(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Функция разбивающая третий аргумент на строки по указанному разеделителю и возвращающая указанный блок #-cut(2,-,1-2-3-4)-# -> 3 #-cut(2,,1,2,3,4)-# -> 3 :param funArgv: :param resS: :param localVars: :param textTemplateTmp: :param nameTemp: :return: """ if funArgv: terms = funArgv.split(",") else: terms = [] if len(terms) > 3: terms = terms[:2] + [",".join(terms[2:])] if len(terms) < 1: terms = ["0"] if len(terms) < 2: terms.append("-") if len(terms) < 3: terms.append(self.objVar.Get('cl_pass_file')) num, delimeter, data = terms if not num.isdigit(): raise self.raiseErrTemplate( _("first parameter must be number")) delimeter = delimeter or "," num = int(num) data = data.split(delimeter) if num < len(data): replace = data[num] else: replace = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcRnd(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона rnd(), выдает строку случайных символов первый аргумент: 'num' - числа, 'pas' - цифры и буквы 'uuid' - цифры и строчные буквы a-f второй аргумент: количество символов """ terms = funArgv.replace(" ", "").split(",") gentype = terms[0].strip() genlen = None uuidmax = 32 if len(terms) not in (1,2): raise self.raiseErrTemplate( _("function rnd support one or two arguments only")) if len(terms) == 2: genlen = terms[1] if not genlen.isdigit(): raise self.raiseErrTemplate( _("the second argument of the function is not a number")) genlen = int(terms[1]) if gentype == 'uuid': if not genlen: genlen = uuidmax if genlen > uuidmax: raise self.raiseErrTemplate( _("length of UUID must not be above {maxlen}").format( maxlen=uuidmax)) if not gentype or not genlen or len(terms) not in (1, 2): raise self.raiseErrTemplate() fArgvNames = {'num': string.digits, 'pas': string.ascii_letters + string.digits, 'hex': string.ascii_lowercase[:6] + string.digits, 'uuid': string.ascii_lowercase[:6] + string.digits} if not gentype in fArgvNames: raise self.raiseErrTemplate( _("the first argument of the function must " "be 'num', 'pas' or 'uuid'")) if gentype == 'uuid': offset = [y for x, y in ( (0, 0), (9, 1), (13, 2), (17, 3), (21, 4)) if genlen >= x][-1] replace = str(uuid.uuid4()) replace = replace[:genlen+offset] else: choiceStr = fArgvNames[gentype] replace = ''.join([random.choice(choiceStr) for i in range(genlen)]) textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcCase(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона case(), выдает переменную в определенном регистре первый аргумент: 'upper' - верхний регистр, 'lower' - нижний регистр, 'capitalize' - первая буква в верхнем регистре второй аргумент: название переменной """ terms = funArgv.replace(" ", "").split(",") if not terms[0].strip() or \ (len(terms) == 2 and not terms[1].strip()) or len(terms) != 2: raise self.raiseErrTemplate() fArgvNames = ['upper', 'lower', 'capitalize'] if not terms[0] in fArgvNames: raise self.raiseErrTemplate(_("the first argument of the function" " is neither 'upper' or 'lower' or" " 'capitalize'")) try: strValue = self.objVar.Get(terms[1]) if not strValue: strValue = "" else: strValue = str(strValue) except Exception: raise TemplatesError( _("error in template %s") % self.nameTemplate + "\n" + _("error: variable %s not found") % str(terms[1])) replace = "" strValue = _u(strValue) if terms[0] == 'upper': replace = strValue.upper() elif terms[0] == 'lower': replace = strValue.lower() elif terms[0] == 'capitalize': replace = strValue.capitalize() if replace: replace = replace #there used to be encode utf-8 here textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcIn(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Function in for check value in variable """ terms = funArgv.replace(" ", "").split(",") # Название локальной переменной nameLocVar = terms[0] try: value = self.objVar.Get(nameLocVar) terms = terms[1:] if any(x in terms for x in iterate_list(value)): replace = "1" else: replace = "" except Exception: raise self.raiseErrTemplate(_("error: variable %s does not exist") \ % str(nameLocVar)) textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcPush(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """локальная функция записывает значение переменной в стек глобальных переменных """ terms = funArgv.replace(" ", "").split(",") # Название локальной переменной nameLocVar = terms[0] value = "" if nameLocVar in localVars.keys(): flagFoundVar = True value = localVars[nameLocVar] else: try: value = self.objVar.Get(nameLocVar) flagFoundVar = True except Exception: flagFoundVar = False if flagFoundVar: # Если переменная существует if len(terms) == 1: self.stackGlobalVars.append(str(value)) else: raise self.raiseErrTemplate(_("error: variable %s exists") \ % str(nameLocVar)) else: # Если переменная не существует if len(terms) == 1: raise self.raiseErrTemplate( _("error: variable %s does not exist") \ % str(nameLocVar)) elif len(terms) == 2: value = terms[1].strip() self.stackGlobalVars.append(str(value)) localVars[nameLocVar] = value else: raise self.raiseErrTemplate() replace = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcPop(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """локальная функция получает значение из стека глобальных переменных и присваивает локальной переменной """ terms = funArgv.replace(" ", "").split(",") # Название локальной переменной nameLocVar = terms[0] if len(terms) == 1: if self.stackGlobalVars: localVars[nameLocVar] = self.stackGlobalVars.pop() else: raise self.raiseErrTemplate( _("error: global variables stack empty")) else: raise self.raiseErrTemplate() replace = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcPrint(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Вывод успешного сообщения """ if funArgv: funArgv = _(funArgv) self.printSUCCESS(funArgv) textTemplateTmp = textTemplateTmp[:resS.start()] + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcWarning(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Вывод сообщения с предупреждением """ if funArgv: funArgv = _(funArgv) self.printWARNING(funArgv) textTemplateTmp = textTemplateTmp[:resS.start()] + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcError(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Вывод сообщения с ошибкой """ if funArgv: funArgv = _(funArgv) self.printERROR(funArgv) textTemplateTmp = textTemplateTmp[:resS.start()] + \ textTemplateTmp[resS.end():] return textTemplateTmp def getElogTimestamp(self): # Получаем время модификации конфигурационного файла curTime = self.getTimeFile(self.fileConfigIni) nameLocVar = "update.timestamp" if self.timeIni != curTime: # читаем переменные из файла self.prevDictIni = self.loadVarsIni(self.fileConfigIni) self.currDictIni = {} self.currDictIni.update(self.prevDictIni) self.timeIni = self.getTimeFile(self.fileConfigIni) if nameLocVar in self.currDictIni.keys(): if self.currDictIni[nameLocVar] is None: return 0 else: val = self.currDictIni[nameLocVar] #used to be encode utf-8 here if val.isdigit(): return int(val) return 0 elogFile = '/var/log/emerge.log' @classmethod def getLastElog(cls): # get last timestamp (of ::completed emerge) entry = EmergeLog(EmergeLogPackageTask()).get_last_time() if entry: return entry.partition(":")[0] else: return "0" def funcElog(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Function for work with emerge.log""" funArgv = funArgv.strip() rePkg = re.compile(r'\) Merging (?:Binary )?\((\S+)::', re.S) replace = "" if funArgv: lastTimestamp = self.getElogTimestamp() for line in reversed(list(readLinesFile(self.elogFile))): timestamp, op, info = line.partition(':') if timestamp.isdigit() and lastTimestamp and \ int(timestamp) < lastTimestamp: break match = rePkg.search(info) if match and match.group(1).startswith(funArgv): pkgInfo = reVerSplitToPV(match.group(1)) if "{CATEGORY}/{PN}".format(**pkgInfo) == funArgv: replace = pkgInfo['PVR'] break else: replace = self.getLastElog() textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp @classmethod def splash_cmd(cls, splash_type): cmd_map = { 'splashutils': "splash=silent,theme:calculate console=tty1", 'plymouth': "splash", } return cmd_map.get(splash_type, "verbose") def funcLivemenu(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): def generateSubmenu(data): base_dn = self.objVar.Get('builder.cl_builder_flash_path') for id, label, iso, vmlinuz_orig, vmlinuz, initrd_orig, initrd, \ xorg, drivers, splash in data: splash = self.splash_cmd(splash) yield ("{id};\n{label};\n/boot/{kernel};\n" "root=live iso-scan/filename={iso};\n" "/boot/{initrd};\n" "init=/linuxrc rd.live.squashimg=livecd.squashfs " "{splash} " "nodevfs quiet noresume;\n".format( id=id, label=label, kernel=vmlinuz, initrd=initrd, splash=splash, iso=iso[len(base_dn):] )) def generateXorg(data): for id, label, iso, vmlinuz_orig, vmlinuz, initrd_orig, initrd, \ xorg, drivers, splash in data: if xorg == "on": yield id def generateVideo(data): for id, label, iso, vmlinuz_orig, vmlinuz, initrd_orig, initrd, \ xorg, drivers, splash in data: if drivers == "on": yield id data = [x for x in self.objVar.Get('builder.cl_builder_image_data') if x] if funArgv == 'submenu': res = "\n".join(generateSubmenu(data)) elif funArgv == 'xorg': res = " ".join(generateXorg(data)) elif funArgv == 'video': res = " ".join(generateVideo(data)) else: res = "" textTemplateTmp = textTemplateTmp[:resS.start()] + res + \ textTemplateTmp[resS.end():] return textTemplateTmp def loadVarsIni(self, iniFileName): """ Читает файл fileName создает и заполняет переменные на основе этого файла Используеться совместно c funcIni """ localVarsIni = {} # получить объект ini файла config = ConfigParser(strict=False) config.read(iniFileName, encoding="utf-8") # получаем все секции из конфигурационного файла allsect = config.sections() if not allsect: return localVarsIni # Заполняем переменные для funcIni for sect in allsect: for name, valueVar in config.items(sect, raw=True): nameVar = "%s.%s" % (sect, name) localVarsIni[nameVar] = valueVar return localVarsIni def getTimeFile(self, fileName): # Получаем время модификации файла nameEnvFile = os.path.split(fileName)[1] if nameEnvFile in self.timeConfigsIni: return self.timeConfigsIni[nameEnvFile] return 0 def funcWallpaper(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Получить наиболее близкое к заданному разрешение из списка обоев """ terms = funArgv.replace(" ", "").split(",") onlyfile = "" if len(terms) == 3: resol, wpath, onlyfile = terms if onlyfile != "file": raise self.raiseErrTemplate( _("third parameter may be 'file' only")) elif len(terms) == 2: resol, wpath = terms else: raise self.raiseErrTemplate( _("function support two or three parameters")) if not resol: resol = "1024x768" _wpath = wpath wpath = pathJoin(self._baseDir, wpath) if os.path.isdir(wpath): re_resol = re.compile("^(\d+)x(\d+)(-\d+(@\d+)?)?$") resol = re_resol.match(resol) if not resol: raise self.raiseErrTemplate( _("the first parameter must be the resolution")) re_resol = re.compile(".*?(\d+)x(\d+).*") res = [(int(x.group(1)), int(x.group(2)), x.group()) for x in [re_resol.search(y) for y in listDirectory(wpath)] if x] width = int(resol.group(1)) height = int(resol.group(2)) gep = sqrt(height ** 2 + width ** 2) k = float(width) / float(height) if res: # наиболее подходящее разрешение: # минимальная разность между пропорциями (отношение ширины к высоте) # минимальная разность между размерами (гепотенуза) near_res = min(res, key=lambda x: (abs(x[0] / float(x[1]) - k), abs(gep - sqrt( x[0] ** 2 + x[1] ** 2)))) replace = near_res[2] if not onlyfile: replace = pathJoin(_wpath, replace) else: replace = "" else: if os.path.exists(wpath): if onlyfile: replace = os.path.basename(_wpath) else: replace = _wpath else: replace = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcIni(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """локальная функция записывает и считывает значение переменной из ini файла ~./calculate/ini.env """ # Создаем директорию if not os.path.exists(self.pathConfigIni): os.makedirs(self.pathConfigIni) os.chown(self.pathConfigIni, self.uid, self.gid) termsRaw = funArgv.split(",") flagFirst = True terms = [] for term in termsRaw: if flagFirst: terms.append(term.replace(" ", "")) flagFirst = False else: val = term.strip() # Флаг (не найдены кавычки) flagNotFoundQuote = True for el in ('"', "'"): if val.startswith(el) and val.endswith(el): terms.append(val[1:-1]) flagNotFoundQuote = False break if flagNotFoundQuote: if not val: terms.append(None) else: terms.append(val) # Название локальной переменной if self.objVar.Get('cl_action') in ('image', 'system'): oldbasedir = self._baseDir old_profile = self.objVar.Get('cl_make_profile') old_config = self.objVar.Get('cl_emerge_config') try: if self.objVar.Get('cl_action') == 'image': self._baseDir = self.objVar.Get('builder.cl_builder_path') else: self._baseDir = self.objVar.Get('cl_chroot_path') dvc = DataVarsConfig(self._baseDir) self.objVar.Set('cl_make_profile', dvc.Get('cl_make_profile'), force=True) self.objVar.Set('cl_emerge_config', dvc.Get('cl_emerge_config'), force=True) self.read_other_ini() finally: self._baseDir = oldbasedir self.objVar.Set('cl_make_profile', old_profile, force=True) self.objVar.Set('cl_emerge_config', old_config, force=True) else: self.read_other_ini() nameLocVar = terms[0] namesVar = nameLocVar.split(".") if len(namesVar) == 1: nameLocVar = "main.%s" % nameLocVar elif len(namesVar) > 2: raise self.raiseErrTemplate() replace = b"" # Получаем время модификации конфигурационного файла curTime = self.getTimeFile(self.fileConfigIni) if 1 <= len(terms) <= 3: if self.timeIni != curTime: # читаем переменные из файла self.prevDictIni = self.loadVarsIni(self.fileConfigIni) self.currDictIni = {} self.currDictIni.update(self.prevDictIni) self.timeIni = self.getTimeFile(self.fileConfigIni) section, op, varname = nameLocVar.partition(".") value = self.upperIni.get(section, varname, raw=True, fallback=None) if value is None: if nameLocVar in self.currDictIni.keys(): if self.currDictIni[nameLocVar] is not None: value = self.currDictIni[nameLocVar] if value is None: value = self.lowerIni.get(section, varname, raw=True, fallback="") else: raise self.raiseErrTemplate() if len(terms) == 1: replace = value.encode("UTF-8") elif len(terms) == 2: # Значение локальной переменной valueLocVar = terms[1] self.currDictIni[nameLocVar] = valueLocVar elif len(terms) == 3: if not terms[2] in ['url', 'purl', 'unicode', 'hexcode']: raise self.raiseErrTemplate( _("the third argument of the function is neither " "'url' or 'purl' or 'unicode'")) if terms[1]: raise self.raiseErrTemplate() if value: #awww yesss, legacy stuff if terms[2] in ('url', 'purl'): replace = (value.encode("UTF-8").__repr__()[1:-1].replace( '\\x', '%').replace(' ', '%20')) if terms[2] == 'purl': replace = replace.replace('/', '%2f') elif terms[2] == 'unicode': replace = value.__repr__()[2:-1] elif terms[2] == 'hexcode': replace = value.__repr__()[2:-1].replace('\\u0','\\x') replace = replace.decode("UTF-8") textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def _replace_hex(self, text): """ Заменить в строке комбинацию \\x00 на символ """ return re.sub(r'\\x([0-9a-fA-F]{2})', lambda x: chr(int(x.group(1), 16)), text) def funcReplace(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """локальная функция заменяет в значении переменной old на new replace(old, new, name_var_template) одинарные и двойные кавычки должны быть обязательно использованы в первых двух аргументах old и new "test\ntest" - преобразование строки (строка с переводом) 'test\ntest' - без преобразования (одна строка) """ def getStrArgv(terms): """Определяет в двойных или одинарных кавычках параметры Результат [(тип, аргумент),...] [("double", arg1).] """ listArgv = [] for term in terms: if term.startswith('"') and term.endswith('"'): replTerms = [(r"\'", "'"), (r'\"', '"'), (r'\n', '\n'), (r'\r', '\r'), (r'\t', '\t'), (r"\\", "\\")] textArgv = term[1:-1] for replTerm in replTerms: textArgv = textArgv.replace(*replTerm) textArgv = self._replace_hex(textArgv) listArgv.append(textArgv) elif term.startswith("'") and term.endswith("'"): listArgv.append(term[1:-1]) else: raise self.raiseErrTemplate() return listArgv terms = [x.strip() for x in funArgv.split(",")] if len(terms) != 3: raise self.raiseErrTemplate() listArgv = getStrArgv(terms[:2]) old = listArgv[0] new = listArgv[1] nameVar = terms[2] # Получаем значение переменной if nameVar in localVars: value = str(localVars[nameVar]) else: try: value = str(self.objVar.Get(nameVar)) except Exception: raise self.raiseErrTemplate( _("template variable '%s' not found") \ % str(nameVar)) replace = value.replace(old, new) textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcEnv(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона env(), выдает значение переменной из env файлов """ terms = funArgv.replace(" ", "").split(",") if len(terms) != 1: raise self.raiseErrTemplate() nameVar = terms[0] if nameVar in self.valuesVarEnv: replace = self.valuesVarEnv[nameVar] else: # Получаем значение из env файлов value = self.objVar.getIniVar(nameVar) if value is False: raise self.raiseErrTemplate(self.getError()) self.valuesVarEnv[nameVar] = value replace = value textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcServer(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона info(), выдает значение опций сервиса из /var/calculate/remote/calculate.env """ terms = funArgv.replace(" ", "").split(",") if len(terms) == 0 or len(terms) > 2: raise self.raiseErrTemplate() nameLocalVar = "" if len(terms) == 2: if not terms[1]: raise self.raiseErrTemplate() nameLocalVar = terms[1] textLine = terms[0] vals = textLine.split(".") if len(vals) != 2: raise self.raiseErrTemplate() if [x for x in vals if not x.strip()]: raise self.raiseErrTemplate() service, option = vals if not service or not option: raise self.raiseErrTemplate() if not self.optionsInfo: # файл /var/calculate/remote/server.env envFile = self.objVar.Get("cl_env_server_path") # получаем словарь всех информационных переменных if self.convObj: optInfo = self.convObj.convert() else: optInfo = self.objVar.getRemoteInfo(envFile) if optInfo is False: raise self.raiseErrTemplate() if optInfo: self.optionsInfo = optInfo replace = '' if service in self.optionsInfo and option in self.optionsInfo[service]: value = self.optionsInfo[service][option] if nameLocalVar: localVars[nameLocalVar] = value else: replace = value elif nameLocalVar: localVars[nameLocalVar] = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcGroups(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона groups(), проверяет нахождение пользователя в группах, если находится выдает '1' """ terms = [x.strip() for x in funArgv.split(",")] groupNames = set(terms) userGroups = set(self.groups) replace = "" if groupNames & userGroups: replace = "1" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcBelong(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): self.printWARNING(_("Function '{funcname}' used by {template} " "is deprecated and will be removed in the future" ).format(funcname="belong", template=nameTemp)) replace = "" return textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] def funcMergepkg(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Функция объединяющая выполнение merge и pkg :param funArgv: :param resS: :param localVars: :param textTemplateTmp: :param nameTemp: :return: """ term = funArgv.replace(" ", "") funcPkg = term replace = self.funcMerge(funcPkg, resS, localVars, "", nameTemp) if replace == "1": replace = self.funcPkg(funcPkg, resS, localVars, "", nameTemp) return textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] def funcMerge(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Belong function use value in first arg and compare it for all values in cl_merge_pkg. If cl_merge_pkg empty or first arg <=cl_belogn_pkg then "1" else "" """ def uniq_warning(message): hashMessage = hashlib.md5(message).digest() if not hashMessage in self.alreadyInformed: self.printWARNING(message) self.alreadyInformed.append(hashMessage) def check_skip(pkgName): varname_map = { HParams.ActionType.Merge: "cl_setup_skip_merge", HParams.ActionType.Patch: "cl_setup_skip_patch", HParams.ActionType.Profile: "cl_setup_skip_profile", } skip_data_varname = varname_map.get(self.currentAction) skip_data = self.objVar.Get(skip_data_varname) pass_location = self.objVar.Get('cl_pass_location') if any(skip_data): for data in skip_data: if len(data) != 2: uniq_warning( _("Wrong entry '{data}' for {var_name}").format( data=",".join(data), var_name=skip_data_varname)) else: pkg, location = data if fnmatch(pkgName, pkg) and (location == "*" or location == pass_location): return True return False term = funArgv.replace(" ", "") funcPkg = term funcPkg, spl, uses = funcPkg.partition('[') funcPkg, spl, slot = funcPkg.partition(":") if uses: uses = uses.rstrip("]") if not funcPkg: funcPkg = self.get_pkgname_by_filename(self.nameTemplate) self.currentBelong = funcPkg self.currentBelongSlot = slot if self.objVar.Get('cl_action') == 'patch': if funcPkg == "%s/%s" % ( self.objVar.Get('core.cl_core_pkg_category'), self.objVar.Get('core.cl_core_pkg_name')): spec_slot = self.objVar.Get('core.cl_core_pkg_slot') spec_slot = spec_slot.partition('/')[0] if not slot or spec_slot == slot: replace = self.objVar.Get('core.cl_core_pkg_version') if uses: from os import environ pkg_use = environ.get("USE", "").split(" ") for use in filter(None, uses.split(',')): if (use[0] == "-" and use[1:] in pkg_use or use[0] != "-" and use not in pkg_use): replace = "" if check_skip(funcPkg): replace = "" else: replace = "" else: replace = "" else: replace = "" pkgs = self.objVar.Get("cl_merge_pkg") if pkgs: pkgs = [x for x in pkgs if x] if slot: for pkg in pkgs: if ":" in pkg: pkg, _, pkgslot = pkg.partition(":") if pkg == funcPkg and pkgslot == slot: replace = "1" break elif pkg == funcPkg: replace = "1" break else: if funcPkg in [x.partition(":")[0] for x in pkgs]: replace = "1" else: replace = "1" if replace == "1" and check_skip(funcPkg): replace = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcList(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона list(). Если первый аргумент является именем локальной или глобальной переменной и значение переменной является списком, выдает элемент списка по второму аргументу индексу. Первый элемент имеет индекс 0 """ terms = funArgv.replace(" ", "").split(",") # У функции должно быть два аргумента if len(terms) != 2: raise self.raiseErrTemplate() # Название локальной или глобальной переменной nameLocVar = terms[0] strIndex = terms[1] try: try: intIndex = int(strIndex) except ValueError: raise TemplatesError(_("'%s' is not a number") % strIndex) if nameLocVar in localVars.keys(): value = localVars[nameLocVar] else: try: value = self.objVar.Get(nameLocVar) except Exception: raise TemplatesError(_("error: variable %s does not exist") % str(nameLocVar)) if not type(value) in (list, tuple): # Значение переменной не список или кортеж raise TemplatesError(_("value of %s is not a list or a tuple") % str(nameLocVar)) if len(value) > intIndex: try: replace = str(value[intIndex]) except IndexError: replace = "" else: replace = "" return textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] except TemplatesError as e: raise self.raiseErrTemplate(str(e)) def funcDisk(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона disk(). Первый аргумент ищется в значении переменной os_disk_install (значение os_install_disk_mount - список точек монтирования при установке) второй аргумент используется для поиска в переменной os_disk_второй_аргумент (значение os_disk_второй_аргумент - список) В os_install_disk_mount ищется первый аргумент, находим его индекс результат - элемент cписка из os_disk_второй_аргумент с этим индексом """ terms = funArgv.replace(" ", "").split(",") # У функции должно быть два аргумента if len(terms) != 2: raise self.raiseErrTemplate() # Название глобальной переменной mountPoint = terms[0] lastElementVar = terms[1] if not mountPoint or mountPoint[:1] != "/": raise self.raiseErrTemplate(_("wrong %s") % lastElementVar) nameVar = "install.os_install_disk_mount" try: try: valueVar = self.objVar.Get(nameVar) except Exception: raise TemplatesError( _("error: variable %s does not exist") % nameVar) nameElementVar = "install.os_install_disk_%s" % lastElementVar try: valueElementVar = self.objVar.Get(nameElementVar) except Exception: # Если переменная не существует nameElementVar = "install.os_disk_%s" % lastElementVar try: valueElementVar = self.objVar.Get(nameElementVar) except Exception: raise TemplatesError(_("wrong %s") % lastElementVar + "\n" + _("error: variable %s does not exist") % nameElementVar) for k, v in ((nameVar, valueVar), (nameElementVar, valueElementVar)): if not type(v) in (list, tuple): # Значение переменной не список или кортеж raise TemplatesError( _("value of %s is not a list or a tuple") % k) if len(valueVar) != len(valueElementVar): raise TemplatesError( _("%(name)s does not match %(nameElement)s in size") % {'name': nameVar, 'nameElement': nameElementVar}) index = None for num, mPoint in enumerate(valueVar): if mountPoint == mPoint: index = num break if index is None: for num, mPoint in enumerate(valueVar): if "/" == mPoint: index = num break if index is None: raise TemplatesError( _("mount point '/' or '/%s' not found " " in the value of variable os_disk_install") % mountPoint) replace = valueElementVar[index] return textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] except TemplatesError as e: raise self.raiseErrTemplate(str(e)) def funcModule(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона module(), выдает значение аттрибута api. аргумент: путь_к_атрибуту - путь к аттрибуту возможные пути: имя_пакета.var.имя_переменной - получаем значение переменной имя_пакета.имя_метода_api - выполнение метода, получение результата all.имя_метода_api - выполнение метода для всех пакетов с api """ current_version = self.objVar.Get('cl_ver') try: if funArgv not in self.objVar.importedModules: importlib.import_module( "calculate.%s.variables" % funArgv) if self.objVar.Get('cl_chroot_path') == "/": replace = current_version else: usrlib = SystemPath(self.objVar.Get('cl_chroot_path')).usrlib module_path = os.path.join( usrlib, "python3.9/site-packages/calculate/%s/variables" % funArgv) if os.path.exists(module_path): pkg = "sys-apps/calculate-utils:3" chroot_version = self.funcPkg( pkg, re.search(".*", pkg), localVars, pkg, nameTemp) t_chroot_version = getTupleVersion(chroot_version) t_current_version = getTupleVersion(current_version) if t_chroot_version < t_current_version: replace = chroot_version else: replace = current_version else: replace = "" except (ImportError, AttributeError): replace = "" return textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] def raiseErrTemplate(self, message=""): """Возвращает ошибки при обработке функций шаблона""" if message: message = "%s\n" % message else: message = "" return TemplatesError( _("error in template %s") % self.nameTemplate + "\n" + \ _("error, template term '%s'") % str(self.functText) + \ " " + message) def applyFuncTemplate(self, textTemplate, nameTemplate): """Применяет функции к тексту шаблона""" # Локальные переменные localVars = {} # Имя обрабатываемого шаблона self.nameTemplate = nameTemplate # Регулярное выражение для поиска функции в шаблоне reFunc = self._reFunc textTemplateTmp = textTemplate flagIniFunc = False writeIniFunc = False def funcSearch(s): minlen = 1 resS = re.search(self._rePreFuncPattern % minlen, s, re.M) while resS and resS.group().count("#-") != (resS.group().count("-#") - resS.group().count("-#-")): minlen = len(resS.group()) - 2 resS = re.search(self._rePreFuncPattern % minlen, s, re.M) if resS: funcblock = resS.group() resS = reFunc.search(s[:resS.end()]) if not resS: raise self.raiseErrTemplate( _("wrong function syntax %s") % funcblock) return resS resS = funcSearch(textTemplateTmp) while resS: mark = textTemplateTmp[resS.start():resS.end()] self.functText = mark[self._deltVarStart:-self._deltVarEnd] funcName, spl, funcEnd = self.functText.partition("(") if funcName in self.namesTemplateFunction: # аргументы функции - '(' аргументы ')' funArgv = funcEnd.rpartition(")")[0] # вызов функции шаблона if "#-" in funArgv and "-#" in funArgv: funArgv = self.applyVarsTemplate(funArgv, nameTemplate) funArgv = self.applyFuncTemplate(funArgv, nameTemplate) textTemplateTmp = self.templateFunction[funcName](self, funArgv, resS, localVars, textTemplateTmp, nameTemplate) resS = funcSearch(textTemplateTmp) if funcName == "ini": if "," in funArgv: writeIniFunc = True flagIniFunc = True else: raise self.raiseErrTemplate( _("function of templates '%s' not found") \ % str(self.functText)) if flagIniFunc: # Очистка файла в случае его ошибочного чтения if not self.prevDictIni and os.path.exists(self.fileConfigIni): with open(self.fileConfigIni, "r+") as FD: FD.truncate(0) FD.seek(0) # Если конф. файл модифицирован шаблоном curTime = self.getTimeFile(self.fileConfigIni) if curTime != self.timeIni: # Считаем переменные из конф. файла self.prevDictIni = self.loadVarsIni(self.fileConfigIni) self.currDictIni.update(self.prevDictIni) self.timeIni = curTime # Если словари переменных не совпадают if self.prevDictIni != self.currDictIni: # Запишем переменные в конфигурационный файл # Создание объекта парсера config = ConfigParser(strict=False) config.read(self.fileConfigIni, encoding="utf-8") comment_block = "\n".join(takewhile(lambda x: x.startswith("#"), readLinesFile( self.fileConfigIni))) for k, v in self.currDictIni.items(): if "." in k: sect, op, k = k.rpartition('.') if v is None: if config.has_section(sect): config.remove_option(sect, k) else: if not config.has_section(sect): config.add_section(sect) config[sect][k] = v for section in (x for x in config.sections() if not config[x]): config.remove_section(section) with codecs.open(self.fileConfigIni, 'wb', 'utf-8', 'ignore') as f: if comment_block: f.write(comment_block) f.write('\n\n') config.write(f) try: oMode = getModeFile(self.fileConfigIni, mode="mode") if oMode != self.modeConfigIni: os.chmod(self.fileConfigIni, self.modeConfigIni) except OSError: pass # читаем переменные из файла self.prevDictIni = self.loadVarsIni(self.fileConfigIni) self.currDictIni.update(self.prevDictIni) self.timeConfigsIni[self.fileConfigIni] = float(time.time()) self.timeIni = self.getTimeFile(self.fileConfigIni) # Меняем владельца в случае необходимости if writeIniFunc and os.path.exists(self.fileConfigIni): uid, gid = getModeFile(self.fileConfigIni, "owner") if self.uid not in (uid, PORTAGEUID) or \ self.gid not in (gid, PORTAGEGID): try: os.chown(self.fileConfigIni, self.uid, self.gid) except OSError: self.setError(_("error") + " " + "'chown %d:%d %s'" % (self.uid, self.gid, self.fileConfigIni)) return textTemplateTmp class ChangedFiles(): """ Object which contains modified files and package """ FILE_MODIFIED, FILE_REMOVED, DIR_CREATED, DIR_REMOVED, DIR_EXISTS = 0, 1, 2, 3, 4 def __init__(self): self.data = {} self.pkgs = set() def _createEntry(self, fn): if not fn in self.data: self.data[fn] = [] def addObj(self, filename, action, pkg, slot=""): if slot: pkgslot = "{}:{}".format(pkg,slot) else: pkgslot = pkg self._createEntry(filename) self.data[filename].append((pkgslot, action)) self.pkgs.add(pkgslot) def getPkgs(self): return self.pkgs def getPkgFiles(self, pkg): return [(x[0], x[1][0][1]) for x in [(y[0], [z for z in y[1] if z[0] == pkg]) for y in self.data.items()] if x[1]] # modes work with configuration file # T_ORIGIN - work with original config file # T_CFG - work with last ._cfg file # T_NEWCFG - new content has difference with (create new ._cfg file) T_ORIGIN, T_CFG, T_NEWCFG = 0, 1, 2 class Template(_file, _terms, _warning, xmlShare, _shareTemplate): """Класс для работы с шаблонами На вход 2 параметра: объект хранения переменных, имя сервиса - не обязательный параметр """ # Название файла шаблона директории templDirNameFile = ".calculate_directory" _titleList = ("Modified", "Processed template files" + ":") titleEnd_old = "For modify this file, create %(conf_path)s.clt template." titleEnd = "To modify this file, create a %(conf_path)s.clt template." protectPaths = [] allContents = {} if "CONFIG_PROTECT" in os.environ: protectPaths = ["/etc"] + [x for x in os.environ["CONFIG_PROTECT"].split(" ") if x.strip()] protectPaths = [os.path.normpath(x) for x in protectPaths] @classmethod def removeComment(cls, text): re_comment = re.compile('(?:|[{symb}]-*)\n'.format( modified=cls._titleList[0], processed=cls._titleList[1], endtitle_old=cls.titleEnd_old % {'conf_path': '.*'}, endtitle=cls.titleEnd % {'conf_path': '.*'}, symb='"#' )) return re_comment.sub('', text) def hasError(self): return self.getError() or self.bHasError or ( self.cltObj and self.cltObj.bHasError) def __init__(self, objVar, servDir=False, dirsFilter=(), filesFilter=(), cltObj=True, cltFilter=True, printWarning=True, printSUCCESS=lambda x: x, printWARNING=lambda x: x, printERROR=lambda x: x, askConfirm=lambda x: x, userProfile=False, dispatchConf=None, critical=False): _file.__init__(self) # совместимость с вызовами из модулей предыдущих версий self.translator = RegexpLocalization("cl_templates3") if userProfile and objVar: objVar.Set('cl_protect_use_set', 'off', force=True) self.protectPaths = objVar.Get('cl_config_protect') self.dispatchConf = dispatchConf self.changedFiles = ChangedFiles() self.printSUCCESS = printSUCCESS self.formatFactory = FormatFactory(self) self.printERROR = printERROR self.critical = critical self.postmergePkgs = [] self._locked_packages = {} if objVar and objVar.Get("cl_merge_pkg"): self._locked_packages = { x.partition(":")[0]: None for x in objVar.Get('cl_merge_pkg') if x } self.postmergeFile = "/var/lib/calculate/-postmerge" self.bHasError = False if printERROR: def newSetError(s): self.printERROR(s) self.bHasError = True self.setError = newSetError self.printWARNING = printWARNING self.askConfirm = askConfirm self.cltObj = None self.functObj = None self.mounts = None # Бесконченое ожидание завершения emerge если выполняется настройка пакетов #if objVar: # if (not objVar.Get('cl_ebuild_phase') # and emerge_running() # and objVar.GetBool('install.ac_install_merge')): # self.printWARNING(_("Waiting for emerge to be complete")) # while emerge_running(): # time.sleep(1) # Предупреждения # self.warning = [] # Печатать ли предупреждения о корневых шаблонах без cl_name==pkg self.printWarning = printWarning # Необрабатываемые директории self.dirsFilter = dirsFilter # Необрабатываемые файлы self.filesFilter = filesFilter _file.__init__(self) # Словарь для создания объектов новых классов по образцу self.newObjProt = {'proftpd': 'apache'} # Заголовок title self.__titleHead = "--------------------------------------\ ----------------------------------------" self._titleBody = "" # Условия self._reTermBloc = re.compile( "#\?(?P(?:[a-z0-9_]+\.)?[a-zA-Z0-9\-_]+)" "(?P\(((?:#-|-#|%s)+|)\))?" "(?P[><=!&\|]+" "(?:#-|-#|[><=!\|&\(\)%s])*)#" "\n?(?P.+?\n*?)\n?#(?P=rTerm)#(?P[ ,\t]*\n?)" % (self._reFunctionArgvText, self._reFunctionArgvInSquareBrackets), re.M | re.S) # Объект с переменными self.objVar = objVar self.recalculateBaseDir() # Последняя часть директории шаблона (имя сервиса: samba, mail) self._servDir = servDir if self._servDir: if self._servDir[0] != "/": self._servDir = "/" + self._servDir if self._servDir[-1] != "/": self._servDir += "/" self._servDir = os.path.split(self._servDir)[0] # Созданные директории self.createdDirs = [] # Примененные файлы self.filesApply = [] # номер обрабатываемого файла self.numberProcessTempl = 0 # имя текущей программы _nameProgram = "Calculate Utilities" # версия текущей программы _versionProgram = self.objVar.Get("cl_ver") # имя и версия текущей программы self.programVersion = "%s %s" % (_nameProgram, _versionProgram) # Словарь директорий с количеством файлов шаблонов self.dictTemplates = {} # Общее количество шаблонов self.allTemplates = 0 # Объект функций шаблона self.functObj = templateFunction(self.objVar) self.functObj.printSUCCESS = self.printSUCCESS self.functObj.printWARNING = self.printWARNING self.functObj.printERROR = self.printERROR if self.printERROR: self.functObj.setError = self.printERROR self.functObj.askConfirm = self.askConfirm # Метод применения функций к шаблонам self.applyFuncTemplate = self.functObj.applyFuncTemplate # Объект для определения типа файла шаблона self.typeFileObj = typeFile() # Глобальный словарь обработанных шаблонов файлов # {путь к конф. файлу:[имена шаблонов] ...} self.dictProcessedTemplates = {} if cltObj is True: # Объект templateClt self.cltObj = templateClt(self.objVar, self.postmergePkgs, printSUCCESS=self.printSUCCESS, printERROR=self.printERROR, printWARNING=self.printWARNING, askConfirm=self.askConfirm, critical=self.critical) elif cltObj: # Объект templateClt self.cltObj = cltObj else: # Объект templateClt self.cltObj = None # Фильтровать ли шаблоны clt по конфигурационным файлам обычных шаблонов self.cltFilter = cltFilter # autoupdate файлы self.autoUpdateFiles = [] self.autoUpdateDirs = [] self.protectedFiles = [ pathJoin(self._baseDir, x) for x in self.objVar.Get('main.cl_env_path') ] + [self.functObj.fileConfigIni] # список выполненных файлов self.executedFiles = [] def recalculateBaseDir(self): """Recalculate basedir and homedir""" # Базовая директория переноса шаблонов "/mnt/calculate" или "/" и.т.д self._baseDir = pathJoin(self.objVar.Get("cl_chroot_path"), self.objVar.Get("cl_root_path")) self._baseDir = os.path.normpath(self._baseDir) self.uid, self.gid, self.homeDir = self.getDataUser() # Домашняя директория, плюс базовая директория self.homeDir = pathJoin(self._baseDir, self.homeDir) if self.cltObj: self.cltObj.recalculateBaseDir() if self.functObj: self.functObj.recalculateBaseDir() def _addFile(self, filesApl, pkg=None, slot=None): """ Add files to ChangedFiles """ for fn in filesApl: if os.path.exists(fn): self.changedFiles.addObj( fn, ChangedFiles.FILE_MODIFIED, pkg or self.functObj.currentBelong, slot or self.functObj.currentBelongSlot) else: self.changedFiles.addObj( fn, ChangedFiles.FILE_REMOVED, pkg or self.functObj.currentBelong, slot or self.functObj.currentBelongSlot) def execute_command(self, cmd, lang): env = dict(os.environ) env['TERM'] = "linux" env['EINFO_QUIET'] = "yes" return process(cmd, lang=lang, envdict=dict(os.environ)) def executeTemplate(self, code, execPath): """Execute template""" p = self.execute_command(execPath, self.objVar.Get('os_locale_locale')) if "/bin/bash" in code.partition('\n')[0]: p.write("""function translate() { gettext -d cl_template "$*" } """) p.write(code) p.pipe.stdin.close() for line in p.readByLine(): if line: line = self.translator.translate(line) self.printSUCCESS(line.strip()) p.pipe.wait() if p.success(): self.executedFiles.append((code, execPath)) errdata = p.readerr().rstrip() if errdata: for line in errdata.split('\n'): if line: line = self.translator.translate(line) self.printWARNING(line.strip()) return True else: errdata = p.readerr().rstrip() if errdata: for line in errdata.split('\n'): if line: line = self.translator.translate(line) self.printERROR(line.strip()) return False def __octToInt(self, strOct): """Преобразование восьмеричного в целое (ввод строка, вывод число)""" if strOct: try: # res = string.atoi(strOct, 8) res = int(strOct, 8) except ValueError: self.setError(_("Invalid oct value: ") + str(strOct)) return False return res else: self.setError(_("Empty oct value")) return False def getTemplateType(self): """выдать тип шаблона (text, bin)""" return self.getFileType(self.nameFileTemplate) def getFileType(self, fileName): """выдать тип файла (text, bin)""" isBin = self.typeFileObj.isBinary(fileName) if isBin is True: typeTemplate = "bin" elif isBin is False: typeTemplate = "text" else: self.setError(_("ERROR") + ": getFileType()") self.setError(isBin) return False return typeTemplate def createDir(self, dirName, mode=False, uid=False, gid=False): """Создает директорию""" if os.access(dirName, os.F_OK): return True else: dMode = False prevDir, tmpSubdir = os.path.split(dirName) createDirs = [] while not os.access(prevDir, os.F_OK) and prevDir: createDirs.append(prevDir) prevDir = os.path.split(prevDir)[0] try: dUid, dGid = getModeFile(prevDir, "owner") except OSError: self.setError(_("No access to the directory: ") + prevDir) return False if not mode is False: dMode = mode if not uid is False: dUid = uid if not gid is False: dGid = gid createDirs.reverse() for nameDir in createDirs: try: if dMode: os.mkdir(nameDir, dMode) os.chmod(nameDir, dMode) else: os.mkdir(nameDir) self.chownConfDir(nameDir, dUid, dGid, None) except OSError: self.setError(_("Failed to create the directory: ") + nameDir) return False try: if dMode: os.mkdir(dirName, dMode) os.chmod(dirName, dMode) else: os.mkdir(dirName) self.chownConfDir(dirName, dUid, dGid, None) createDirs.append(dirName) except OSError: self.setError(_("Failed to create the directory: ") + dirName) return False return createDirs reBlock = re.compile( "#\?(?P(?:[a-z0-9_]+\.)?[a-zA-Z0-9\-_]+).*?#(?P=rTerm)#" "(?:[ ,\t]*\n?)", re.S | re.M) def applyTermsTemplate(self, textTemplate, nameTemplate): """ Применяет условия, к условным блокам текста """ def function(text): """Функция обработки функций в заголовке""" return self.applyFuncTemplate(text, nameTemplate) def searchBlock(s): resS = self.reBlock.search(s) if resS: funcblock = resS.group() resS = self._reTermBloc.search(textTemplateTmp[:resS.end()]) if not resS: raise TemplatesError( "Wrong conditional block: %s" % funcblock) return resS textTemplateTmp = textTemplate resS = searchBlock(textTemplateTmp) while resS: mark = resS.group(0) body = resS.group("body") end = resS.group("end") notbody = "" elseblock = "#!%s#" % resS.group("rTerm") if elseblock in body: data = re.split("\n?%s\n?" % elseblock, body) body = data[0] notbody = "".join(data[1:]) + end body = body + end parent = resS.group("func") if not parent: parent = "" term = resS.group("rTerm") + parent + \ resS.group("lTerm") if self._equalTerm(term, _("invalid template content: ") + \ nameTemplate, function): textTemplateTmp = textTemplateTmp.replace(mark, body) else: textTemplateTmp = textTemplateTmp.replace(mark, notbody) resS = searchBlock(textTemplateTmp) return textTemplateTmp def getNeedTemplate(self, fileTemplate): """Применяем правила к названию файла""" dirP, fileP = os.path.split(fileTemplate) if fileP: spFile = fileP.split("?") if len(spFile) > 1: flagTrue = False for term in spFile[1:]: if self._equalTerm(term, _("invalid template name: ") + \ fileTemplate): flagTrue = True break if flagTrue: return True else: return False else: return True else: self.setError(_("invalid template name: ") + str(fileTemplate)) return False def getTitle(self, comment, commentList, configPath=""): """Выдает заголовок шаблона ( версия и.т.д)""" origConfigPath = PkgContents.reCfg.sub("/", configPath) if self._baseDir != "/": lenBaseDir = len(self._baseDir) commentList = [x[lenBaseDir:] if x.startswith(self._baseDir) else x for x in commentList] if configPath and self.protectPaths: for protectPath in self.protectPaths: if self._baseDir != "/": lenBaseDir = len(self._baseDir) if len(configPath) > lenBaseDir and \ configPath[:lenBaseDir] == self._baseDir: configPath = configPath[lenBaseDir:] if configPath.startswith(protectPath + "/"): if not any(origConfigPath.endswith(x) for x in ("/calculate.env", "/ini.env", "/custom")): commentList = commentList + \ [self.titleEnd % { 'conf_path': origConfigPath}] break if comment: commentFirst = comment commentInsert = comment commentLast = comment flagList = False # В случае открывающего и закрывающего комментария if type(comment) == tuple and len(comment) == 2: commentFirst = comment[0] commentInsert = "" commentLast = comment[1] flagList = True if flagList: self._titleBody = commentFirst + "\n" else: self._titleBody = commentFirst + self.__titleHead + "\n" z = 0 flagFirst = True for com in list(self._titleList) + [""] * (len(commentList)): if com: if flagFirst: self._titleBody += commentInsert + " " + com + " " + \ self.programVersion + "\n" flagFirst = False else: self._titleBody += commentInsert + " " + com + "\n" else: self._titleBody += commentInsert + " " + \ commentList[z] + "\n" z += 1 if flagList: self._titleBody += commentLast + "\n" else: self._titleBody += commentLast + self.__titleHead + "\n" return self._titleBody else: return "" def changeMergePackage(self, package): return True def numberAllTemplates(self, number): """Количество шаблонов Вызов происходит перед наложением шаблонов в момент вызова в number находится количество обрабатываемых файлов Наследуемая функция Используется для отображения прогресса при наложениии шаблонов """ return True def numberProcessTemplates(self, number): """Номер текущего обрабатываемого шаблона Вызов происходит при наложении шаблона в момент вызова в number находится номер обрабатываемого шаблона Наследуемая функция Используется для отображения прогресса при наложениии шаблонов """ return True def templateModify(self): """ Files which created by apping templates """ return True def fixNameFileConfig(self, origfilename): """Support ._cfg0000 files for postinst""" # if self.objVar.Get('cl_ebuild_phase') != 'postinst': # return origfilename directory, filename = os.path.split(origfilename) for i in range(0, 9999): if not os.path.exists(os.path.join(directory, "._cfg%04d_%s" % (i, filename))): if i: filename = os.path.join(directory, "._cfg%04d_%s" % (i - 1, filename)) #if not os.path.exists(origfilename): # return origfilename # origstat = os.stat(origfilename)[stat.ST_CTIME] # newstat = os.stat(filename)[stat.ST_CTIME] self.configMode = T_CFG return filename return origfilename return origfilename def getHeaderText(self, text, binary=False): textLines = text.splitlines() paramLine = "" if textLines: textLine = textLines[0] reg = "\s*#\s*calculate\s+\\\\?|\s*#\s*calculate\\\\?$" if not binary \ else b"\s*#\s*calculate\s+\\\\?|\s*#\s*calculate\\\\?$" rePar = re.compile(reg, re.I) reP = rePar.search(textLine) if reP: reg = r"\A([^\\\n]*\\\n)+[^\n]*\n*" if not binary \ else rb"\A([^\\\n]*\\\n)+[^\n]*\n*" reLns = re.compile(reg, re.M) reLs = reLns.search(text) if reLs: paramLine = text[reP.end():reLs.end()] if binary: paramLine = paramLine.decode("UTF-8") paramLine = paramLine.replace("\\", " ") else: paramLine = textLine[reP.end():] if binary: paramLine = paramLine.decode("UTF-8") return paramLine def getTemplateDirs(self, dirsTemplates): """Check template variable cl_name in first directories and files""" skipDirs = [] skipTemplates = [] debug = False for dirsTemplate in dirsTemplates: filesAndDirs = [os.path.join(dirsTemplate, x) for x in listDirectory(dirsTemplate)] for dirFile in filesAndDirs: if os.path.isdir(dirFile): flagDir = True templatePath = os.path.join(dirFile, self.templDirNameFile) if not os.path.isfile(templatePath): skipDirs.append(dirFile) continue else: flagDir = False templatePath = dirFile if os.path.isfile(templatePath): if self.getFileType(templatePath) == "bin": skipTemplates.append(dirFile) else: with open(templatePath, "r") as f: textTemplate = f.read() if textTemplate: headerLine = self.getHeaderText(textTemplate) if headerLine: if not debug and \ not "cl_name==" in headerLine and \ not "ac_" in headerLine: if flagDir: skipDirs.append(dirFile) else: skipTemplates.append(dirFile) else: if flagDir: skipDirs.append(dirFile) else: skipTemplates.append(dirFile) else: skipTemplates.append(dirFile) if skipDirs or skipTemplates: # print warning self.printWARNING(_("No conditions for checking the value of " "an action variable")) skipDirTemplates = [] for skipDir in skipDirs: skipTempl = os.path.join(skipDir, self.templDirNameFile) if os.path.isfile(skipTempl): skipDirTemplates.append(skipTempl) if skipTemplates or skipDirTemplates: self.printWARNING(_("Skipped templates:")) for skipTemplate in skipTemplates + skipDirTemplates: self.printWARNING(" " * 6 + skipTemplate) if skipDirs: self.printWARNING(_("Skipped directories:")) for skipDir in skipDirs: self.printWARNING(" " * 6 + skipDir) self.printWARNING("") self.printWARNING(_("Headers of directory templates and headers " "of files on the first level should include " "an action variable")) self.printWARNING(_("Example:")) self.printWARNING("# Calculate ac_install_merge==on") return skipDirs + skipTemplates def lock_package(self, pkg, fuser=None): pkg = pkg.partition(":")[0] if pkg not in self._locked_packages: category, _, pkg = pkg.partition("/") pkglockfile = ("/var/calculate/tmp/portage/" "{}/.{}.calculate_lockfile".format(category, pkg)) ipcfile = ("/var/db/pkg/" "{}/.{}*.portage_lockfile".format(category, pkg)) if os.path.exists(pkglockfile): fuser = fuser or FUser() if any(fuser.search(ipcfile)): return False l = Locker(fn=pkglockfile, timeout=0) if l.acquire(): self._locked_packages[pkg] = l return True return False return True def unlock_packages(self): for pkg, l in self._locked_packages.items(): if l: l.remove() self._locked_packages = {} @catch_no_space_left @post_unlock_packages def applyTemplates(self, progress=True, rerun=True): """Применяет шаблоны к конфигурационным файлам""" def createDictTemplates(path, prefix, dictTemplates): """Создает словарь {"директория":"кол-во шаблонов" ...} и считает общее количество шаблонов """ # Количество шаблонов self.allTemplates += 1 dirTemplate = os.path.split(path)[0] while True: if dirTemplate in dictTemplates.keys(): dictTemplates[dirTemplate] += 1 else: dictTemplates[dirTemplate] = 1 if dirTemplate == prefix: break dirTemplate = os.path.split(dirTemplate)[0] return dictTemplates self.clearErrors() self.clearWarnings() if not self.objVar.defined("cl_template_path_use"): self.setError(_("undefined variable: ") + "cl_template_path_use") return False dirsTemplates = [ os.path.realpath(x) for x in self.objVar.Get("cl_template_path_use")] # Созданные директории self.createdDirs = [] # Примененные файлы self.filesApply = [] # Номер применяемого шаблона self.numberProcessTempl = 0 # Словарь директорий с количеством файлов шаблонов self.dictTemplates = {} # Количество шаблонов self.allTemplates = 0 # Установка по умолчанию аттрибутов для функциии шаблонов ini() # Время доступа к конфигурационному файлу функции шаблона ini() self.functObj.timeIni = -1 # Первоначальный словарь переменных для ini() self.functObj.prevDictIni = {} # Текущий словарь переменных для ini() self.functObj.currDictIni = {} self.functObj.currentBelong = "" self.functObj.currentBelongSlot = "" self.functObj.currentAction = HParams.ActionType.Merge # Словарь времен модификации env файлов для ini() self.functObj.timeConfigsIni = {} if self._servDir: tmpDirsTemplates = [] for dirP in dirsTemplates: dirTempl = dirP + self._servDir if os.access(dirTempl, os.F_OK): # Если директория существует tmpDirsTemplates.append(dirTempl) dirsTemplates = tmpDirsTemplates scanObj = scanDirectory() scanObj.processingFile = lambda x, y: createDictTemplates(x, y, self.dictTemplates) # Считаем количество шаблонов dirsTemplatesExists = [x for x in dirsTemplates if os.path.exists(x)] if not dirsTemplatesExists and not self.cltObj: return self.createdDirs, self.filesApply # check cl_name in first template dirs and files skipTemplates = self.getTemplateDirs(dirsTemplatesExists) # if not os.environ.get("EBUILD_PHASE","") and progress: # for dirTemplate in dirsTemplatesExists: # scanObj.scanningDirectory(dirTemplate) # Считаем количество шаблонов clt if self.cltObj: # Считаем количество шаблонов clt self.cltObj.countsNumberTemplates() # не считать количество файлов в объекте self.cltObj self.cltObj.checkNumberTemplate = False # начальный номер clt шаблона self.cltObj.numberProcessTempl = self.allTemplates # метод показывающий номер clt шаблона self.cltObj.numberProcessTemplates = self.numberProcessTemplates # метод показывающий номер clt шаблона self.cltObj.templateModify = self.templateModify # общее количество шаблонов self.allTemplates += self.cltObj.allTemplates self.cltObj.allTemplates = self.allTemplates self.numberAllTemplates(self.allTemplates) # Обрабатываем шаблоны locationPath = dict(self.objVar.ZipVars('main.cl_template_path', 'main.cl_template_location')) for dirTemplate in dirsTemplatesExists: self.objVar.Set('cl_pass_location', locationPath.get(dirTemplate, dirTemplate), force=True) if self.scanningTemplates(dirTemplate, skipTemplates=skipTemplates) is False: break if self.cltObj: self.objVar.Set('cl_pass_location', 'clt', True) # Созданные директории self.cltObj.createdDirs = self.createdDirs # Примененные файлы self.cltObj.filesApply = self.filesApply # Словарь директорий с количеством файлов шаблонов self.cltObj.dictTemplates = self.dictTemplates # Количество шаблонов self.cltObj.allTemplates = self.allTemplates # Установка по умолчанию аттрибутов для функциии шаблонов ini() # Время доступа к конфигурационному файлу функции шаблона ini() self.cltObj.functObj = self.functObj self.cltObj.protectedFiles = self.protectedFiles # Метод применения функций к шаблонам self.cltObj.applyFuncTemplate = self.functObj.applyFuncTemplate # Словарь примененных файлов шаблонов self.cltObj.dictProcessedTemplates = self.dictProcessedTemplates self.cltObj.changedFiles = self.changedFiles self.cltObj.autoUpdateFiles = self.autoUpdateFiles self.cltObj.autoUpdateDirs = self.autoUpdateDirs if self.cltFilter: # Шаблоны + .clt которые будут применены self.cltObj.filterApplyTemplates = {} if self.objVar.Get('cl_merge_set') == "on": for pkg in self.objVar.Get('cl_merge_pkg'): if not pkg: continue atom = list(sorted(getInstalledAtom(pkg))) if atom: pkgContents = PkgContents("{CATEGORY}/{PF}".format( **atom[-1])) for filename in pkgContents.content.keys(): if not filename in self.cltObj.filterApplyTemplates: self.cltObj.filterApplyTemplates[ filename] = [] self.cltObj.filterApplyTemplates[ filename].append(pkg) for filename, pkgs in self.changedFiles.data.items(): filename = PkgContents.reCfg.sub("/", filename) if not filename in self.cltObj.filterApplyTemplates: self.cltObj.filterApplyTemplates[filename] = [] pkgs = [x for x in [z[0] for z in pkgs] if x not in self.cltObj.filterApplyTemplates[filename]] self.cltObj.filterApplyTemplates[filename].extend(pkgs) old_mod = self.objVar.defaultModule try: self.objVar.defaultModule = "install" self.cltObj.applyTemplatesClt() finally: self.objVar.defaultModule = old_mod if ((self.objVar.Get('cl_merge_pkg') or self.objVar.Get('cl_action') in ( "sync", "domain", "server_setup", "undomain")) and self.objVar.Get('cl_merge_pkg_new')): skip_pkglist = [] if self.objVar.Get('cl_ebuild_phase'): new_pkglist = [] for pkgn in self.objVar.Get('cl_merge_pkg_new'): if not pkgn: continue if self.lock_package(pkgn): new_pkglist.append(pkgn) else: skip_pkglist.append(pkgn) self.objVar.Set('cl_merge_pkg_new', new_pkglist, force=True) else: new_pkglist = self.objVar.Get('cl_merge_pkg_new') if new_pkglist: self.objVar.Set('cl_root_path', self.objVar.Get('cl_root_path_next'), force=True) self.recalculateBaseDir() self.objVar.Set('cl_merge_pkg_pass', list( set(self.objVar.Get('cl_merge_pkg_pass')) | set(self.objVar.Get('cl_merge_pkg')) | set(skip_pkglist) | set(self.objVar.Get('cl_merge_pkg_new'))), force=True) self.objVar.Set('cl_merge_pkg', self.objVar.Get('cl_merge_pkg_new'), force=True) self.objVar.Set('cl_merge_pkg_new', [], force=True) createdDirs = self.createdDirs filesApply = self.filesApply self.changeMergePackage(self.objVar.Get('cl_merge_pkg')) self.applyTemplates(rerun=False) createdDirs.extend(self.createdDirs) filesApply.extend(self.filesApply) self.filesApply = filesApply self.createdDirs = createdDirs if rerun: if self.cltObj: self.queueExecute.extend(self.cltObj.queueExecute) for processor, text, nameTemplate in self.queueExecute: if not self.executeTemplate(text, processor): self.setError(_("Failed to execute") + _(": ") + nameTemplate) return False self.queueExecute = [] self.filesApply = list(set(self.filesApply)) if (self.objVar.Get('cl_root_path') == '/' and self.objVar.Get('cl_chroot_path') == '/'): post_script = [] if any("/etc/env.d" in x for x in self.filesApply): post_script.append( "LANG=C /usr/sbin/env-update --no-ldconfig") post_script.append("source /etc/profile") root_type = self.objVar.Get('install.os_install_root_type') if (root_type != "livecd" and any("/etc/locale.gen" in x for x in self.filesApply)): post_script.append("/usr/sbin/locale-gen &>/dev/null") self.executeTemplate("\n".join(post_script), "/bin/bash") if self.objVar.Get('cl_protect_use_set') == 'on': self.updateProtectedFiles() if self.objVar.Get('cl_verbose_set') == 'on' and self.filesApply: self.verboseOutput([x for x in self.filesApply if not x.endswith('/ini.env')]) self.objVar.Set('cl_merge_pkg_pass', [], force=True) self.objVar.Set('cl_merge_pkg_new', [], force=True) self.objVar.Set('cl_merge_pkg', [], force=True) return self.createdDirs, self.filesApply def verboseOutput(self, filesApply): """ Verbose output applied templates """ if not filesApply: return self.printWARNING(_("Calculate Utilities have changed files") + _(":")) reGrey = re.compile(r"\._cfg\d{4}_") rootPath = self.objVar.Get('cl_root_path') for fn in sorted(list(set(filesApply))): if rootPath != '/' and self.objVar.Get('cl_action') == 'patch' and \ fn.startswith(rootPath): fn = fn[len(rootPath) + 1:] if reGrey.search(fn): self.printSUCCESS(" " * 5 + \ "%s" % fn) else: self.printSUCCESS(" " * 5 + fn) def updateProtectedFiles(self): """ Update ._cfg0000 files """ if self.objVar.Get('cl_ebuild_phase') == 'compile': return chrootPath = self.objVar.Get('cl_chroot_path') cfgs = getCfgFiles(self.objVar.Get('cl_config_protect'), prefix=chrootPath) reverse_cfgs = {y[0][1]: x for x, y in cfgs.items()} autoUpdateDict = {} get_digest = lambda x: hashlib.md5(readFile(x, binary=True)).hexdigest() for pkg in list(set(filter(None, list(self.changedFiles.getPkgs()) + self.objVar.Get('cl_merge_pkg')))): atom = list(sorted(getInstalledAtom(pkg, prefix=chrootPath))) if atom: pkgContents = PkgContents("{CATEGORY}/{PF}".format( **atom[-1]), prefix=chrootPath) protected = [] checked_map = {} for filename, action in self.changedFiles.getPkgFiles(pkg): origFn = pkgContents.origFileName(filename) if origFn in self.protectedFiles: pkgContents.removeObject(filename) protected.append(origFn) continue if action in (ChangedFiles.FILE_MODIFIED, ChangedFiles.DIR_CREATED, ChangedFiles.DIR_EXISTS): orig_filename = reverse_cfgs.get(filename, None) if orig_filename: checked_map[orig_filename] = ( get_digest(filename) == get_digest( orig_filename)) # не давать править CONTENTS if (orig_filename is None or not checked_map[orig_filename]): pkgContents.addObject(filename) elif action in (ChangedFiles.FILE_REMOVED, ChangedFiles.DIR_REMOVED): pkgContents.removeObject(filename) files = set([pathJoin(chrootPath, x) for x in pkgContents.content.keys()] + protected) if (self.objVar.Get('cl_dispatch_conf') != 'usenew' and self.objVar.Get('cl_autoupdate_set') != "on"): notUpdate = files - set(self.autoUpdateFiles) files &= set(self.autoUpdateFiles) for filename in list(notUpdate & set(cfgs.keys())): equal = checked_map.get( filename, get_digest(filename) == get_digest( cfgs[filename][0][1])) if equal: files.add(filename) for filename in list(files & set(cfgs.keys())): # get ctime from orig filename # if os.path.exists(filename): # ctime = os.stat(filename).st_ctime # else: # ctime = 0 # if orig filename older that .cfg cfgs[filename].sort(reverse=True) # if ctime < cfgs[filename][0][0]: try: with open(filename, 'w') as f: f.write(readFile(cfgs[filename][0][1])) self.copy_mod_own(cfgs[filename][0][1], filename) except Exception as e: self.printERROR(str(e)) self.printWARNING( _("Failed to copy {ffrom} to {fto}").format( ffrom=cfgs[filename][0][1], fto=filename)) continue autoUpdateDict[cfgs[filename][0][1]] = filename for mtime, fn in cfgs[filename]: try: if os.path.exists(fn): os.unlink(fn) except OSError: self.printWARNING(_("Failed to remove %s") % fn) try: pkgContents.writeContents() except IOError: self.printWARNING(_("Failed to modify %s contents") % pkg) self.filesApply = [autoUpdateDict.get(x, x) for x in self.filesApply] if [x for x in self.filesApply if "._cfg" in x]: if self.objVar.Get('cl_ebuild_phase') != '': self.printWARNING(_("Some config files need updating. " "Perform run dispatch-conf.")) if self.dispatchConf and \ self.objVar.Get( 'cl_dispatch_conf') == 'dispatch' and \ self.objVar.Get('cl_ebuild_phase') == '': self.dispatchConf(self.filesApply) def scanningTemplates(self, scanDir, prefix=None, flagDir=False, optDir=None, skipTemplates=()): """Сканирование и обработка шаблонов в директории scanDir""" if optDir is None: # ключи: HParams.OptDir.{Path,Skip,Autoupdate} optDir = {} ret = True ##scanDir == '/var/db/repos/calculate/profiles/templates/3.6/2_ac_install_merge/media-sound/pulseaudio/daemon.conf' if not prefix: prefix = os.path.realpath(scanDir) if not flagDir: # проверка корневой директории retDir = self.processingDirectory(scanDir, scanDir, optDir) if retDir is None: return None elif retDir is False: return False pathDir, objHead = retDir optDir[HParams.OptDir.Path] = pathDir if not objHead is True: if objHead.typeAppend == HParams.AppendParams.Skip: # Установка опции пропуска директории optDir[HParams.OptDir.Skip] = True if (HParams.Autoupdate in objHead.params or self.objVar.Get('cl_autoupdate_set') == 'on'): optDir[HParams.OptDir.Autoupdate] = True if flagDir or stat.S_ISDIR(os.lstat(str(scanDir))[stat.ST_MODE]): if not os.access(scanDir, os.R_OK | os.X_OK): self.printWARNING(_("Failed to read templates directory %s") % scanDir) return False for fileOrDir in sorted(listDirectory(scanDir)): absPath = os.path.join(scanDir, fileOrDir) if skipTemplates and absPath in skipTemplates: continue stInfo = os.lstat(str(absPath)) statInfo = stInfo[stat.ST_MODE] prevModule = self.objVar.defaultModule prevBelong = self.functObj.currentBelong prevBelongSlot = self.functObj.currentBelongSlot prevAction = self.functObj.currentAction try: if stat.S_ISREG(statInfo): if not self.processingFile(absPath, prefix, optDir): ret = False continue elif stat.S_ISDIR(statInfo): # Обработка директории retDir = self.processingDirectory(absPath, prefix, optDir) if retDir is None: continue elif retDir is False: ret = False break # Опции следующей директории optNextDir = {} pathDir, objHead = retDir optNextDir[HParams.OptDir.Path] = pathDir if objHead is not True: if objHead.typeAppend == HParams.AppendParams.Skip: # Установка опции пропуска директории optNextDir[HParams.OptDir.Skip] = True if (HParams.Autoupdate in objHead.params or self.objVar.Get( 'cl_autoupdate_set') == 'on'): optNextDir[HParams.OptDir.Autoupdate] = True ret = self.scanningTemplates(absPath, prefix, True, optNextDir) if objHead is not True: if has_any(objHead.params, HParams.ServiceControl): self.doServiceControl(objHead.params) if ret is False: break except TemplatesError as e: self.clearErrors() if self.critical: raise else: self.printWARNING(str(e)) finally: self.objVar.defaultModule = prevModule self.functObj.currentBelong = prevBelong self.functObj.currentBelongSlot = prevBelongSlot self.functObj.currentAction = prevAction return ret def processingFile(self, path, prefix, optFile): """Обработка в случае шаблона файла""" self.numberProcessTempl += 1 self.numberProcessTemplates(self.numberProcessTempl) # Пропуск шаблонов директорий if self.templDirNameFile == os.path.split(path)[1]: return True # Проверка на переменные в названии файла if not self.getNeedTemplate(path): if self.getError(): return False return True if self.getError(): return False nameFileConfig = path.partition(prefix)[2] # файл в системе без условий nameFileConfig = "/".join((x.split("?")[0] for x in nameFileConfig.split("/"))) # Записываем в переменную обрабатываемый файл self.objVar.Set("cl_pass_file", os.path.basename(nameFileConfig)) self.headerParams = None #debug these templates: #2_ac_install_merge/xfce-base/thunar/uca.xml (ELEMENT goes in before header for some reason (check add comment)) filesApl = self.joinTemplate(path, nameFileConfig, optFile) if self.headerParams: if has_any(self.headerParams, HParams.ServiceControl): self.doServiceControl(self.headerParams) if self.getError(): return False if filesApl: # Настоящее имя конфигурационного файла nameFileConfig = filesApl[0] # Пишем время модификации *.env файлов if nameFileConfig.endswith(".env"): nameEnvFile = os.path.basename(nameFileConfig) self.functObj.timeConfigsIni[nameEnvFile] = float(time.time()) self.filesApply += filesApl if filesApl: self._addFile(filesApl) return True def processingDirectory(self, path, prefix, opt): """Обработка в случае директории если возвращаем None то пропуск дир.""" if path.endswith("/.git"): return None # Файл шаблона директории if not os.access(path, os.R_OK | os.X_OK): self.printWARNING(_("Failed to read templates directory %s") % path) return None dirInfoFile = os.path.join(path, self.templDirNameFile) newDir = pathJoin(self._baseDir, path.partition(prefix)[2]) newDir = "/".join((x.split("?")[0]) for x in newDir.split("/")) # Применяем шаблон pathDir, objHeadDir, createdDirs = \ self.getApplyHeadDir(newDir, dirInfoFile, opt) if createdDirs: self.createdDirs += createdDirs if os.path.isfile(pathDir): self.printWARNING(_("{dirpath} is a file").format(dirpath=pathDir)) self.printWARNING( _("templates in {tempath} are skipped").format(tempath=path)) return None if objHeadDir: return pathDir, objHeadDir else: if self.getError(): return False # Добавление количества файлов в пропущенной директории if path in self.dictTemplates.keys(): self.numberProcessTempl += self.dictTemplates[path] return None def setRebuildVersion(self, prev_ver): if "_rc73" in prev_ver: return prev_ver ver_nor = prev_ver.partition("-")[0] # rc даже после уже существующего гарантирует, что версия будет # считаться ниже #reRc = re.compile(r"(.*rc)(\d+)(.*)") #rc_match = reRc.search(ver_nor) #if rc_match: # rc_num = max(0, int(rc_match.group(2)) - 1) # return "%s%d%s" % (rc_match.group(1), rc_num, rc_match.group(3)) return "%s_rc73" % ver_nor def _processRebuild(self, params, templateDirFile): """ Обработка параметра rebuild= :param params: :param templateDirFile: :return: """ if HParams.Rebuild in params: rebuild_packages = params[HParams.Rebuild].split(',') chroot_path = self.objVar.Get('cl_chroot_path') for atom in rebuild_packages: for pkg in getInstalledAtom( atom, prefix=chroot_path): ver = pkg["PVR"] new_ver = self.setRebuildVersion(ver) if ver != new_ver: try: fn = pathJoin(chroot_path, "var/db/pkg", "%s-%s" % (pkg["CATEGORY/PN"], pkg["PVR"])) new_fn = pathJoin(chroot_path, "var/db/pkg", "%s-%s" % (pkg["CATEGORY/PN"], new_ver)) shutil.copytree(fn, new_fn, symlinks=True) shutil.rmtree(fn) except (OSError, IOError) as e: self.printWARNING( _("Failed to change version of %s") % str(pkg)) def _processMergePostmerge(self, params, templateDirFile): """Обработка параметров merge= , postmerge=""" if HParams.Merge in params: mergePkgs = params[HParams.Merge].split(',') if self.objVar.Get('cl_action') == "config": self.printWARNING( _("Config action is not support '%s' parameter") % HParams.Merge ) return else: mergePkgs = [] if HParams.PostMerge in params: postmergePkgs = params[HParams.PostMerge].split(',') if self.objVar.Get('cl_action') == "config": self.printWARNING( _("Config action is not support '%s' parameter") % HParams.PostMerge ) return else: postmergePkgs = [] if mergePkgs or postmergePkgs: if self.objVar.Get('cl_ebuild_phase') == 'postinst': for pkg in postmergePkgs: if pkg not in self.objVar.Get('cl_merge_pkg_pass'): self.objVar.Get('cl_merge_pkg_pass').append(pkg) if pkg not in self.postmergePkgs: try: with open(self.postmergeFile, "a") as f: f.write("%s\n" % pkg) self.postmergePkgs.append(pkg) except OSError: self.printWARNING( _("Failed to reconfigure package %s") % pkg) else: mergePkgs = mergePkgs + postmergePkgs for pkg in mergePkgs: curlistset = set(self.objVar.Get('cl_merge_pkg_new') + self.objVar.Get('cl_merge_pkg_pass') + self.objVar.Get('cl_merge_pkg')) if ":" not in pkg: curlistset = {x.partition(":")[0] for x in curlistset if x} if pkg not in curlistset: self.objVar.Get('cl_merge_pkg_new').append(pkg) def checkVfat(self, fn): if self.mounts is None: self.mounts = Mounts() if self.mounts.getBy(self.mounts.TYPE, where=self.mounts.DIR, _in=fn) in ("vfat", "ntfs-3g", "ntfs"): return True return False def checkOsError(self, e, fn): if hasattr(e, 'errno') and e.errno == errno.EPERM: if self.checkVfat(fn): return True if hasattr(e, 'errno') and e.errno == errno.EACCES and \ "var/calculate/remote" in fn: return True return False def setUidGidError(self, fn, uid, gid, tfn=None): """ Установить ошибку связанную со сменой UID и GID файла """ import pwd, grp try: userName = pwd.getpwuid(uid).pw_name except (TypeError, KeyError): userName = str(uid) try: groupName = grp.getgrgid(gid).gr_name except (TypeError, KeyError): groupName = str(gid) owner = userName + ":" + groupName if tfn: self.setError(_("Failed to apply template file %s") % tfn) self.setError(_("error") + " " + "'%s %s %s'" % ( HParams.ChangeOwner, owner, fn)) def setModeError(self, fn, mode, tfn): """ Установить ошибку связанную со сменой доступа к файлу """ self.setError(_("Failed to apply template file %s") % tfn) self.setError(_("error") + " " + "'%s %s %s'" % ( HParams.ChangeMode, str(oct(mode)), fn)) def chownConfDir(self, nameDirConfig, uid, gid, nameFileTemplate): """Изменение владельца конфигурационной директории""" try: os.chown(nameDirConfig, uid, gid) except (OSError, Exception) as e: if self.checkOsError(e, nameDirConfig): return True self.setUidGidError( nameDirConfig, uid, gid, nameFileTemplate) return False return True def chmodConfDir(self, nameDirConfig, mode, nameFileTemplate): """Изменения режима доступа конфигурационного файла""" try: os.chmod(nameDirConfig, mode) except (OSError, Exception) as e: if self.checkOsError(e, nameDirConfig): return True self.setModeError(nameDirConfig, mode, nameFileTemplate) return False return True def getApplyHeadDir(self, newDir, templateDirFile, optDir): """Применяет шаблон к директории (права, владелец, и.т. д)""" def function(text): """Функция обработки функций в заголовке""" return self.applyFuncTemplate(text, templateDirFile) applyDir = newDir # Родительская директория if optDir.get(HParams.OptDir.Path): path = optDir[HParams.OptDir.Path] else: if applyDir == self._baseDir: path = os.path.dirname(self._baseDir) else: path = os.path.split(applyDir)[1] path = pathJoin(self._baseDir, path) if not os.path.exists(templateDirFile): if applyDir != self._baseDir: applyDir = os.path.join(path, os.path.split(applyDir)[1]) # Фильтрация шаблонов по названию директории realPath = os.path.join("/", applyDir.partition(self._baseDir)[2]) if realPath in self.dirsFilter: return "", False, [] # Создаем директорию если необходимо crDirs = self.createDir(applyDir, False, self.uid, self.gid) if not crDirs: return "", False, [] if HParams.OptDir.Autoupdate in optDir: self.autoUpdateDirs.append(applyDir) if crDirs is True: return applyDir, True, [] else: return applyDir, True, crDirs try: self.objVar.Set("cl_pass_file", os.path.basename(os.path.dirname(templateDirFile))) with open(templateDirFile) as FD: textTemplate = FD.readline().rstrip() buf = textTemplate while buf and textTemplate.endswith('\\'): buf = FD.readline() textTemplate = "%s %s" % (textTemplate[:-1], buf.rstrip()) except IOError: self.setError(_("Failed to open the template") + _(": ") + templateDirFile) return "", False, [] headerLine = self.getHeaderText(textTemplate) if headerLine: envparam = "%s=" % HParams.Environ moduleParam = [x for x in headerLine.split() if x.startswith(envparam)] if moduleParam: self.objVar.defaultModule = moduleParam[0].partition('=')[2] try: importlib.import_module( "calculate.%s.variables" % self.objVar.defaultModule) except (ImportError, AttributeError): return "", False, [] # Заменяем переменные на их значения # textTemplate = self.applyVarsTemplate(textTemplate, templateDirFile) # Заменяем функции на их значения # textTemplate = self.applyFuncTemplate(textTemplate, templateDirFile) # Обработка заголовка objHead = dirHeader(templateDirFile, textTemplate, self.objVar, function, templateObj=self) signs = {'ac_install_patch==on': HParams.ActionType.Patch, 'ac_desktop_profile==on': HParams.ActionType.Profile} for sign in signs: if sign in textTemplate: self.functObj.currentAction = signs[sign] break # Директория с профилями не будет применена if not objHead.headerTerm: if objHead.getError(): self.setError(_("Incorrect template") + _(": ") + templateDirFile) return "", False, [] # add packeges for reconfigure self._processMergePostmerge(objHead.params, templateDirFile) self._processRebuild(objHead.params, templateDirFile) # Пропускаем директорию if objHead.typeAppend == HParams.AppendParams.Skip: applyDir = path return applyDir, objHead, [] # Изменяем название родительской директории if HParams.Path in objHead.params: path = objHead.params[HParams.Path] if path and path[0] == "~": # Получаем путь с заменой ~ на директорию пользователя path = os.path.join(self.homeDir, path.partition("/")[2], "")[:-1] elif not path or path and path[0] != "/": self.setError( (_("Wrong value '%s' in the template") % HParams.Path) + _(": ") + templateDirFile) return "", False, [] else: path = pathJoin(self._baseDir, path) # Изменяем название директории if HParams.Name in objHead.params: nameDir = objHead.params[HParams.Name] if "/" in nameDir or nameDir == ".." or nameDir == ".": self.setError( (_("Wrong value '%s' in the template") % HParams.Name) + _(": ") + templateDirFile) return "", False, [] # Новый путь к директории applyDir = pathJoin(path, nameDir) else: applyDir = pathJoin(path, os.path.split(applyDir)[1]) # Фильтрация шаблонов по названию директории realPath = os.path.join("/", applyDir.partition(self._baseDir)[2]) if realPath in self.dirsFilter: return "", False, [] if HParams.DirectoryLink in objHead.params: if objHead.typeAppend not in HParams.AppendParams.LinkDirCompatible: self.setError( _("Option '%(opt)s' should be used with %(appends)s only") % {'opt': HParams.DirectoryLink, 'appends': ",".join( "%s=%s" % (HParams.Append, x) for x in HParams.AppendParams.LinkDirCompatible) }) return "", False, [] # Удаляем директорию if objHead.typeAppend == HParams.AppendParams.Remove: if os.path.isdir(applyDir): self.changedFiles.addObj(applyDir, ChangedFiles.DIR_REMOVED, self.functObj.currentBelong, self.functObj.currentBelongSlot) # удаляем директорию try: removeDir(applyDir) except OSError: self.setError(_("Failed to delete the directory: ") + \ applyDir) return "", False, [] # Очищаем директорию if objHead.typeAppend == HParams.AppendParams.Clear: if os.path.isdir(applyDir): for rmPath in os.listdir(applyDir): removePath = pathJoin(applyDir, rmPath) if os.path.isdir(removePath): # удаляем директорию try: removeDir(removePath) except OSError: self.setError( _("Failed to delete the directory: ") + \ removePath) else: try: os.unlink(removePath) except OSError: self.setError( _("Failed to delete: ") + removePath) return "", False, [] # Созданные директории createdDirs = [] # chmod - изменяем права mode = None if HParams.ChangeMode in objHead.params: mode = self.__octToInt(objHead.params[HParams.ChangeMode]) if mode: if not os.path.exists(applyDir): crDirs = self.createDir(applyDir, mode, self.uid, self.gid) if not crDirs: return "", False, [] if not crDirs is True: createdDirs += crDirs else: self.chmodConfDir(applyDir, mode, templateDirFile) else: self.setError( (_("Wrong value '%s' in the template") % HParams.ChangeMode) + _(": ") + templateDirFile) return "", False, [] # chown - изменяем владельца и группу owner_uid, owner_gid = None, None if HParams.ChangeOwner in objHead.params: owner = objHead.params[HParams.ChangeOwner] if owner: if ":" in owner: strUid, strGid = owner.split(":") if strUid.isdigit(): owner_uid = int(strUid) else: owner_uid = self.getUidFromPasswd(strUid) import pwd try: if owner_uid is None: owner_uid = pwd.getpwnam(strUid).pw_uid except (KeyError, TypeError): self.setError(_("No such user on the system: ") + strUid) self.setError( (_("Wrong value '%s' in the template") % HParams.ChangeOwner) + _(": ") + templateDirFile) return "", False, [] if strGid.isdigit(): owner_gid = int(strGid) else: owner_gid = self.getGidFromGroup(strGid) import grp try: if owner_gid is None: owner_gid = grp.getgrnam(strGid).gr_gid except (KeyError, TypeError): self.setError(_("Group not found on the system: ") + strGid) self.setError( (_("Wrong value '%s' in the template") % HParams.ChangeOwner) + _(": ") + templateDirFile) return "", False, [] if not os.path.exists(applyDir): crDirs = self.createDir(applyDir, False, owner_uid, owner_gid) if not crDirs: return "", False, [] if not crDirs is True: createdDirs += crDirs else: if not self.chownConfDir(applyDir, owner_uid, owner_gid, templateDirFile): return "", False, [] else: self.setError( (_("Wrong value '%s' in the template") % HParams.ChangeOwner) + _(": ") + templateDirFile) return "", False, [] else: self.setError( (_("Wrong value '%s' in the template") % HParams.ChangeOwner) + _(": ") + templateDirFile) return "", False, [] else: # Устанавливаем владельцем директории, пользователя по умолчанию # (переменная шаблона ur_login) if os.path.exists(applyDir): self.changedFiles.addObj(applyDir, ChangedFiles.DIR_EXISTS, self.functObj.currentBelong, self.functObj.currentBelongSlot) tUid, tGid = getModeFile(applyDir, mode="owner") if (self.uid, self.gid) != (tUid, tGid): if not self.chownConfDir(applyDir, self.uid, self.gid, templateDirFile): return "", False, [] else: self.changedFiles.addObj(applyDir, ChangedFiles.DIR_CREATED, self.functObj.currentBelong, self.functObj.currentBelongSlot) crDirs = self.createDir(applyDir, False, self.uid, self.gid) if not crDirs: return "", False, [] if crDirs is not True: createdDirs += crDirs if HParams.DirectoryLink in objHead.params: templateFile = objHead.params[HParams.DirectoryLink] templateFile = pathJoin(self._baseDir, templateFile) if not os.path.isdir(templateFile): self.setError(_("Source path %s is not a directory") % templateFile) return "", False, [] try: if objHead.typeAppend == HParams.AppendParams.Replace: for fn in listDirectory(applyDir, fullPath=True): if os.path.isdir(fn): shutil.rmtree(fn) elif os.path.isfile(fn) or os.path.islink(fn): os.unlink(fn) for fn in listDirectory(templateFile, fullPath=True): applyFn = os.path.join(applyDir, os.path.basename(fn)) if os.path.isfile(fn): shutil.copy2(fn, applyFn) elif os.path.islink(fn): os.symlink(os.readlink(fn), applyFn) elif os.path.isdir(fn): dir_sync(fn, applyFn) except (IOError, OSError) as e: self.setError(_("Failed to synchronize directory " "{dn}: {error}").format(dn=templateFile, error=str(e))) return "", False, [] if not objHead: applyDir = "" if applyDir: if ((HParams.OptDir.Autoupdate in optDir or HParams.Autoupdate in objHead.params) and not self.objVar.Get('cl_merge_pkg_pass')): self.autoUpdateDirs.append(applyDir) return applyDir, objHead, createdDirs def getUidFromPasswd(self, strUid): """Get uid by username from chroot passwd file""" passwdFile = os.path.join(self._baseDir, 'etc/passwd') if os.path.exists(passwdFile): with open(passwdFile, 'r') as f: mapUid = dict([x for x in [y.split(':')[0:3:2] for y in f if not y.startswith('#')] if x and len(x) > 1 and x[0] and x[1]]) if strUid in mapUid: return int(mapUid[strUid]) return None def getGidFromGroup(self, strGid): """Get gid by groupname from chroot group file""" groupFile = os.path.join(self._baseDir, 'etc/group') if os.path.exists(groupFile): with open(groupFile, 'r') as f: mapGid = dict([x for x in [y.split(':')[0:3:2] for y in f if not y.startswith('#')] if x and len(x) > 1 and x[0] and x[1]]) if strGid in mapGid: return int(mapGid[strGid]) return None def checkOnNewConfigName(self, pathFile): """ Check on need update and return pathFile """ # if file in PROTECT_MASK or not in PROTECT chrootPath = self.objVar.Get('cl_chroot_path') if not [x for x in [pathJoin(chrootPath, y) for y in self.objVar.Get('cl_config_protect')] if pathFile.startswith(x)] or \ [x for x in [pathJoin(chrootPath, y) for y in self.objVar.Get('cl_config_protect_mask')] if pathFile.startswith(x)]: return pathFile # if file was already modified by templates if pathFile in self.changedFiles.data.keys(): return pathFile # if using already created ._cfg file if self.configMode != T_ORIGIN: return pathFile # not current package file pkg = self.functObj.currentBelong slot = self.functObj.currentBelongSlot if not pkg: if not self.allContents: fillContents(self.allContents, self.objVar.Get('cl_config_protect'), prefix=self.objVar.Get('cl_chroot_path')) origName = pathFile if chrootPath == '/' \ else pathFile[len(chrootPath):] if origName in self.allContents: pkg = self.allContents[origName] else: return pathFile if slot: pkgslot = "{}:{}".format(pkg,slot) else: pkgslot = pkg atom = list(sorted(getInstalledAtom(pkgslot, prefix=chrootPath))) if not atom: return pathFile if checkContents("{CATEGORY}/{PF}".format(**atom[-1]), pathFile, prefix=chrootPath, reservedFile='/var/lib/calculate/-CONTENTS-{PN}'.format( **atom[-1]) \ if self.objVar.Get('cl_ebuild_phase') == 'postinst' \ else None): return pathFile real_filename = os.path.basename(pathFile) real_dirname = os.path.dirname(pathFile) self.configMode = T_NEWCFG return os.path.join(real_dirname, "._cfg0000_%s" % real_filename) def chownConfFile(self, nameFileConfig, uid, gid, nameFileTemplate, checkExists=True): """Изменение владельца конфигурационного файла""" try: if checkExists and not os.path.exists(nameFileConfig): # Создание файла open(nameFileConfig, "w").close() os.lchown(nameFileConfig, uid, gid) except (OSError, Exception) as e: if self.checkOsError(e, nameFileConfig): return True self.setUidGidError( nameFileConfig, uid, gid, nameFileTemplate) return False return True def chmodConfFile(self, nameFileConfig, mode, nameFileTemplate, checkExists=True): """Изменения режима доступа конфигурационного файла""" try: if checkExists and not os.path.exists(nameFileConfig): # Создание файла open(nameFileConfig, "w").close() os.chmod(nameFileConfig, mode) except (OSError, Exception) as e: if self.checkOsError(e, nameFileConfig): return True self.setModeError(nameFileConfig, mode, nameFileTemplate) return False return True def getApplyHeadTemplate(self, nameFileTemplate, nameFileConfig, templateFileType, optFile): """Применяет заголовок к шаблону (права, владелец, и.т. д)""" def function(text): """Функция обработки функций в заголовке""" return self.applyFuncTemplate(text, nameFileTemplate) def preReturn(pathProg): """Действия перед выходом из метода""" if pathProg: os.chdir(pathProg) self.closeFiles() pathProg = "" self.executeType = None # Файлы в системе к которым были применены шаблоны # В случае бинарного типа файла читаем шаблон if templateFileType == "bin": self.nameFileTemplate = os.path.abspath(nameFileTemplate) self.F_TEMPL = self.openTemplFile(self.nameFileTemplate) if not self.F_TEMPL: self.setError(_("Failed to open the template") + _(": ") + self.nameFileTemplate) return [], False self.textTemplate, ___ = try_decode_utf8(self.F_TEMPL.read()) self.closeTemplFile() objHeadNew = fileHeader(nameFileTemplate, self.textTemplate, False, templateFileType, objVar=self.objVar, function=function, templateObj=self) # файл шаблона не будет применен if not objHeadNew.headerTerm: if objHeadNew.getError(): self.setError(_("Incorrect template") + _(": ") + nameFileTemplate) return [], False else: self.headerParams = objHeadNew.params # add packeges for reconfigure self._processMergePostmerge(objHeadNew.params, nameFileTemplate) self._processRebuild(objHeadNew.params, nameFileTemplate) # Родительская директория path = optFile[HParams.OptDir.Path] # Изменяем название родительской директории if HParams.Path in objHeadNew.params: path = objHeadNew.params[HParams.Path] if path and path[0] == "~": # Получаем путь с заменой ~ на директорию пользователя path = os.path.join( self.homeDir, path.partition("/")[2], "")[:-1] elif not path or path and path[0] != "/": self.setError( (_("Wrong value '%s' in the template") % HParams.Path) + _(": ") + nameFileTemplate) return [], False else: path = pathJoin(self._baseDir, path) # Путь к оригинальному файлу - pathOldFile # Изменяем путь к оригинальному файлу if HParams.Name in objHeadNew.params: nameFile = objHeadNew.params[HParams.Name] if "/" in nameFile or nameFile == ".." or nameFile == ".": self.setError( (_("Wrong value '%s' in the template") % HParams.Name) + _(": ") + nameFileTemplate) return [], False # Новый путь к оригинальному файлу pathOldFile = pathJoin(path, nameFile) else: pathOldFile = pathJoin(path, os.path.split(nameFileConfig)[1]) pathOrigFile = pathOldFile self.nameFileConfigOrig = pathOrigFile if self.objVar.Get('cl_protect_use_set') == 'on': pathOldFile = self.fixNameFileConfig(pathOldFile) pathOldFile = self.checkOnNewConfigName(pathOldFile) # буффер для использование в link= newBuffer = None applyFiles = [pathOldFile] # Фильтрация шаблонов по названию файла realPath = os.path.join("/", pathOldFile.partition(self._baseDir)[2]) if realPath in self.filesFilter: return [], False typeAppendTemplate = objHeadNew.typeAppend # Параметр exec if (HParams.RunPost in objHeadNew.params or HParams.RunNow in objHeadNew.params): if HParams.RunPost in objHeadNew.params: paramName = HParams.RunPost self.executeType = HParams.ExecuteType.Post else: paramName = HParams.RunNow self.executeType = HParams.ExecuteType.Now execPath = objHeadNew.params[paramName] if not os.access(execPath, os.X_OK): self.setError( _("Wrong value '%s' in the template") % paramName + _(": ") + nameFileTemplate) self.setError(_("Failed to execute %s") % execPath) return [], False if typeAppendTemplate == HParams.AppendParams.Join: self.setError( (_("Wrong value '{var}={val}' in template").format (var=HParams.Append, val=HParams.AppendParams.Join)) + _(": ") + nameFileTemplate) return [], False # Очищаем оригинальный файл if typeAppendTemplate == HParams.AppendParams.Clear: try: with open(pathOldFile, "w") as f: f.truncate(0) except IOError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to clear the file") + _(": ") + pathOldFile) return applyFiles, False # Удаляем оригинальный файл if typeAppendTemplate == HParams.AppendParams.Remove: if HParams.Force in objHeadNew.params: pathOldFile = pathOrigFile self.configMode = T_ORIGIN try: if os.path.islink(pathOldFile): # удаляем ссылку try: os.unlink(pathOldFile) return applyFiles, False except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to delete the link") + _(": ") + pathOldFile) return [], False if os.path.isfile(pathOldFile) and self.configMode == T_ORIGIN: # удаляем файл try: os.remove(pathOldFile) return applyFiles, False except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to delete the file") + _(": ") + pathOldFile) return [], False finally: pattern = "%s/._cfg????_%s" % (os.path.dirname(pathOrigFile), os.path.basename(pathOrigFile)) for fn in glob.glob(pattern): try: os.unlink(fn) except OSError: pass if self.functObj.currentBelong: self.changedFiles.addObj(pathOrigFile, ChangedFiles.FILE_REMOVED, self.functObj.currentBelong, self.functObj.currentBelongSlot) return [], False # Пропускаем обработку шаблона elif typeAppendTemplate == HParams.AppendParams.Skip: return [], False # Создаем директорию для файла если ее нет if not os.path.exists(path): if not self.createDir(path): return [], False # создаём пустой файл если его нет для sqlite if objHeadNew.fileType in HParams.Formats.Modificator: try: if not os.path.exists(pathOrigFile): with open(pathOrigFile, "w") as f: f.truncate(0) except IOError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to create the file") + _(": ") + pathOrigFile) # В случае force if (HParams.Force in objHeadNew.params and objHeadNew.fileType not in HParams.Formats.Executable): if os.path.islink(pathOldFile): # удаляем ссылку newBuffer = "" try: os.unlink(pathOldFile) except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to delete the link") + _(": ") + pathOldFile) return [], False if os.path.isfile(pathOldFile): # удаляем файл newBuffer = "" try: os.remove(pathOldFile) except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to delete the file") + _(": ") + pathOldFile) return [], False flagSymlink = False # Если есть параметр mirror if (HParams.Mirror in objHeadNew.params and objHeadNew.fileType not in HParams.Formats.Executable): if HParams.Link in objHeadNew.params: templateFile = objHeadNew.params[HParams.Link] if templateFile and templateFile[0] == "~": # Получаем директорию пользователя templateFile = os.path.join( self.homeDir, templateFile.partition("/")[2], "")[:-1] templateFile = pathJoin(self._baseDir, templateFile) if (not os.path.exists(templateFile) or not objHeadNew.params[HParams.Link]): if os.path.exists(pathOldFile): try: os.remove(pathOldFile) except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError( _("Failed to delete the file") + _(": ") + pathOldFile) return [], False elif not os.path.exists(pathOldFile): return [], False # Если есть указатель на файл шаблона (link) if (HParams.Link in objHeadNew.params and objHeadNew.fileType not in HParams.Formats.Executable and HParams.Symbolic not in objHeadNew.params): templateFile = objHeadNew.params[HParams.Link] if templateFile and templateFile[0] == "~": # Получаем директорию пользователя templateFile = os.path.join( self.homeDir, templateFile.partition("/")[2], "")[:-1] templateFile = pathJoin(self._baseDir, templateFile) foundTemplateFile = os.path.exists(templateFile) buff = None fMode, fUid, fGid = None, None, None if foundTemplateFile and objHeadNew.params[HParams.Link]: try: F_CONF = self.openTemplFile(templateFile) if not F_CONF: raise IOError buff, ___ = try_decode_utf8(F_CONF.read()) F_CONF.close() fMode, fUid, fGid = getModeFile(templateFile) except (OSError, IOError): self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to open the file") + _(": ") + templateFile) return [], False if os.path.exists(pathOldFile): newBuffer = "" try: os.remove(pathOldFile) except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to delete the file") + _(": ") + pathOldFile) return [], False if buff is not None: try: mode = "wb+" if isinstance(buff, bytes) else "w+" with open(pathOldFile, mode) as FD: newBuffer = buff FD.write(buff) except IOError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to create the file") + " '%s'" \ % pathOldFile) return [], False oMode = getModeFile(pathOldFile, mode="mode") # Если права не совпадают, меняем права if fMode != oMode: if not self.chmodConfFile( pathOldFile, fMode, nameFileTemplate, checkExists=False): return [], False # Если символическая ссылка prevOldFile = None if HParams.Symbolic in objHeadNew.params: prevOldFile = pathOldFile pathOldFile = objHeadNew.params[HParams.Link] flagSymlink = True if not pathOldFile: raise TemplatesError( _("Missed source link in template '%s'") % str(nameFileTemplate)) if not "/" == pathOldFile[0]: pathLink = os.path.split(os.path.abspath(prevOldFile))[0] pathProg = os.getcwd() try: os.chdir(pathLink) except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError( _("Failed to change the current directory to") + \ " " + pathLink) return [], False # chmod - изменяем права if HParams.ChangeMode in objHeadNew.params: mode = self.__octToInt(objHeadNew.params[HParams.ChangeMode]) if mode: if not self.chmodConfFile(pathOldFile, mode, nameFileTemplate): preReturn(pathProg) return [], False else: self.setError( (_("Wrong value '%s' in the template") % HParams.ChangeMode) + _(": ") + nameFileTemplate) preReturn(pathProg) return [], False # chown - изменяем владельца и группу if HParams.ChangeOwner in objHeadNew.params: owner = objHeadNew.params[HParams.ChangeOwner] if owner: if ":" in owner: strUid, strGid = owner.split(":") if strUid.isdigit(): uid = int(strUid) else: uid = self.getUidFromPasswd(strUid) import pwd try: if uid is None: uid = pwd.getpwnam(strUid).pw_uid except (KeyError, TypeError): self.setError(_("No such user on the system: ") + strUid) self.setError((_("Wrong value '%s' in the template") % HParams.ChangeOwner) + _(": ") + nameFileTemplate) preReturn(pathProg) return [], False if strGid.isdigit(): gid = int(strGid) else: gid = self.getGidFromGroup(strGid) try: if gid is None: import grp gid = grp.getgrnam(strGid).gr_gid except (KeyError, TypeError): self.setError(_("Group not found on the system: ") + strGid) self.setError((_("Wrong value '%s' in the template") % HParams.ChangeOwner) + _(": ") + nameFileTemplate) preReturn(pathProg) return [], False # Изменяем владельца файла if not self.chownConfFile(pathOldFile, uid, gid, nameFileTemplate): preReturn(pathProg) return [], False else: self.setError((_("Wrong value '%s' in the template") % HParams.ChangeOwner) + _(": ") + nameFileTemplate) preReturn(pathProg) return [], False else: self.setError((_("Wrong value '%s' in the template") % HParams.ChangeOwner) + _(": ") + nameFileTemplate) preReturn(pathProg) return [], False if not flagSymlink: self.openFiles(nameFileTemplate, pathOldFile, objHeadNew.fileType, newBuffer) if self.getError(): return [], False if HParams.ChangeOwner not in objHeadNew.params: # Устанавливаем владельцем конфигурационного файла, # пользователя по умолчанию (переменная шаблона ur_login) if os.path.exists(pathOldFile): tUid, tGid = getModeFile(pathOldFile, mode="owner") if (self.uid, self.gid) != (tUid, tGid): # Изменяем владельца файла if not self.chownConfFile( pathOldFile, self.uid, self.gid, nameFileTemplate, checkExists=False): preReturn(pathProg) return [], False if flagSymlink: if os.path.exists(prevOldFile) or os.path.islink(prevOldFile): try: if os.path.islink(prevOldFile): # если ссылка то удаляем её os.unlink(prevOldFile) else: # иначе удаляем файл os.remove(prevOldFile) except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to delete the file") + _(": ") + prevOldFile) preReturn(pathProg) return [], False if not "/" == pathOldFile[0]: applyFiles = [ prevOldFile] # ,os.path.join(pathLink,pathOldFile)] else: applyFiles = [prevOldFile] # ,pathOldFile] try: os.symlink(pathOldFile, prevOldFile) except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to create a symbolic link") + _(": ") + "%s -> %s" % (prevOldFile, pathOldFile)) preReturn(pathProg) return [], False if not objHeadNew.body.strip(): preReturn(pathProg) if HParams.Protected in objHeadNew.params: self.protectedFiles += applyFiles return applyFiles, False else: applyFiles = [pathOldFile] preReturn(pathProg) if HParams.Protected in objHeadNew.params: self.protectedFiles += applyFiles if ((HParams.OptDir.Autoupdate in optFile or HParams.Autoupdate in objHeadNew.params) and not self.objVar.Get('cl_merge_pkg_pass')): reCfg = re.compile(r"/._cfg\d{4}_", re.S) self.autoUpdateFiles += [reCfg.sub('/', x) for x in applyFiles] if pathOldFile not in self.dictProcessedTemplates: self.dictProcessedTemplates[pathOldFile] = [] self.dictProcessedTemplates[pathOldFile].append(nameFileTemplate) # Если файлы заменяются не нужно их обрабатывать дальше if (HParams.AppendParams.Replace == typeAppendTemplate and HParams.Symbolic not in objHeadNew.params and HParams.Link in objHeadNew.params): return applyFiles, False return applyFiles, objHeadNew def doServiceControl(self, params): """ Выполнить действие над сервисом :param params: параметры заголовка шаблонов :return: """ command_action_map = { HParams.RestartService: "restart", HParams.StopService: "stop", HParams.StartService: "start" } command_action_messages = { HParams.RestartService: _("Service %s has been restarted"), HParams.StopService: _("Service %s has been stopped"), HParams.StartService: _("Service %s has been started") } command_action_error = { HParams.RestartService: _("Failed to restart %s service"), HParams.StopService: _("Failed to stop %s service"), HParams.StartService: _("Failed to start %s service") } for param in HParams.ServiceControl: if param in params: service_list = (x for x in params[param].split(',') if x) command_action = command_action_map[param] for service in service_list: try: p = process("/etc/init.d/%s" % service, command_action) if p.success(): self.printSUCCESS( command_action_messages[param] % service) else: self.printERROR( command_action_error[param] % service) for line in p.readerr().strip().split('\n'): self.printERROR(line) except FilesError as e: self.printERROR( command_action_error[param] % service) self.printERROR(str(e)) def createNewClass(self, name, bases, attrs=None): """Создает объект нового класса createNewClass(self, name, bases, attrs) name - имя класса - str, bases - cписок наследуемых классов - (tuple), attrs - аттрибуты класса - {dict} """ if attrs is None: attrs = {} class newMethod(): # Объединяем конфигурации def join(self, newObj): if newObj.__class__.__name__ == self.__class__.__name__: if hasattr(self, "docObj"): self.docObj.joinDoc(newObj.doc) # Пост обработка if hasattr(self, "postXML"): self.postXML() attrsNew = {"configName": name} if attrs: attrsNew.update(attrs) newCl = type(name, bases + (newMethod, object,), attrsNew) return newCl def fileIsUtf(self, fileName, data=None): """Проверяет файл на кодировку UTF-8""" if os.path.isfile(fileName): if data is None: with open(os.path.abspath(fileName), 'rb') as FD: data = FD.read(1) + FD.read() try: if(not isinstance(data, str)): data.decode("UTF-8") except UnicodeDecodeError: return False return True return False def joinTemplate(self, nameFileTemplate, nameFileConfig, optFile=None): """Объединения шаблона и конф. файла join(nameFileTemplate, nameFileConfig, ListOptTitle) Объединение шаблона nameFileTemplate и конф. файла nameFileConfig, ListOptTitle - список строк которые добавятся в заголовок optFile = опции для шаблона """ if optFile is None: optFile = {} # Выполняем условия для блока текста а так-же заменяем переменные self.nameFileTemplate = os.path.abspath(nameFileTemplate) self.F_TEMPL = self.openTemplFile(self.nameFileTemplate) origTextTemplate, file_is_unicode = try_decode_utf8(self.F_TEMPL.read()) self.textTemplate = origTextTemplate self.configMode = T_ORIGIN self.closeTemplFile() # Флаг копирования шаблона в конфигурационный файл flagCopyTemplate = True # Тип шаблона бинарный или текстовый if self.textTemplate[:11] == "# Calculate": templateFileType = "text" else: templateFileType = self.getTemplateType() if templateFileType == "text" and not file_is_unicode: #go home getTemplateType, you're drunk templateFileType = "bin" elif templateFileType == "bin" and file_is_unicode: #probably svg file self.textTemplate = self.textTemplate.encode("UTF-8") headerLine = self.getHeaderText(self.textTemplate, binary = templateFileType == "bin") if headerLine: envparam = "%s=" % HParams.Environ moduleParam = [x for x in headerLine.split() if x.startswith(envparam)] if moduleParam: self.objVar.defaultModule = moduleParam[0].partition('=')[2] try: importlib.import_module( "calculate.%s.variables" % self.objVar.defaultModule) except (ImportError, AttributeError) as e: #DEBUG # print(e) return [] if not optFile: optFile = {"path": os.path.split(nameFileConfig)[0]} filesApply, objHeadNew = self.getApplyHeadTemplate(nameFileTemplate, nameFileConfig, templateFileType, optFile) if not objHeadNew: return filesApply # if filesApply and not list(filter(lambda x: "calculate/ini.env" in x, # filesApply)): if filesApply and not any(["calculate/ini.env" in x for x in filesApply]): self.templateModify() if templateFileType != "bin": # Вычисляем условные блоки objHeadNew.body = self.applyTermsTemplate(objHeadNew.body, nameFileTemplate) # Заменяем переменные на их значения objHeadNew.body = self.applyVarsTemplate(objHeadNew.body, nameFileTemplate) flagCopyTemplate = False # Вычисляем функции objHeadNew.body = self.applyFuncTemplate(objHeadNew.body, nameFileTemplate) # Настоящее имя конфигурационного файла nameFileConfig = filesApply[0] # Флаг - кодировка с бинарными примесями у файла шаблона включаем при # условии текстового файла и кодировки отличной от UTF-8 flagNotUtf8New = False # Флаг - кодировка с бинарными примесями у оригинального файла flagNotUtf8Old = False if not flagCopyTemplate: # проверяем кодировку шаблона if not self.fileIsUtf(nameFileTemplate, data=origTextTemplate): flagNotUtf8New = True if not (HParams.Link in objHeadNew.params and HParams.Symbolic in objHeadNew.params): # проверяем кодировку оригинального файла if not self.fileIsUtf(nameFileConfig): flagNotUtf8Old = True self.textTemplate = objHeadNew.body # Список примененных шаблонов ListOptTitle = [] if nameFileConfig in self.dictProcessedTemplates: ListOptTitle = self.dictProcessedTemplates[nameFileConfig] # Титл конфигурационного файла title = "" if ListOptTitle: title = self.getTitle(objHeadNew.comment, ListOptTitle, configPath=nameFileConfig) objHeadOld = False if objHeadNew.comment: objHeadOld = fileHeader(nameFileConfig, self.textConfig, objHeadNew.comment) elif (objHeadNew.fileType and objHeadNew.typeAppend in (HParams.AppendParams.Before, HParams.AppendParams.After)): configFileType = self.getFileType(nameFileConfig) objHeadOld = fileHeader(nameFileConfig, self.textConfig, fileType=configFileType) # Строка вызова скрипта (#!/bin/bash ...) execStr = "" if objHeadNew.execStr: execStr = objHeadNew.execStr elif objHeadOld and objHeadOld.execStr: execStr = objHeadOld.execStr if objHeadNew.fileType != 'patch': wrongOpt = [x for x in (HParams.Multiline, HParams.DotAll) if x in objHeadNew.params] if wrongOpt: self.setError( _("Option %s should be used for format=patch only") % wrongOpt[0]) return None if objHeadNew.fileType != 'dconf': wrongOpt = [x for x in (HParams.DConf,) if x in objHeadNew.params] if wrongOpt: self.setError( _("Option %s should be used for format=dconf only") % wrongOpt[0]) return None if objHeadNew.fileType != 'backgrounds': wrongOpt = [x for x in (HParams.Convert, HParams.Stretch) if x in objHeadNew.params] if wrongOpt: self.setError( _("Option %s should be used for format=backgrounds only") % wrongOpt[0]) return None if objHeadNew.fileType: formatTemplate = objHeadNew.fileType typeAppendTemplate = objHeadNew.typeAppend if formatTemplate in chain(("patch",), HParams.Formats.Executable): if typeAppendTemplate != HParams.AppendParams.Patch: self.setError( _("Wrong option '%(param)s=%(type)s' " "in template %(file)s") % {"param": HParams.Append, "type": typeAppendTemplate, "file": nameFileTemplate}) return None # создаем объект формата шаблона objTempl = self.formatFactory.createObject( formatTemplate, self.textTemplate) if formatTemplate == 'patch': if HParams.Multiline in objHeadNew.params: objTempl.setMultiline() if HParams.DotAll in objHeadNew.params: objTempl.setDotall() if formatTemplate == 'dconf': if HParams.DConf in objHeadNew.params: objTempl.setPath(objHeadNew.params[HParams.DConf]) objTempl.setUser(self.objVar.Get('ur_login')) if formatTemplate == 'backgrounds': root_path = self.objVar.Get('cl_chroot_path') if root_path != '/': objTempl.setRootPath(root_path) if HParams.Convert in objHeadNew.params: objTempl.setConvert(objHeadNew.params[HParams.Convert]) if HParams.Link in objHeadNew.params: objTempl.setSource(objHeadNew.params[HParams.Link]) if HParams.Mirror in objHeadNew.params: objTempl.setMirror() if HParams.Stretch in objHeadNew.params: objTempl.setStretch(True) if (HParams.Name in objHeadNew.params and not objHeadNew.params[HParams.Name]): objTempl.setPrefix("") if not objTempl: self.setError( _("Wrong header parameter 'format=%s' " "in template") % formatTemplate + " " + nameFileTemplate) return None if objHeadOld and objHeadOld.body: self.textConfig = objHeadOld.body # обработка конфигурационного файла objTempl.printWARNING = self.printWARNING self.textTemplate = objTempl.processingFile( self.textConfig, pathJoin(self.objVar.Get('cl_chroot_path'), self.objVar.Get('cl_root_path')), self.nameFileConfigOrig ) error = objTempl.getError() if error: self.printERROR(error.strip()) if (formatTemplate in HParams.Formats.Executable and formatTemplate != "diff"): raise TemplatesError( (_("Failed to use %s ") % formatTemplate) + nameFileTemplate) if (self.objVar.Get('cl_ebuild_phase') == 'compile' and self.objVar.Get('cl_template_wrong_patch') == 'break'): raise CriticalError(_("Failed to use patch ") + nameFileTemplate) raise TemplatesError(_("Failed to use patch ") + nameFileTemplate) elif (formatTemplate == 'diff' and self.objVar.Get('cl_verbose_set') == "on"): #TODO fix transl self.printSUCCESS(_("Appling patch") + " " + os.path.basename(nameFileTemplate)) self.textConfig = self.add_comment( execStr, title, self.textTemplate) if formatTemplate in HParams.Formats.Executable: return objTempl.changed_files else: self.saveConfFile() if HParams.RunNow in objHeadNew.params: if not self.executeTemplate( self.textConfig, objHeadNew.params[HParams.RunNow]): self.setError(_("Failed to execute") + _(": ") + nameFileTemplate) return None return None if HParams.RunPost not in objHeadNew.params: return filesApply else: return None # Создаем объект в случае параметра format в заголовке if ((typeAppendTemplate == HParams.AppendParams.Replace or typeAppendTemplate == HParams.AppendParams.Before or typeAppendTemplate == HParams.AppendParams.After) and not (formatTemplate == "bin" or formatTemplate == "raw")): # Преобразовываем бинарные файлы objTxtCoder = None if flagNotUtf8New: objTxtCoder = utfBin() self.textTemplate = objTxtCoder.encode(self.textTemplate) # создаем объект формата шаблона objTemplNew = self.formatFactory.createObject( formatTemplate, self.textTemplate) if not objTemplNew: self.setError( _("Wrong header parameter '{var}={val}' " "in template").format( var=HParams.Format, val=formatTemplate) + " " + nameFileTemplate) return None if "xml_" in formatTemplate: if objTemplNew.getError(): self.setError(_("Wrong template") + _(": ") + nameFileTemplate) return None # Имя файла внутри xml xfce конфигурационных файлов nameRootNode = \ nameFileConfig.rpartition("/")[2].split(".")[0] objTemplNew.setNameBodyNode(nameRootNode) # Объект Документ docObj = objTemplNew.docObj # Удаление комментариев из документа docObj.removeComment(docObj.getNodeBody()) # Добавление необходимых переводов строк docObj.insertBRtoBody(docObj.getNodeBody()) # Добавление необходимых разделителей между областями docObj.insertBeforeSepAreas(docObj.getNodeBody()) # Пост обработка if 'postXML' in dir(objTemplNew): objTemplNew.postXML() # Получение текстового файла из XML документа self.textTemplate = objTemplNew.getConfig() # Если не UTF-8 производим преобразование if objTxtCoder: self.textTemplate = objTxtCoder.decode(self.textTemplate) # Титл для объединения if ListOptTitle: title = self.getTitle(objTemplNew._comment, ListOptTitle, configPath=nameFileConfig) # Замена if typeAppendTemplate == HParams.AppendParams.Replace: if "xml_" in formatTemplate: data = self.textTemplate.split("\n") data.insert(1, title) self.textConfig = "\n".join(data) else: self.textConfig = self.add_comment( objHeadNew.execStr, title, self.textTemplate) self.saveConfFile() if HParams.RunNow in objHeadNew.params: if not self.executeTemplate( self.textConfig, objHeadNew.params[HParams.RunNow]): self.setError(_("Failed to execute") + _(": ") + \ nameFileTemplate) return None return None if HParams.RunPost not in objHeadNew.params: return filesApply else: return None # Вверху elif typeAppendTemplate == HParams.AppendParams.Before: if "xml_" in formatTemplate: self.setError( _("Wrong option '{var}={val}' in template {fn}").format( var=HParams.Append, val=HParams.AppendParams.Before, fn=nameFileTemplate)) return None if objHeadOld and objHeadOld.body: self.textConfig = objHeadOld.body if self.textTemplate and self.textTemplate[-1] == "\n": tmpTemplate = self.textTemplate + self.textConfig else: tmpTemplate = self.textTemplate + "\n" + self.textConfig self.textConfig = self.add_comment(execStr, title, tmpTemplate) self.saveConfFile() if HParams.RunNow in objHeadNew.params: if not self.executeTemplate( self.textConfig, objHeadNew.params[HParams.RunNow]): self.setError(_("Failed to execute") + _(": ") + nameFileTemplate) return None return None if HParams.RunPost not in objHeadNew.params: return filesApply else: return None # Внизу elif typeAppendTemplate == HParams.AppendParams.After: if "xml_" in formatTemplate: self.setError( _("Wrong option '{var}={val}' in template {fn}").format( var=HParams.Append, val=HParams.AppendParams.After, fn=nameFileTemplate)) return None if objHeadOld and objHeadOld.body: self.textConfig = objHeadOld.body if not self.textTemplate or self.textTemplate[-1] == "\n": tmpTemplate = self.textConfig + self.textTemplate else: tmpTemplate = self.textConfig + "\n" + self.textTemplate self.textConfig = self.add_comment(execStr, title, tmpTemplate) self.saveConfFile() if HParams.RunNow in objHeadNew.params: if not self.executeTemplate( self.textConfig, objHeadNew.params[HParams.RunNow]): self.setError(_("Failed to execute") + _(": ") + nameFileTemplate) return None return None if HParams.RunPost not in objHeadNew.params: return filesApply else: return None # Объединение elif typeAppendTemplate == HParams.AppendParams.Join: objTxtCoder = None if flagNotUtf8New: objTxtCoder = utfBin() self.textTemplate = objTxtCoder.encode(self.textTemplate) if formatTemplate == "raw": self.setError( _("Wrong header parameter '{var}={val}' " "in template").format(var=HParams.Append, val=typeAppendTemplate) + " " + nameFileTemplate) return None # создаем объект формата шаблона objTemplNew = self.formatFactory.createObject( formatTemplate, self.textTemplate) if not objTemplNew: self.setError( _("Wrong header parameter '{var}={val}' in " "template").format(var=HParams.Format, val=formatTemplate) + " " + nameFileTemplate) return None if "xml_" in formatTemplate: if objTemplNew.getError(): self.setError(_("Wrong template") + _(": ") + \ nameFileTemplate) return None nameRootNode = nameFileConfig.rpartition("/")[2].split(".")[0] objTemplNew.setNameBodyNode(nameRootNode) # Титл для объединения if ListOptTitle: title = self.getTitle(objTemplNew._comment, ListOptTitle, configPath=nameFileConfig) # В случае пустого конфигурационного файла reNoClean = re.compile("[^\s]", re.M) if not self.textConfig or \ not reNoClean.search(self.textConfig): self.textConfig = "" objHeadOld = fileHeader(nameFileConfig, self.textConfig, objTemplNew._comment) if objHeadOld.body: self.textConfig = objHeadOld.body else: self.textConfig = "" if flagNotUtf8Old: objTxtCoder = utfBin() self.textConfig = objTxtCoder.encode(self.textConfig) # создаем объект формата шаблона для конфигурационного файла objTemplOld = self.formatFactory.createObject( formatTemplate, self.textConfig) if not objTemplOld: self.setError(_("Error in template %s") % nameFileConfig) return None if "xml_" in formatTemplate: if objTemplOld.getError(): self.setError(_("Wrong template") + _(": ") + nameFileConfig) return None nameRootNode = nameFileConfig.rpartition("/")[2].split(".")[ 0] objTemplOld.setNameBodyNode(nameRootNode) objTemplOld.join(objTemplNew) if "xml_" in formatTemplate: if objTemplOld.getError(): self.setError(_("Wrong template") + _(": ") + \ nameFileTemplate) return None data = objTemplOld.getConfig().split("\n") data.insert(1, title) self.textConfig = "\n".join(data) else: self.textConfig = self.add_comment( execStr, title, objTemplOld.getConfig()) # Декодируем если кодировка не UTF-8 if objTxtCoder: self.textTemplate = objTxtCoder.decode(self.textTemplate) self.textConfig = objTxtCoder.decode(self.textConfig) self.saveConfFile() if HParams.RunNow in objHeadNew.params: if not self.executeTemplate( self.textConfig, objHeadNew.params[HParams.RunNow]): self.setError(_("Failed to execute") + _(": ") + \ nameFileTemplate) return None return None if HParams.RunPost not in objHeadNew.params: return filesApply else: return None else: self.setError(_("Wrong template option (type append)") + _(": ") + typeAppendTemplate) return None else: self.setError(_("Template type not found: ") + nameFileTemplate) return None def add_comment(self, execStr, comment, body): """ Сформировать выходной файл с учётом строки выполнения, комментария и содержимого конфига :param execStr: :param comment: :param body: :return: """ if len(execStr) == 0 and len(comment) == 0: return body if comment.startswith("