Files
entropy/libraries/entropy/core/settings/base.py
T

1513 lines
51 KiB
Python

# -*- coding: utf-8 -*-
"""
@author: Fabio Erculiani <lxnay@sabayon.org>
@contact: lxnay@sabayon.org
@copyright: Fabio Erculiani
@license: GPL-2
B{Entropy Framework SystemSettings module}.
SystemSettings is a singleton, pluggable interface which contains
all the runtime settings (mostly parsed from configuration files
and inherited from entropy.const -- which contains almost all the
default values).
SystemSettings works as a I{dict} object. Due to limitations of
multiple inherittance when using the Singleton class, SystemSettings
ONLY mimics a I{dict} AND it's not a subclass of it.
"""
import os
import sys
from entropy.const import etpConst, etpUi, etpSys, const_setup_perms, \
const_secure_config_file, const_set_nice_level, const_isunicode, \
const_convert_to_unicode, const_convert_to_rawstring
from entropy.core import Singleton, EntropyPluginStore
from entropy.cache import EntropyCacher
from entropy.core.settings.plugins.skel import SystemSettingsPlugin
import entropy.tools
class SystemSettings(Singleton, EntropyPluginStore):
"""
This is the place where all the Entropy settings are stored if
they are not considered instance constants (etpConst).
For example, here we store package masking cache information and
settings, client-side, server-side and services settings.
Also, this class mimics a dictionary (even if not inheriting it
due to development choices).
Sample code:
>>> from entropy.core.settings.base import SystemSettings
>>> system_settings = SystemSettings()
>>> system_settings.clear()
>>> system_settings.destroy()
"""
def init_singleton(self):
"""
Replaces __init__ because SystemSettings is a Singleton.
see Singleton API reference for more information.
"""
EntropyPluginStore.__init__(self)
from entropy.core.settings.plugins.factory import get_available_plugins
self.__get_external_plugins = get_available_plugins
from threading import RLock
self.__lock = RLock()
self.__cacher = EntropyCacher()
self.__data = {}
self.__is_destroyed = False
self.__inside_with_stmt = 0
self.__pkg_comment_tag = "##"
self.__external_plugins = {}
self.__setting_files_order = []
self.__setting_files_pre_run = []
self.__setting_files = {}
self.__mtime_files = {}
self.__persistent_settings = {
'pkg_masking_reasons': etpConst['pkg_masking_reasons'].copy(),
'pkg_masking_reference': etpConst['pkg_masking_reference'].copy(),
'backed_up': {},
# package masking, live
'live_packagemasking': {
'unmask_matches': set(),
'mask_matches': set(),
},
}
self.__setup_const()
self.__scan()
def __enter__(self):
"""
Make possible to add plugins without triggering parse() every time.
"""
self.__inside_with_stmt += 1
def __exit__(self, exc_type, exc_value, traceback):
"""
Make possible to add plugins without triggering parse() every time.
Reload SystemSettings on exit
"""
self.__inside_with_stmt -= 1
if self.__inside_with_stmt == 0:
self.clear()
def destroy(self):
"""
Overloaded method from Singleton.
"Destroys" the instance.
@return: None
@rtype: None
"""
self.__is_destroyed = True
def add_plugin(self, system_settings_plugin_instance):
"""
This method lets you add custom parsers to SystemSettings.
Mind that you are responsible of handling your plugin instance
and remove it before it is destroyed. You can remove the plugin
instance at any time by issuing remove_plugin.
Every add_plugin or remove_plugin method will also issue clear()
for you. This could be bad and it might be removed in future.
@param system_settings_plugin_instance: valid SystemSettingsPlugin
instance
@type system_settings_plugin_instance: SystemSettingsPlugin instance
@return: None
@rtype: None
"""
inst = system_settings_plugin_instance
if not isinstance(inst, SystemSettingsPlugin):
raise AttributeError("SystemSettings: expected valid " + \
"SystemSettingsPlugin instance")
EntropyPluginStore.add_plugin(self, inst.get_id(), inst)
if self.__inside_with_stmt == 0:
self.clear()
def remove_plugin(self, plugin_id):
"""
This method lets you remove previously added custom parsers from
SystemSettings through its plugin identifier. If plugin_id is not
available, KeyError exception will be raised.
Every add_plugin or remove_plugin method will also issue clear()
for you. This could be bad and it might be removed in future.
@param plugin_id: plugin identifier
@type plugin_id: basestring
@return: None
@rtype: None
"""
EntropyPluginStore.remove_plugin(self, plugin_id)
self.clear()
def __setup_const(self):
"""
Internal method. Does constants initialization.
@return: None
@rtype: None
"""
del self.__setting_files_order[:]
del self.__setting_files_pre_run[:]
self.__setting_files.clear()
self.__mtime_files.clear()
self.__setting_files.update({
# keywording configuration files
'keywords': etpConst['confpackagesdir']+"/package.keywords",
# unmasking configuration files
'unmask': etpConst['confpackagesdir']+"/package.unmask",
# masking configuration files
'mask': etpConst['confpackagesdir']+"/package.mask",
# satisfied packages configuration file
'satisfied': etpConst['confpackagesdir']+"/package.satisfied",
# masking configuration files
'license_mask': etpConst['confpackagesdir']+"/license.mask",
'license_accept': etpConst['confpackagesdir']+"/license.accept",
'system_mask': etpConst['confpackagesdir']+"/system.mask",
'system_dirs': etpConst['confdir']+"/fsdirs.conf",
'system_dirs_mask': etpConst['confdir']+"/fsdirsmask.conf",
'extra_ldpaths': etpConst['confdir']+"/fsldpaths.conf",
'system_rev_symlinks': etpConst['confdir']+"/fssymlinks.conf",
'broken_syms': etpConst['confdir']+"/brokensyms.conf",
'broken_libs_mask': etpConst['confdir']+"/brokenlibsmask.conf",
'hw_hash': etpConst['confdir']+"/.hw.hash",
'socket_service': etpConst['socketconf'],
'system': etpConst['entropyconf'],
'repositories': etpConst['repositoriesconf'],
'system_package_sets': {},
})
self.__setting_files_order.extend([
'keywords', 'unmask', 'mask', 'satisfied', 'license_mask',
'license_accept', 'system_mask', 'system_package_sets',
'system_dirs', 'system_dirs_mask', 'extra_ldpaths',
'socket_service', 'system', 'system_rev_symlinks', 'hw_hash',
'broken_syms', 'broken_libs_mask'
])
self.__setting_files_pre_run.extend(['repositories'])
dmp_dir = etpConst['dumpstoragedir']
self.__mtime_files.update({
'keywords_mtime': os.path.join(dmp_dir, "keywords.mtime"),
'unmask_mtime': os.path.join(dmp_dir, "unmask.mtime"),
'mask_mtime': os.path.join(dmp_dir, "mask.mtime"),
'satisfied_mtime': os.path.join(dmp_dir, "satisfied.mtime"),
'license_mask_mtime': os.path.join(dmp_dir, "license_mask.mtime"),
'license_accept_mtime': os.path.join(dmp_dir, "license_accept.mtime"),
'system_mask_mtime': os.path.join(dmp_dir, "system_mask.mtime"),
})
def __scan(self):
"""
Internal method. Scan settings and fill variables.
@return: None
@rtype: None
"""
def enforce_persistent():
# merge persistent settings back
self.__data.update(self.__persistent_settings)
# restore backed-up settings
self.__data.update(self.__persistent_settings['backed_up'].copy())
self.__parse()
enforce_persistent()
# plugins support
local_plugins = self.get_plugins()
for plugin_id in sorted(local_plugins):
local_plugins[plugin_id].parse(self)
# external plugins support
external_plugins = self.__get_external_plugins()
for external_plugin_id in sorted(external_plugins):
external_plugin = external_plugins[external_plugin_id]()
external_plugin.parse(self)
self.__external_plugins[external_plugin_id] = external_plugin
enforce_persistent()
# run post-SystemSettings setup, plugins hook
for plugin_id in sorted(local_plugins):
local_plugins[plugin_id].post_setup(self)
# run post-SystemSettings setup for external plugins too
for external_plugin_id in sorted(self.__external_plugins):
self.__external_plugins[external_plugin_id].post_setup(self)
def __setitem__(self, mykey, myvalue):
"""
dict method. See Python dict API reference.
"""
# backup here too
if mykey in self.__persistent_settings:
self.__persistent_settings[mykey] = myvalue
self.__data[mykey] = myvalue
def __getitem__(self, mykey):
"""
dict method. See Python dict API reference.
"""
with self.__lock:
return self.__data[mykey]
def __delitem__(self, mykey):
"""
dict method. See Python dict API reference.
"""
with self.__lock:
del self.__data[mykey]
def __iter__(self):
"""
dict method. See Python dict API reference.
"""
return iter(self.__data)
def __contains__(self, item):
"""
dict method. See Python dict API reference.
"""
return item in self.__data
def __hash__(self):
"""
dict method. See Python dict API reference.
"""
return hash(self.__data)
def __len__(self):
"""
dict method. See Python dict API reference.
"""
return len(self.__data)
def get(self, *args, **kwargs):
"""
dict method. See Python dict API reference.
"""
return self.__data.get(*args, **kwargs)
def copy(self):
"""
dict method. See Python dict API reference.
"""
return self.__data.copy()
def fromkeys(self, *args, **kwargs):
"""
dict method. See Python dict API reference.
"""
return self.__data.fromkeys(*args, **kwargs)
def items(self):
"""
dict method. See Python dict API reference.
"""
return self.__data.items()
def iteritems(self):
"""
dict method. See Python dict API reference.
"""
return self.__data.iteritems()
def iterkeys(self):
"""
dict method. See Python dict API reference.
"""
return self.__data.iterkeys()
def keys(self):
"""
dict method. See Python dict API reference.
"""
return self.__data.keys()
def pop(self, *args, **kwargs):
"""
dict method. See Python dict API reference.
"""
return self.__data.pop(*args, **kwargs)
def popitem(self):
"""
dict method. See Python dict API reference.
"""
return self.__data.popitem()
def setdefault(self, *args, **kwargs):
"""
dict method. See Python dict API reference.
"""
return self.__data.setdefault(*args, **kwargs)
def update(self, kwargs):
"""
dict method. See Python dict API reference.
"""
return self.__data.update(kwargs)
def values(self):
"""
dict method. See Python dict API reference.
"""
return self.__data.values()
def clear(self):
"""
dict method. See Python dict API reference.
Settings are also re-initialized here.
@return None
"""
with self.__lock:
self.__data.clear()
self.__setup_const()
self.__scan()
def set_persistent_setting(self, persistent_dict):
"""
Make metadata persistent, the input dict will be merged
with the base one at every reset call (clear()).
@param persistent_dict: dictionary to merge
@type persistent_dict: dict
@return: None
@rtype: None
"""
self.__persistent_settings.update(persistent_dict)
def unset_persistent_setting(self, persistent_key):
"""
Remove dict key from persistent dictionary
@param persistent_key: key to remove
@type persistent_dict: dict
@return: None
@rtype: None
"""
del self.__persistent_settings[persistent_key]
del self.__data[persistent_key]
def __setup_package_sets_vars(self):
"""
This function setups the *files* dictionary about package sets
that will be read and parsed afterwards by the respective
internal parser.
@return: None
@rtype: None
"""
# user defined package sets
sets_dir = etpConst['confsetsdir']
pkg_set_data = {}
if (os.path.isdir(sets_dir) and os.access(sets_dir, os.R_OK)):
set_files = [x for x in os.listdir(sets_dir) if \
(os.path.isfile(os.path.join(sets_dir, x)) and \
os.access(os.path.join(sets_dir, x), os.R_OK))]
for set_file in set_files:
try:
set_file = const_convert_to_unicode(set_file, 'utf-8')
except UnicodeDecodeError:
set_file = const_convert_to_unicode(set_file,
sys.getfilesystemencoding())
path = os.path.join(sets_dir, set_file)
if sys.hexversion < 0x3000000:
if const_isunicode(path):
path = const_convert_to_rawstring(path, 'utf-8')
pkg_set_data[set_file] = path
self.__setting_files['system_package_sets'].update(pkg_set_data)
def __parse(self):
"""
This is the main internal parsing method.
*files* and *mtimes* dictionaries are prepared and
parsed just a few lines later.
@return: None
@rtype: None
"""
# some parsers must be run BEFORE everything:
for item in self.__setting_files_pre_run:
myattr = '_%s_parser' % (item,)
if not hasattr(self, myattr):
continue
func = getattr(self, myattr)
self.__data[item] = func()
# parse main settings
self.__setup_package_sets_vars()
for item in self.__setting_files_order:
myattr = '_%s_parser' % (item,)
if not hasattr(self, myattr):
continue
func = getattr(self, myattr)
self.__data[item] = func()
def get_setting_files_data(self):
"""
Return a copy of the internal *files* dictionary.
This dict contains config file paths and their identifiers.
@return: dict __setting_files
@rtype: dict
"""
return self.__setting_files.copy()
def _keywords_parser(self):
"""
Parser returning package keyword masking metadata
read from package.keywords file.
This file contains package mask or unmask directives
based on package keywords.
@return: parsed metadata
@rtype: dict
"""
# merge universal keywords
data = {
'universal': set(),
'packages': {},
'repositories': {},
}
self.validate_entropy_cache(self.__setting_files['keywords'],
self.__mtime_files['keywords_mtime'])
content = [x.split() for x in \
self.__generic_parser(self.__setting_files['keywords'],
comment_tag = self.__pkg_comment_tag) \
if len(x.split()) < 4]
for keywordinfo in content:
# skip wrong lines
if len(keywordinfo) > 3:
continue
# inversal keywording, check if it's not repo=
if len(keywordinfo) == 1:
if keywordinfo[0].startswith("repo="):
continue
# convert into entropy format
if keywordinfo[0] == "**":
keywordinfo[0] = ""
data['universal'].add(keywordinfo[0])
continue
# inversal keywording, check if it's not repo=
if len(keywordinfo) in (2, 3,):
# repo=?
if keywordinfo[0].startswith("repo="):
continue
# add to repo?
items = keywordinfo[1:]
# convert into entropy format
if keywordinfo[0] == "**":
keywordinfo[0] = ""
reponame = [x for x in items if x.startswith("repo=") \
and (len(x.split("=")) == 2)]
if reponame:
reponame = reponame[0].split("=")[1]
if reponame not in data['repositories']:
data['repositories'][reponame] = {}
# repository unmask or package in repository unmask?
if keywordinfo[0] not in data['repositories'][reponame]:
data['repositories'][reponame][keywordinfo[0]] = set()
if len(items) == 1:
# repository unmask
data['repositories'][reponame][keywordinfo[0]].add('*')
elif "*" not in \
data['repositories'][reponame][keywordinfo[0]]:
item = [x for x in items if not x.startswith("repo=")]
data['repositories'][reponame][keywordinfo[0]].add(
item[0])
elif len(items) == 2:
# it's going to be a faulty line!!??
# can't have two items and no repo=
continue
else:
# add keyword to packages
if keywordinfo[0] not in data['packages']:
data['packages'][keywordinfo[0]] = set()
data['packages'][keywordinfo[0]].add(items[0])
# merge universal keywords
etpConst['keywords'].clear()
etpConst['keywords'].update(etpSys['keywords'])
for keyword in data['universal']:
etpConst['keywords'].add(keyword)
return data
def _unmask_parser(self):
"""
Parser returning package unmasking metadata read from
package.unmask file.
This file contains package unmask directives, allowing
to enable experimental or *secret* packages.
@return: parsed metadata
@rtype: dict
"""
self.validate_entropy_cache(self.__setting_files['unmask'],
self.__mtime_files['unmask_mtime'])
return self.__generic_parser(self.__setting_files['unmask'],
comment_tag = self.__pkg_comment_tag)
def _mask_parser(self):
"""
Parser returning package masking metadata read from
package.mask file.
This file contains package mask directives, allowing
to disable experimental or *secret* packages.
@return: parsed metadata
@rtype: dict
"""
self.validate_entropy_cache(self.__setting_files['mask'],
self.__mtime_files['mask_mtime'])
return self.__generic_parser(self.__setting_files['mask'],
comment_tag = self.__pkg_comment_tag)
def _satisfied_parser(self):
"""
Parser returning package forced satisfaction metadata
read from package.satisfied file.
This file contains packages which updates as dependency are
filtered out.
@return: parsed metadata
@rtype: dict
"""
self.validate_entropy_cache(self.__setting_files['satisfied'],
self.__mtime_files['satisfied_mtime'])
return self.__generic_parser(self.__setting_files['satisfied'],
comment_tag = self.__pkg_comment_tag)
def _system_mask_parser(self):
"""
Parser returning system packages mask metadata read from
package.system_mask file.
This file contains packages that should be always kept
installed, extending the already defined (in repository database)
set of atoms.
@return: parsed metadata
@rtype: dict
"""
self.validate_entropy_cache(self.__setting_files['system_mask'],
self.__mtime_files['system_mask_mtime'])
return self.__generic_parser(self.__setting_files['system_mask'],
comment_tag = self.__pkg_comment_tag)
def _license_mask_parser(self):
"""
Parser returning packages masked by license metadata read from
license.mask file.
Packages shipped with licenses listed there will be masked.
@return: parsed metadata
@rtype: dict
"""
self.validate_entropy_cache(self.__setting_files['license_mask'],
self.__mtime_files['license_mask_mtime'])
return self.__generic_parser(self.__setting_files['license_mask'])
def _license_accept_parser(self):
"""
Parser returning packages unmasked by license metadata read from
license.mask file.
Packages shipped with licenses listed there will be unmasked.
@return: parsed metadata
@rtype: dict
"""
self.validate_entropy_cache(self.__setting_files['license_accept'],
self.__mtime_files['license_accept_mtime'])
return self.__generic_parser(self.__setting_files['license_accept'])
def _extract_packages_from_set_file(self, filepath):
"""
docstring_title
@param filepath:
@type filepath:
@return:
@rtype:
"""
if sys.hexversion >= 0x3000000:
f = open(filepath, "r", encoding = 'raw_unicode_escape')
else:
f = open(filepath, "r")
items = set()
line = f.readline()
while line:
x = line.strip().rsplit("#", 1)[0]
if x and (not x.startswith('#')):
items.add(x)
line = f.readline()
f.close()
return items
def _system_package_sets_parser(self):
"""
Parser returning system defined package sets read from
/etc/entropy/packages/sets.
@return: parsed metadata
@rtype: dict
"""
data = {}
for set_name in self.__setting_files['system_package_sets']:
set_filepath = self.__setting_files['system_package_sets'][set_name]
set_elements = self._extract_packages_from_set_file(set_filepath)
if set_elements:
data[set_name] = set_elements.copy()
return data
def _extra_ldpaths_parser(self):
"""
Parser returning directories considered part of the base system.
@return: parsed metadata
@rtype: dict
"""
return self.__generic_parser(self.__setting_files['extra_ldpaths'])
def _system_dirs_parser(self):
"""
Parser returning directories considered part of the base system.
@return: parsed metadata
@rtype: dict
"""
return self.__generic_parser(self.__setting_files['system_dirs'])
def _system_dirs_mask_parser(self):
"""
Parser returning directories NOT considered part of the base system.
Settings here overlay system_dirs_parser.
@return: parsed metadata
@rtype: dict
"""
return self.__generic_parser(self.__setting_files['system_dirs_mask'])
def _broken_syms_parser(self):
"""
Parser returning a list of shared objects symbols that can be used by
QA tools to scan the filesystem or a subset of it.
@return: parsed metadata
@rtype: dict
"""
return self.__generic_parser(self.__setting_files['broken_syms'])
def _broken_libs_mask_parser(self):
"""
Parser returning a list of broken shared libraries which are
always considered sane.
@return: parsed metadata
@rtype: dict
"""
return self.__generic_parser(self.__setting_files['broken_libs_mask'])
def _hw_hash_parser(self):
"""
Hardware hash metadata parser and generator. It returns a theorically
unique SHA256 hash bound to the computer running this Framework.
@return: string containing SHA256 hexdigest
@rtype: string
"""
hw_hash_file = self.__setting_files['hw_hash']
if os.access(hw_hash_file, os.R_OK) and os.path.isfile(hw_hash_file):
hash_f = open(hw_hash_file, "r")
hash_data = hash_f.readline().strip()
hash_f.close()
return hash_data
hash_file_dir = os.path.dirname(hw_hash_file)
hw_hash_exec = etpConst['etp_hw_hash_gen']
if os.access(hash_file_dir, os.W_OK) and \
os.access(hw_hash_exec, os.X_OK | os.R_OK) and \
os.path.isfile(hw_hash_exec):
pipe = os.popen('{ ' + hw_hash_exec + '; } 2>&1', 'r')
hash_data = pipe.read().strip()
sts = pipe.close()
if sts is not None:
return None
hash_f = open(hw_hash_file, "w")
hash_f.write(hash_data)
hash_f.flush()
hash_f.close()
return hash_data
def _system_rev_symlinks_parser(self):
"""
Parser returning important system symlinks mapping. For example:
{'/usr/lib': ['/usr/lib64']}
Useful for reverse matching files belonging to packages.
@return: dict containing the mapping
@rtype: dict
"""
setting_file = self.__setting_files['system_rev_symlinks']
raw_data = self.__generic_parser(setting_file)
data = {}
for line in raw_data:
line = line.split()
if len(line) < 2:
continue
data[line[0]] = frozenset(line[1:])
return data
def _socket_service_parser(self):
"""
Parses socket service configuration file.
This file contains information about Entropy remote service ports
and SSL.
@return: parsed metadata
@rtype: dict
"""
data = etpConst['socket_service'].copy()
sock_conf = self.__setting_files['socket_service']
if not (os.path.isfile(sock_conf) and \
os.access(sock_conf, os.R_OK)):
return data
socket_f = open(sock_conf, "r")
socketconf = [x.strip() for x in socket_f.readlines() if \
x.strip() and not x.strip().startswith("#")]
socket_f.close()
def _listen(setting):
data['hostname'] = setting
def _listen_port(setting):
try:
data['port'] = int(setting)
except ValueError:
return
def _listen_timeout(setting):
try:
data['timeout'] = int(setting)
except ValueError:
return
def _listen_threads(setting):
try:
data['threads'] = int(setting)
except ValueError:
return
def _session_ttl(setting):
try:
data['session_ttl'] = int(setting)
except ValueError:
return
def _max_connections(setting):
try:
data['max_connections'] = int(setting)
except ValueError:
return
def _ssl_port(setting):
try:
data['ssl_port'] = int(setting)
except ValueError:
return
def _disabled_commands(setting):
for disabled_cmd in setting.split():
data['disabled_cmds'].add(disabled_cmd)
def _ip_blacklist(setting):
for ip_blacklist in setting.split():
data['ip_blacklist'].add(ip_blacklist)
settings_map = {
'listen': _listen,
'listen-port': _listen_port,
'listen-timeout': _listen_timeout,
'listen-threads': _listen_threads,
'session-ttl': _session_ttl,
'max-connections': _max_connections,
'ssl-port': _ssl_port,
'disabled-commands': _disabled_commands,
'ip-blacklist': _ip_blacklist,
}
for line in socketconf:
key, value = entropy.tools.extract_setting(line)
if key is None:
continue
func = settings_map.get(key)
if func is None:
continue
func(value)
return data
def _system_parser(self):
"""
Parses Entropy system configuration file.
@return: parsed metadata
@rtype: dict
"""
data = {
'proxy': etpConst['proxy'].copy(),
'name': etpConst['systemname'],
'log_level': etpConst['entropyloglevel'],
'spm_backend': None,
}
etp_conf = self.__setting_files['system']
if not (os.path.isfile(etp_conf) and \
os.access(etp_conf, os.R_OK)):
return data
const_secure_config_file(etp_conf)
entropy_f = open(etp_conf, "r")
entropyconf = [x.strip() for x in entropy_f.readlines() if \
x.strip() and not x.strip().startswith("#")]
entropy_f.close()
def _loglevel(setting):
try:
loglevel = int(setting)
except ValueError:
return
if (loglevel > -1) and (loglevel < 3):
data['log_level'] = loglevel
def _ftp_proxy(setting):
ftpproxy = setting.strip().split()
if ftpproxy:
data['proxy']['ftp'] = ftpproxy[-1]
def _http_proxy(setting):
httpproxy = setting.strip().split()
if httpproxy:
data['proxy']['http'] = httpproxy[-1]
def _rsync_proxy(setting):
rsyncproxy = setting.strip().split()
if rsyncproxy:
data['proxy']['rsync'] = rsyncproxy[-1]
def _proxy_username(setting):
username = setting.strip().split()
if username:
data['proxy']['username'] = username[-1]
def _proxy_password(setting):
password = setting.strip().split()
if password:
data['proxy']['password'] = password[-1]
def _name(setting):
data['name'] = setting.strip()
def _spm_backend(setting):
data['spm_backend'] = setting.strip()
def _nice_level(setting):
mylevel = setting.strip()
try:
mylevel = int(mylevel)
if (mylevel >= -19) and (mylevel <= 19):
const_set_nice_level(mylevel)
except (ValueError,):
return
settings_map = {
'loglevel': _loglevel,
'ftp-proxy': _ftp_proxy,
'http-proxy': _http_proxy,
'rsync-proxy': _rsync_proxy,
'proxy-username': _proxy_username,
'proxy-password': _proxy_password,
'system-name': _name,
'spm-backend': _spm_backend,
'nice-level': _nice_level,
}
for line in entropyconf:
key, value = entropy.tools.extract_setting(line)
if key is None:
continue
func = settings_map.get(key)
if func is None:
continue
func(value)
return data
def _analyze_client_repo_string(self, repostring, branch = None,
product = None):
"""
Extract repository information from the provided repository string,
usually contained in the repository settings file, repositories.conf.
@param repostring: valid repository identifier
@type repostring: string
@rtype: tuple (string, dict)
@return: tuple composed by (repository identifier, extracted repository
metadata)
"""
if branch == None:
branch = etpConst['branch']
if product == None:
product = etpConst['product']
reponame = repostring.split("|")[1].strip()
repodesc = repostring.split("|")[2].strip()
repopackages = repostring.split("|")[3].strip()
repodatabase = repostring.split("|")[4].strip()
eapi3_uri = None
eapi3_port = etpConst['socket_service']['port']
eapi3_ssl_port = etpConst['socket_service']['ssl_port']
eapi3_formatcolon = repodatabase.rfind("#")
# Support for custom EAPI3 ports
if eapi3_formatcolon != -1:
try:
ports = repodatabase[eapi3_formatcolon+1:].split(",")
if ports:
eapi3_port = int(ports[0])
if len(ports) > 1:
eapi3_ssl_port = int(ports[1])
except (ValueError, IndexError,):
eapi3_port = etpConst['socket_service']['port']
eapi3_ssl_port = etpConst['socket_service']['ssl_port']
repodatabase = repodatabase[:eapi3_formatcolon]
# Support for custom database file compression
dbformat = etpConst['etpdatabasefileformat']
dbformatcolon = repodatabase.rfind("#")
if dbformatcolon != -1:
if dbformat in etpConst['etpdatabasesupportedcformats']:
try:
dbformat = repodatabase[dbformatcolon+1:]
except (IndexError, ValueError, TypeError,):
pass
repodatabase = repodatabase[:dbformatcolon]
# Support for custom EAPI3 service URI
eapi3_uricolon = repodatabase.rfind(",")
if eapi3_uricolon != -1:
found_eapi3_uri = repodatabase[eapi3_uricolon+1:]
if found_eapi3_uri:
eapi3_uri = found_eapi3_uri
repodatabase = repodatabase[:eapi3_uricolon]
mydata = {}
mydata['repoid'] = reponame
mydata['service_port'] = eapi3_port
mydata['ssl_service_port'] = eapi3_ssl_port
if not repodatabase.endswith("file://") and (eapi3_uri is None):
try:
# try to cope with the fact that no specific EAPI3 URI has been
# provided
eapi3_uri = repodatabase.split("/")[2]
except IndexError:
eapi3_uri = None
mydata['service_uri'] = eapi3_uri
mydata['description'] = repodesc
mydata['packages'] = []
mydata['plain_packages'] = []
mydata['dbpath'] = etpConst['etpdatabaseclientdir'] + os.path.sep + \
reponame + os.path.sep + product + os.path.sep + \
etpConst['currentarch'] + os.path.sep + branch
mydata['dbcformat'] = dbformat
if not dbformat in etpConst['etpdatabasesupportedcformats']:
mydata['dbcformat'] = etpConst['etpdatabasesupportedcformats'][0]
mydata['plain_database'] = repodatabase
mydata['database'] = repodatabase + os.path.sep + product + \
os.path.sep + reponame + "/database/" + etpConst['currentarch'] + \
os.path.sep + branch
mydata['notice_board'] = mydata['database'] + os.path.sep + \
etpConst['rss-notice-board']
mydata['local_notice_board'] = mydata['dbpath'] + os.path.sep + \
etpConst['rss-notice-board']
mydata['local_notice_board_userdata'] = mydata['dbpath'] + \
os.path.sep + etpConst['rss-notice-board-userdata']
mydata['dbrevision'] = "0"
dbrevision_file = os.path.join(mydata['dbpath'],
etpConst['etpdatabaserevisionfile'])
if os.path.isfile(dbrevision_file) and \
os.access(dbrevision_file, os.R_OK):
with open(dbrevision_file, "r") as dbrev_f:
mydata['dbrevision'] = dbrev_f.readline().strip()
# setup GPG key path
mydata['gpg_pubkey'] = mydata['dbpath'] + os.path.sep + \
etpConst['etpdatabasegpgfile']
# setup script paths
mydata['post_branch_hop_script'] = mydata['dbpath'] + os.path.sep + \
etpConst['etp_post_branch_hop_script']
mydata['post_branch_upgrade_script'] = mydata['dbpath'] + \
os.path.sep + etpConst['etp_post_branch_upgrade_script']
mydata['post_repo_update_script'] = mydata['dbpath'] + os.path.sep + \
etpConst['etp_post_repo_update_script']
# initialize CONFIG_PROTECT
# will be filled the first time the db will be opened
mydata['configprotect'] = None
mydata['configprotectmask'] = None
# protocol filter takes place inside entropy.fetchers
repopackages = [x.strip() for x in repopackages.split() if x.strip()]
for repo_package in repopackages:
new_repo_package = self.__expand_plain_package_mirror(repo_package,
product, reponame)
if new_repo_package is None:
continue
mydata['plain_packages'].append(repo_package)
mydata['packages'].append(new_repo_package)
return reponame, mydata
def __expand_plain_package_mirror(self, mirror, product, reponame):
if not entropy.tools.is_valid_uri(mirror):
return None
try:
mirror = str(mirror)
except (UnicodeDecodeError, UnicodeEncodeError,):
return None
return mirror + os.path.sep + product + os.path.sep + reponame
def _repositories_parser(self):
"""
Setup Entropy Client repository settings reading them from
the relative config file specified in etpConst['repositoriesconf']
@return: parsed metadata
@rtype: dict
"""
data = {
'available': {},
'excluded': {},
'order': [],
'product': etpConst['product'],
'branch': etpConst['branch'],
'default_repository': etpConst['officialrepositoryid'],
'transfer_limit': etpConst['downloadspeedlimit'],
'timeout': etpConst['default_download_timeout'],
'security_advisories_url': etpConst['securityurl'],
'developer_repo': False,
'differential_update': True,
}
repo_conf = etpConst['repositoriesconf']
if not (os.path.isfile(repo_conf) and os.access(repo_conf, os.R_OK)):
return data
repo_f = open(repo_conf, "r")
repositoriesconf = [x.strip() for x in repo_f.readlines() if x.strip()]
repo_f.close()
repoids = set()
def _product_func(line, setting):
data['product'] = setting
def _branch_func(line, setting):
data['branch'] = setting
def _repository_func(line, setting):
excluded = False
my_repodata = data['available']
if line.startswith("##"):
return
elif line.startswith("#"):
excluded = True
my_repodata = data['excluded']
line = line[1:]
reponame, repodata = self._analyze_client_repo_string(line,
data['branch'], data['product'])
if reponame == etpConst['clientdbid']:
# not allowed!!!
return
repoids.add(reponame)
if reponame in my_repodata:
my_repodata[reponame]['plain_packages'].extend(
repodata['plain_packages'])
my_repodata[reponame]['packages'].extend(
repodata['packages'])
if (not my_repodata[reponame]['plain_database']) and \
repodata['plain_database']:
my_repodata[reponame]['plain_database'] = \
repodata['plain_database']
my_repodata[reponame]['database'] = \
repodata['database']
my_repodata[reponame]['dbrevision'] = \
repodata['dbrevision']
my_repodata[reponame]['dbcformat'] = \
repodata['dbcformat']
my_repodata[reponame]['service_uri'] = \
repodata['service_uri']
my_repodata[reponame]['service_port'] = \
repodata['service_port']
my_repodata[reponame]['ssl_service_port'] = \
repodata['ssl_service_port']
else:
my_repodata[reponame] = repodata.copy()
if not excluded:
data['order'].append(reponame)
def _offrepoid(line, setting):
data['default_repository'] = setting
def _developer_repo(line, setting):
bool_setting = entropy.tools.setting_to_bool(setting)
if bool_setting is not None:
data['developer_repo'] = bool_setting
def _differential_update(line, setting):
bool_setting = entropy.tools.setting_to_bool(setting)
if bool_setting is not None:
data['differential_update'] = bool_setting
def _down_speed_limit(line, setting):
data['transfer_limit'] = None
try:
myval = int(setting)
if myval > 0:
data['transfer_limit'] = myval
except ValueError:
data['transfer_limit'] = None
def _down_timeout(line, setting):
try:
data['timeout'] = int(setting)
except ValueError:
return
def _security_url(setting):
data['security_advisories_url'] = setting
settings_map = {
'product': _product_func,
'branch': _branch_func,
'repository': _repository_func,
# backward compatibility
'officialrepositoryid': _offrepoid,
'official-repository-id': _offrepoid,
'developer-repo': _developer_repo,
'differential-update': _differential_update,
# backward compatibility
'downloadspeedlimit': _down_speed_limit,
'download-speed-limit': _down_speed_limit,
# backward compatibility
'downloadtimeout': _down_timeout,
'download-timeout': _down_timeout,
# backward compatibility
'securityurl': _security_url,
'security-url': _security_url,
}
# setup product and branch first
for line in repositoriesconf:
key, value = entropy.tools.extract_setting(line)
if key is None:
continue
func = settings_map.get(key)
if func is None:
continue
func(line, value)
for line in repositoriesconf:
key, value = entropy.tools.extract_setting(line)
if key is None:
continue
func = settings_map.get(key)
if func is None:
continue
func(line, value)
try:
tx_limit = int(os.getenv("ETP_DOWNLOAD_KB"))
except (ValueError, TypeError,):
tx_limit = None
if tx_limit is not None:
data['transfer_limit'] = tx_limit
# validate using mtime
dmp_path = etpConst['dumpstoragedir']
for repoid in repoids:
found_into = 'available'
if repoid in data['available']:
repo_data = data['available'][repoid]
elif repoid in data['excluded']:
repo_data = data['excluded'][repoid]
found_into = 'excluded'
else:
continue
# validate repository settings
if not repo_data['plain_database'].strip():
data[found_into].pop(repoid)
if repoid in data['order']:
data['order'].remove(repoid)
repo_db_path = os.path.join(repo_data['dbpath'],
etpConst['etpdatabasefile'])
repo_mtime_fn = "%s_%s_%s.mtime" % (repoid, data['branch'],
data['product'],)
repo_db_path_mtime = os.path.join(dmp_path, repo_mtime_fn)
if os.path.isfile(repo_db_path) and \
os.access(repo_db_path, os.R_OK):
self.validate_entropy_cache(repo_db_path, repo_db_path_mtime,
repoid = repoid)
# insert extra packages mirrors directly from repository dirs
# if they actually exist. use data['order'] because it reflects
# the list of available repos.
for repoid in data['order']:
if repoid in data['available']:
obj = data['available'][repoid]
elif repoid in data['excluded']:
obj = data['excluded'][repoid]
else:
continue
mirrors_file = os.path.join(obj['dbpath'],
etpConst['etpdatabasemirrorsfile'])
if not (os.path.isfile(mirrors_file) and \
os.access(mirrors_file, os.R_OK)):
continue
raw_mirrors = entropy.tools.generic_file_content_parser(
mirrors_file)
mirrors_data = []
for mirror in raw_mirrors:
expanded_mirror = self.__expand_plain_package_mirror(
mirror, data['product'], repoid)
if expanded_mirror is None:
continue
mirrors_data.append((mirror, expanded_mirror))
# add in reverse order, at the beginning of the list
mirrors_data.reverse()
for mirror, expanded_mirror in mirrors_data:
obj['plain_packages'].insert(0, mirror)
obj['packages'].insert(0, expanded_mirror)
# override parsed branch from env
override_branch = os.getenv('ETP_BRANCH')
if override_branch is not None:
data['branch'] = override_branch
return data
def _clear_repository_cache(self, repoid = None):
"""
Internal method, go away!
"""
self.__cacher.discard()
EntropyCacher.clear_cache(excluded_items = ["db_match"])
if repoid is not None:
EntropyCacher.clear_cache_item("%s/%s%s/" % (
EntropyCacher.CACHE_IDS['db_match'],
etpConst['dbnamerepoprefix'], repoid,))
def __generic_parser(self, filepath, comment_tag = "#"):
"""
Internal method. This is the generic file parser here.
@param filepath: valid path
@type filepath: string
@keyword comment_tag: default comment tag (column where comments starts)
@type: string
@return: raw text extracted from file
@rtype: list
"""
lines = entropy.tools.generic_file_content_parser(filepath,
comment_tag = comment_tag)
# filter out non-ASCII lines
lines = [x for x in lines if entropy.tools.is_valid_ascii(x)]
return lines
def __remove_repo_cache(self, repoid = None):
"""
Internal method. Remove repository cache, because not valid anymore.
@keyword repoid: repository identifier or None
@type repoid: string or None
@return: None
@rtype: None
"""
if os.path.isdir(etpConst['dumpstoragedir']):
if repoid:
self._clear_repository_cache(repoid = repoid)
return
for repoid in self['repositories']['order']:
self._clear_repository_cache(repoid = repoid)
else:
try:
os.makedirs(etpConst['dumpstoragedir'])
except IOError as e:
if e.errno == 30: # readonly filesystem
etpUi['pretend'] = True
return
except OSError:
return
def __save_file_mtime(self, toread, tosaveinto):
"""
Internal method. Save mtime of a file to another file.
@param toread: file path to read
@type toread: string
@param tosaveinto: path where to save retrieved mtime information
@type tosaveinto: string
@return: None
@rtype: None
"""
if not os.path.isfile(toread):
currmtime = 0.0
else:
currmtime = os.path.getmtime(toread)
if not os.path.isdir(etpConst['dumpstoragedir']):
try:
os.makedirs(etpConst['dumpstoragedir'], 0o775)
const_setup_perms(etpConst['dumpstoragedir'],
etpConst['entropygid'])
except IOError as e:
if e.errno == 30: # readonly filesystem
etpUi['pretend'] = True
return
except (OSError,) as e:
# unable to create the storage directory
# useless to continue
return
try:
mtime_f = open(tosaveinto, "w")
except IOError as e: # unable to write?
if e.errno == 30: # readonly filesystem
etpUi['pretend'] = True
return
else:
mtime_f.write(str(currmtime))
mtime_f.flush()
mtime_f.close()
os.chmod(tosaveinto, 0o664)
if etpConst['entropygid'] is not None:
os.chown(tosaveinto, 0, etpConst['entropygid'])
def validate_entropy_cache(self, settingfile, mtimefile, repoid = None):
"""
Internal method. Validates Entropy Cache based on a setting file
and its stored (cache bound) mtime.
@param settingfile: path of the setting file
@type settingfile: string
@param mtimefile: path where to save retrieved mtime information
@type mtimefile: string
@keyword repoid: repository identifier or None
@type repoid: string or None
@return: None
@rtype: None
"""
def revalidate():
try:
self.__remove_repo_cache(repoid = repoid)
self.__save_file_mtime(settingfile, mtimefile)
except (OSError, IOError):
return
# handle on-disk cache validation
# in this case, repositories cache
# if file is changed, we must destroy cache
if not os.path.isfile(mtimefile):
# we can't know if it has been updated
# remove repositories caches
revalidate()
return
# check mtime
try:
with open(mtimefile, "r") as mtime_f:
mtime = str(mtime_f.readline().strip())
except (OSError, IOError,):
mtime = "0.0"
try:
currmtime = str(os.path.getmtime(settingfile))
except (OSError, IOError,):
currmtime = "0.0"
if mtime != currmtime:
revalidate()