|
| 1 | +import glob |
| 2 | +import os |
| 3 | + |
| 4 | +from leapp.libraries.common.rpms import has_package |
| 5 | +from leapp.libraries.stdlib import api, CalledProcessError, run |
| 6 | +from leapp.models import DynamicLinkerConfiguration, InstalledRedHatSignedRPM, LDConfigFile, MainLDConfigFile |
| 7 | + |
| 8 | +LD_SO_CONF_DIR = '/etc/ld.so.conf.d' |
| 9 | +LD_SO_CONF_MAIN = '/etc/ld.so.conf' |
| 10 | +LD_SO_CONF_DEFAULT_INCLUDE = 'ld.so.conf.d/*.conf' |
| 11 | +LD_SO_CONF_COMMENT_PREFIX = '#' |
| 12 | +LD_LIBRARY_PATH_VAR = 'LD_LIBRARY_PATH' |
| 13 | +LD_PRELOAD_VAR = 'LD_PRELOAD' |
| 14 | + |
| 15 | + |
| 16 | +def _read_file(file_path): |
| 17 | + with open(file_path, 'r') as fd: |
| 18 | + return fd.readlines() |
| 19 | + |
| 20 | + |
| 21 | +def _is_modified(config_path): |
| 22 | + """ Decide if the configuration file was modified based on the package it belongs to. """ |
| 23 | + result = run(['rpm', '-Vf', config_path], checked=False) |
| 24 | + if not result['exit_code']: |
| 25 | + return False |
| 26 | + modification_flags = result['stdout'].split(' ', 1)[0] |
| 27 | + # The file is considered modified only when the checksum does not match |
| 28 | + return '5' in modification_flags |
| 29 | + |
| 30 | + |
| 31 | +def _is_included_config_custom(config_path): |
| 32 | + if not os.path.isfile(config_path): |
| 33 | + return False |
| 34 | + |
| 35 | + # Check if the config file has any lines that have an effect on dynamic linker configuration |
| 36 | + has_effective_line = False |
| 37 | + for line in _read_file(config_path): |
| 38 | + line = line.strip() |
| 39 | + if line and not line.startswith(LD_SO_CONF_COMMENT_PREFIX): |
| 40 | + has_effective_line = True |
| 41 | + break |
| 42 | + |
| 43 | + if not has_effective_line: |
| 44 | + return False |
| 45 | + |
| 46 | + is_custom = False |
| 47 | + try: |
| 48 | + package_name = run(['rpm', '-qf', '--queryformat', '%{NAME}', config_path])['stdout'] |
| 49 | + is_custom = not has_package(InstalledRedHatSignedRPM, package_name) or _is_modified(config_path) |
| 50 | + except CalledProcessError: |
| 51 | + is_custom = True |
| 52 | + |
| 53 | + return is_custom |
| 54 | + |
| 55 | + |
| 56 | +def _parse_main_config(): |
| 57 | + """ |
| 58 | + Extracts included configs from the main dynamic linker configuration file (/etc/ld.so.conf) |
| 59 | + along with lines that are likely custom. The lines considered custom are simply those that are |
| 60 | + not includes. |
| 61 | +
|
| 62 | + :returns: tuple containing all the included files and lines considered custom |
| 63 | + :rtype: tuple(list, list) |
| 64 | + """ |
| 65 | + config = _read_file(LD_SO_CONF_MAIN) |
| 66 | + |
| 67 | + included_configs = [] |
| 68 | + other_lines = [] |
| 69 | + for line in config: |
| 70 | + line = line.strip() |
| 71 | + if line.startswith('include'): |
| 72 | + cfg_glob = line.split(' ', 1)[1].strip() |
| 73 | + cfg_glob = os.path.join('/etc', cfg_glob) if not os.path.isabs(cfg_glob) else cfg_glob |
| 74 | + included_configs.append(cfg_glob) |
| 75 | + elif line and not line.startswith(LD_SO_CONF_COMMENT_PREFIX): |
| 76 | + other_lines.append(line) |
| 77 | + |
| 78 | + return included_configs, other_lines |
| 79 | + |
| 80 | + |
| 81 | +def scan_dynamic_linker_configuration(): |
| 82 | + included_configs, other_lines = _parse_main_config() |
| 83 | + |
| 84 | + is_default_include_present = '/etc/' + LD_SO_CONF_DEFAULT_INCLUDE in included_configs |
| 85 | + if not is_default_include_present: |
| 86 | + api.current_logger().debug('The default include "{}" is not present in ' |
| 87 | + 'the {} file.'.format(LD_SO_CONF_DEFAULT_INCLUDE, LD_SO_CONF_MAIN)) |
| 88 | + |
| 89 | + if is_default_include_present and len(included_configs) != 1: |
| 90 | + # The additional included configs will most likely be created manually by the user |
| 91 | + # and therefore will get flagged as custom in the next part of this function |
| 92 | + api.current_logger().debug('The default include "{}" is not the only include in ' |
| 93 | + 'the {} file.'.format(LD_SO_CONF_DEFAULT_INCLUDE, LD_SO_CONF_MAIN)) |
| 94 | + |
| 95 | + main_config_file = MainLDConfigFile(path=LD_SO_CONF_MAIN, modified=any(other_lines), modified_lines=other_lines) |
| 96 | + |
| 97 | + # Expand the config paths from globs and ensure uniqueness of resulting paths |
| 98 | + config_paths = set() |
| 99 | + for cfg_glob in included_configs: |
| 100 | + for cfg in glob.glob(cfg_glob): |
| 101 | + config_paths.add(cfg) |
| 102 | + |
| 103 | + included_config_files = [] |
| 104 | + for config_path in config_paths: |
| 105 | + config_file = LDConfigFile(path=config_path, modified=_is_included_config_custom(config_path)) |
| 106 | + included_config_files.append(config_file) |
| 107 | + |
| 108 | + # Check if dynamic linker variables used for specifying custom libraries are set |
| 109 | + variables = [LD_LIBRARY_PATH_VAR, LD_PRELOAD_VAR] |
| 110 | + used_variables = [var for var in variables if os.getenv(var, None)] |
| 111 | + |
| 112 | + configuration = DynamicLinkerConfiguration(main_config=main_config_file, |
| 113 | + included_configs=included_config_files, |
| 114 | + used_variables=used_variables) |
| 115 | + |
| 116 | + if other_lines or any([config.modified for config in included_config_files]) or used_variables: |
| 117 | + api.produce(configuration) |
0 commit comments