Esempio n. 1
0
    def load_config(self, config=None):
        """
        Load the configuration files and append it to local dictionary.

        It's stored in self.configuration with the content of already loaded
        options.
        """
        if config:
            # just add the new config at the end of the list, someone injected
            # config file to us
            for path in config:
                if path not in self.conf_files and path.exists():
                    self.conf_files.append(path)

        cfg = {}
        for cf in sorted(self.conf_files, key=self._sort_config_files):
            try:
                toml_config = toml.load(cf)
                self._merge_dictionaries(cfg, toml_config,
                                         self._is_override_config(cf))
            except toml.decoder.TomlDecodeError as terr:
                print_warning(
                    f'(none): E: fatal error while parsing configuration file {cf}: {terr}'
                )
                sys.exit(4)
        self.configuration = cfg
Esempio n. 2
0
def process_diff_args(argv):
    """
    Process the passed arguments and return the result
    :param argv: passed arguments
    """

    parser = argparse.ArgumentParser(
        prog='rpmdiff',
        description='Shows basic differences between two rpm packages')
    parser.add_argument('old_package',
                        metavar='RPM_ORIG',
                        type=Path,
                        help='the old package')
    parser.add_argument('new_package',
                        metavar='RPM_NEW',
                        type=Path,
                        help='the new package')
    parser.add_argument('-V',
                        '--version',
                        action='version',
                        version=__version__,
                        help='show package version and exit')
    parser.add_argument(
        '-i',
        '--ignore',
        nargs='+',
        default=None,
        choices=['S', 'M', '5', 'D', 'N', 'L', 'V', 'U', 'G', 'F', 'T'],
        help="""file property to ignore when calculating differences.
                                Valid values are: S (size), M (mode), 5 (checksum), D (device),
                                N (inode), L (number of links), V (vflags), U (user), G (group),
                                F (digest), T (time)""")
    parser.add_argument('-e',
                        '--exclude',
                        metavar='GLOB',
                        nargs='+',
                        default=None,
                        help="""Paths to exclude when showing differences.
                                Takes a glob. When absolute (starting with /)
                                all files in a matching directory are excluded as well.
                                When relative, files matching the pattern anywhere
                                are excluded but not directory contents.""")

    # print help if there is no argument or less than the 2 mandatory ones
    if len(argv) < 2:
        parser.print_help()
        sys.exit(0)

    options = parser.parse_args(args=argv)
    # the rpms must exist for us to do anything
    if not options.old_package.exists():
        print_warning(f"The file '{options.old_package}' does not exist")
        exit(2)
    if not options.new_package.exists():
        print_warning(f"The file '{options.new_package}' does not exist")
        exit(2)

    # convert options to dict
    options_dict = vars(options)
    return options_dict
Esempio n. 3
0
    def find_configs(self, config=None):
        """
        Load all the configuration files from XDG_CONFIG_DIRS.
        User can override and then that is added too.
        """

        # first load up the file that contains defaults
        self.conf_files.append(self.config_defaults)

        # Then load up config directories on system
        for directory in reversed(xdg_config_dirs):
            confdir = Path(directory) / 'rpmlint'
            if confdir.is_dir():
                # load all configs in the folders
                confopts = sorted(confdir.glob('*config'))
                self.conf_files += confopts

        # As a last item load up the user configuration
        if config:
            if config.exists():
                # load this only if it really exist
                self.conf_files.append(config)
            else:
                print_warning(
                    '(none): W: error locating user requested configuration: {}'
                    .format(config))
Esempio n. 4
0
    def _extract_rpm(self, dirname, verbose):
        if not Path(dirname).is_dir():
            print_warning('Unable to access dir %s' % dirname)
        elif dirname == '/':
            # it is an InstalledPkg
            pass
        else:
            self.__tmpdir = tempfile.TemporaryDirectory(
                prefix='rpmlint.%s.' % Path(self.filename).name, dir=dirname)
            dirname = self.__tmpdir.name
            # TODO: sequence based command invocation
            # TODO: warn some way if this fails (e.g. rpm2cpio not installed)

            # BusyBox' cpio does not support '-D' argument and the only safe
            # usage is doing chdir before invocation.
            filename = Path(self.filename).resolve()
            cwd = os.getcwd()
            os.chdir(dirname)
            command_str = f'rpm2cpio {quote(str(filename))} | cpio -id ; chmod -R +rX .'
            stderr = None if verbose else subprocess.DEVNULL
            subprocess.check_output(command_str,
                                    shell=True,
                                    env=ENGLISH_ENVIROMENT,
                                    stderr=stderr)
            os.chdir(cwd)
            self.extracted = True
        return dirname
Esempio n. 5
0
def _validate_conf_location(string):
    """
    Help validate configuration location during argument parsing.

    We accept either one configuration file or a directory (then it processes
    all *.toml files in this directory). It exits the program if location
    doesn't exist.

    Args:
        string: A string representing configuration path (file or directory).

    Returns:
        A list with individual paths for each configuration file found.
    """
    config_paths = []
    path = Path(string)

    # Exit if file or dir doesn't exist
    if not path.exists():
        print_warning(
            f"File or dir with user specified configuration '{string}' does not exist"
        )
        exit(2)

    if path.is_dir():
        config_paths.extend(path.glob('*.toml'))
    elif path.is_file():
        config_paths.append(path)

    return config_paths
Esempio n. 6
0
    def find_configs(self, config=None):
        """
        Find and store paths to all config files.

        It searches for default configuration, files in XDG_CONFIG_DIRS and
        user defined configuration (argument "config"). All configuration
        file paths found are then stored in self.conf_files variable.
        XDG_CONFIG_DIRS contains preference-ordered set of base directories
        to search for configuration files. Users can override it by their
        own configuration file (config parameter) and then that is
        added too.
        """
        # first load up the file that contains defaults
        self.conf_files.append(self.config_defaults)

        # Skip auto-loading when running under PYTEST
        if not os.environ.get('PYTEST_XDIST_TESTRUNUID'):
            # Then load up config directories on system
            for directory in reversed(xdg_config_dirs):
                confdir = Path(directory) / 'rpmlint'
                if confdir.is_dir():
                    # load all configs in the folders
                    confopts = sorted(confdir.glob('*toml'))
                    self.conf_files += confopts

        # As a last item load up the user configuration
        if config:
            for path in config:
                if path.exists():
                    # load this only if it really exist
                    self.conf_files.append(path)
                else:
                    print_warning(
                        f'(none): W: error locating user requested configuration: {path}'
                    )
Esempio n. 7
0
    def _init_checker(self, lang='en_US'):
        """
        Initialize a checker of selected language if it is not yet present
        lang: language to initialize the dictionary
        """

        # C language means English
        if lang == 'C':
            lang = 'en_US'

        # test if we actually have working enchant
        if not ENCHANT:
            print_warning(
                '(none): W: unable to init enchant, spellchecking disabled.')
            return

        # there might not be myspell/aspell/etc dicts present
        broker = Broker()
        if not broker.dict_exists(lang):
            print_warning(
                f'(none): W: unable to load spellchecking dictionary for {lang}.'
            )
            return

        if lang not in self._enchant_checkers:
            checker = SpellChecker(
                lang, filters=[EmailFilter, URLFilter, WikiWordFilter])
            self._enchant_checkers[lang] = checker
Esempio n. 8
0
 def run(self):
     # if we just want to print config, do so and leave
     if self.options['print_config']:
         self.print_config()
         return 0
     # just explain the error and abort too
     if self.options['explain']:
         self.print_explanation(self.options['explain'])
         return 0
     # if no exclusive option is passed then just loop over all the
     # arguments that are supposed to be either rpm or spec files
     self.validate_files(self.options['rpmfile'])
     print(self.output.print_results(self.output.results))
     print('{} packages and {} specfiles checked; {} errors, {} warnings'.
           format(self.packages_checked, self.specfiles_checked,
                  self.output.printed_messages['E'],
                  self.output.printed_messages['W']))
     if self.output.badness_threshold > 0 and self.output.score > self.output.badness_threshold:
         print_warning(
             f'(none): E: Badness {self.output.score} exceeeds threshold {self.output.badness_threshold}, aborting.'
         )
         return 66
     if self.output.printed_messages['E'] > 0:
         return 64
     return 0
Esempio n. 9
0
 def _load_rpmlintrc(self):
     """
     Load rpmlintrc from argument or load up from folder
     """
     if self.options['rpmlintrc']:
         self.config.load_rpmlintrc(self.options['rpmlintrc'])
     else:
         # load only from the same folder specname.rpmlintrc or specname-rpmlintrc
         # do this only in a case where there is one folder parameter or one file
         # to avoid multiple folders handling
         rpmlintrc = []
         if not len(self.options['rpmfile']) == 1:
             return
         pkg = self.options['rpmfile'][0]
         if pkg.is_file():
             pkg = pkg.parent
         rpmlintrc += sorted(pkg.glob('*.rpmlintrc'))
         rpmlintrc += sorted(pkg.glob('*-rpmlintrc'))
         if len(rpmlintrc) > 1:
             # multiple rpmlintrcs are highly undesirable
             print_warning(
                 'There are multiple items to be loaded for rpmlintrc, ignoring them: {}.'
                 .format(' '.join(map(str, rpmlintrc))))
         elif len(rpmlintrc) == 1:
             self.options['rpmlintrc'] = rpmlintrc[0]
             self.config.load_rpmlintrc(rpmlintrc[0])
Esempio n. 10
0
 def _load_rpmlintrc(self):
     """
     Load rpmlintrc from argument or load up from folder
     """
     if self.options['rpmlintrc']:
         # Right now, we allow loading of just a single file, but the 'opensuse'
         # branch contains auto-loading mechanism that can eventually load
         # multiple files.
         for rcfile in self.options['rpmlintrc']:
             self.config.load_rpmlintrc(rcfile)
     else:
         # load only from the same folder specname.rpmlintrc or specname-rpmlintrc
         # do this only in a case where there is one folder parameter or one file
         # to avoid multiple folders handling
         rpmlintrc = []
         if len(self.options['rpmfile']) != 1:
             return
         pkg = self.options['rpmfile'][0]
         if pkg.is_file():
             pkg = pkg.parent
         rpmlintrc += sorted(pkg.glob('*.rpmlintrc'))
         rpmlintrc += sorted(pkg.glob('*-rpmlintrc'))
         if len(rpmlintrc) > 1:
             # multiple rpmlintrcs are highly undesirable
             print_warning(
                 'There are multiple items to be loaded for rpmlintrc, ignoring them: {}.'
                 .format(' '.join(map(str, rpmlintrc))))
         elif len(rpmlintrc) == 1:
             self.options['rpmlintrc'] = rpmlintrc[0]
             self.config.load_rpmlintrc(rpmlintrc[0])
Esempio n. 11
0
def process_lint_args(argv):
    """
    Process the passed arguments and return the result
    :param argv: passed arguments
    """

    parser = argparse.ArgumentParser(prog='rpmlint',
                                     description='Check for common problems in rpm packages')
    parser.add_argument('rpmfile', nargs='*', type=Path, help='files to be validated by rpmlint')
    parser.add_argument('-V', '--version', action='version', version=__version__, help='show package version and exit')
    parser.add_argument('-c', '--config', type=_validate_conf_location, help='load up additional configuration data from specified path (file or directory with *.toml files')
    parser.add_argument('-e', '--explain', nargs='+', default='', help='provide detailed explanation for one specific message id')
    parser.add_argument('-r', '--rpmlintrc', type=Path, help='load up specified rpmlintrc file')
    parser.add_argument('-v', '--verbose', '--info', action='store_true', help='provide detailed explanations where available')
    parser.add_argument('-p', '--print-config', action='store_true', help='print the settings that are in effect when using the rpmlint')
    parser.add_argument('-i', '--installed', nargs='+', default='', help='installed packages to be validated by rpmlint')
    parser.add_argument('-t', '--time-report', action='store_true', help='print time report for run checks')
    parser.add_argument('-T', '--profile', action='store_true', help='print cProfile report')
    lint_modes_parser = parser.add_mutually_exclusive_group()
    lint_modes_parser.add_argument('-s', '--strict', action='store_true', help='treat all messages as errors')
    lint_modes_parser.add_argument('-P', '--permissive', action='store_true', help='treat individual errors as non-fatal')

    # print help if there is no argument
    if len(argv) < 1:
        parser.print_help()
        sys.exit(0)

    options = parser.parse_args(args=argv)

    # make sure rpmlintrc exists
    if options.rpmlintrc:
        if not options.rpmlintrc.exists():
            print_warning(f"User specified rpmlintrc '{options.rpmlintrc}' does not exist")
            exit(2)
    # validate all the rpmlfile options to be either file or folder
    f_path = []
    invalid_path = False
    for item in options.rpmfile:
        p_path = Path()
        pattern = None
        for pos, component in enumerate(item.parts):
            if ('*' in component) or ('?' in component):
                pattern = '/'.join(item.parts[pos:])
                break
            p_path = p_path / component
        p_path = list(p_path.glob(pattern)) if pattern else [p_path]

        for path in p_path:
            if not path.exists():
                print_warning(f"The file or directory '{path}' does not exist")
                invalid_path = True
        f_path += p_path

    if invalid_path:
        exit(2)
    # convert options to dict
    options_dict = vars(options)
    # use computed rpmfile
    options_dict['rpmfile'] = f_path
    return options_dict
Esempio n. 12
0
def runChecks(pkg):
    for name in cfg.configuration['Checks']:
        check = AbstractCheck.known_checks.get(name)
        if check:
            check.verbose = verbose
            check.check(pkg)
        else:
            print_warning('(none): W: unknown check %s, skipping' % name)
Esempio n. 13
0
def runSpecChecks(pkg, fname, spec_lines=None):
    for name in cfg.configuration['Checks']:
        check = AbstractCheck.known_checks.get(name)
        if check:
            check.verbose = verbose
            check.check_spec(pkg, fname, spec_lines)
        else:
            print_warning('(none): W: unknown check %s, skipping' % name)
Esempio n. 14
0
File: lint.py Progetto: lbt/rpmlint
 def _load_installed_rpms(self, packages):
     existing_packages = []
     for name in packages:
         pkg = getInstalledPkgs(name)
         if pkg:
             existing_packages.extend(pkg)
         else:
             print_warning(f'(none): E: there is no installed rpm "{name}".')
     return existing_packages
Esempio n. 15
0
 def _load_descriptions():
     descriptions = {}
     descr_folder = Path(__file__).parent / 'descriptions'
     try:
         description_files = sorted(descr_folder.glob('*.toml'))
         descriptions = toml.load(description_files)
     except toml.decoder.TomlDecodeError as terr:
         print_warning(f'(none): W: unable to parse description files: {terr}')
     return descriptions
Esempio n. 16
0
def test_warnprint(capsys):
    """
    Check we print stuff to stderr
    """
    message = 'I am writing to stderr'
    helpers.print_warning(message)
    out, err = capsys.readouterr()
    assert message not in out
    assert message in err
Esempio n. 17
0
File: lint.py Progetto: lbt/rpmlint
 def validate_file(self, pname):
     try:
         if pname.suffix == '.rpm' or pname.suffix == '.spm':
             with Pkg(pname, self.config.configuration['ExtractDir']) as pkg:
                 self.run_checks(pkg)
         elif pname.suffix == '.spec':
             with FakePkg(pname) as pkg:
                 self.run_spec_checks(pkg)
     except Exception as e:
         print_warning(f'(none): E: while reading {pname}: {e}')
Esempio n. 18
0
    def check(self, pkg):
        retcode, output = pkg.check_signature()

        # Skip all signature checks if check_signature output is empty
        if output is None:
            print_warning(f'No output from check_signature() for '
                          f'{pkg.filename}. Skipping signature checks.')
            return

        self._check_no_signature(pkg, retcode, output)
        self._check_unknown_key(pkg, retcode, output)
        self._check_invalid_signature(pkg, retcode, output)
Esempio n. 19
0
 def add_check(self, check):
     """
     Add specified file to be loaded up by checks.
     Check is just a string file.
     It used to be possible to specify additional locations for checks
     but to keep it simple all checks must be part of rpmlint package
      -> from rpmlint.checks.<CHECKNAME> import *
     """
     # Validate first if it is possible to import the added check
     if find_spec('.{}'.format(check), package='rpmlint.checks'):
         self.configuration['Checks'].append(check)
     else:
         print_warning(
             '(none): W: error adding requested check: {}'.format(check))
Esempio n. 20
0
 def grep(self, regex, filename):
     """Grep regex from a file, return matching line numbers."""
     ret = []
     lineno = 0
     try:
         with open(os.path.join(
                 self.dirName() or '/', filename.lstrip('/'))) as in_file:
             for line in in_file:
                 lineno += 1
                 if regex.search(line):
                     ret.append(str(lineno))
                     break
     except Exception as e:
         print_warning(f'Unable to read {filename}: {e}')
     return ret
Esempio n. 21
0
 def _extract(self):
     if not Path(self.dirname).is_dir():
         print_warning('Unable to access dir %s' % self.dirname)
     else:
         self.__tmpdir = tempfile.TemporaryDirectory(
             prefix='rpmlint.%s.' % Path(self.filename).name,
             dir=self.dirname)
         self.dirname = self.__tmpdir.name
         # TODO: sequence based command invocation
         # TODO: warn some way if this fails (e.g. rpm2cpio not installed)
         command_str = \
             'rpm2cpio %(f)s | cpio -id -D %(d)s ; chmod -R +rX %(d)s' % \
             {'f': quote(str(self.filename)), 'd': quote(str(self.dirname))}
         subprocess.run(command_str, shell=True)
         self.extracted = True
Esempio n. 22
0
 def check(self, pkg):
     res = pkg.checkSignature()
     if not res or res[0] != 0:
         if res and res[1]:
             kres = SignatureCheck.unknown_key_regex.search(res[1])
         else:
             kres = None
         if kres:
             self.output.add_info('E', pkg, 'unknown-key', kres.group(1))
         else:
             print_warning('Error checking signature of %s: %s' %
                           (pkg.filename, res[1]))
     else:
         if not SignatureCheck.pgp_regex.search(res[1]):
             self.output.add_info('E', pkg, 'no-signature')
Esempio n. 23
0
File: lint.py Progetto: lbt/rpmlint
 def validate_files(self, files):
     """
     Run all the check for passed file list
     """
     if not files:
         if self.packages_checked == 0:
             # print warning only if we didn't process even installed files
             print_warning('There are no files to process nor additional arguments.')
             print_warning('Nothing to do, aborting.')
         return
     # check all elements if they are a folder or a file with proper suffix
     # and expand everything
     packages = self._expand_filelist(files)
     for pkg in packages:
         self.validate_file(pkg)
Esempio n. 24
0
 def load_config(self, config=None):
     """
     Load the configuration files and append it to local dictionary with the
     content of already loaded options.
     """
     if config and config not in self.conf_files:
         # just add the config at the end of the list, someone injected
         # config file to us
         if config.exists():
             self.conf_files.append(config)
     try:
         cfg = toml.load(self.conf_files)
     except toml.decoder.TomlDecodeError as terr:
         print_warning(f'(none): W: error parsing configuration files: {terr}')
         cfg = None
     self.configuration = cfg
Esempio n. 25
0
 def _extract(self):
     if not os.path.isdir(self.dirname):
         print_warning('Unable to access dir %s' % self.dirname)
         return None
     else:
         self.dirname = tempfile.mkdtemp(
             prefix='rpmlint.%s.' % os.path.basename(self.filename),
             dir=self.dirname)
         # TODO: sequence based command invocation
         # TODO: warn some way if this fails (e.g. rpm2cpio not installed)
         command_str = \
             'rpm2cpio %(f)s | (cd %(d)s; cpio -id); chmod -R +rX %(d)s' % \
             {'f': shquote(self.filename), 'd': shquote(self.dirname)}
         cmd = getstatusoutput(command_str, shell=True)
         self.extracted = True
         return cmd
Esempio n. 26
0
 def validate_file(self, pname, is_last):
     try:
         if pname.suffix == '.rpm' or pname.suffix == '.spm':
             with Pkg(pname,
                      self.config.configuration['ExtractDir'],
                      verbose=self.config.info) as pkg:
                 self.check_duration['rpm2cpio'] += pkg.extraction_time
                 self.run_checks(pkg, is_last)
         elif pname.suffix == '.spec':
             with FakePkg(pname) as pkg:
                 self.run_checks(pkg, is_last)
     except Exception as e:
         print_warning(f'(none): E: fatal error while reading {pname}: {e}')
         if self.config.info:
             raise e
         else:
             sys.exit(3)
Esempio n. 27
0
 def load_rpmlintrc(self, rpmlint_file):
     """
     Function to load up existing rpmlintrc files
     Only setBadness and addFilter are processed
     """
     if not self.configuration:
         print_warning(
             '(none): W: loading rpmlint before configuration is not allowed: {}'
             .format(rpmlint_file))
         return
     with open(rpmlint_file) as f:
         rpmlintrc_content = f.read()
     filters = self.re_filter.findall(rpmlintrc_content)
     self.configuration['Filters'] += filters
     badness = self.re_badness.findall(rpmlintrc_content)
     for entry in badness:
         self.configuration['Scoring'].update({entry[0]: entry[1]})
Esempio n. 28
0
 def _extract(self, dirname, verbose):
     if not Path(dirname).is_dir():
         print_warning('Unable to access dir %s' % dirname)
     else:
         dirname = dirname if dirname != '/' else None
         self.__tmpdir = tempfile.TemporaryDirectory(
             prefix='rpmlint.%s.' % Path(self.filename).name, dir=dirname
         )
         dirname = self.__tmpdir.name
         # TODO: sequence based command invocation
         # TODO: warn some way if this fails (e.g. rpm2cpio not installed)
         command_str = \
             'rpm2cpio %(f)s | cpio -id -D %(d)s ; chmod -R +rX %(d)s' % \
             {'f': quote(str(self.filename)), 'd': quote(dirname)}
         stderr = None if verbose else subprocess.DEVNULL
         subprocess.check_output(command_str, shell=True, env=ENGLISH_ENVIROMENT,
                                 stderr=stderr)
         self.extracted = True
     return dirname
Esempio n. 29
0
    def _load_descriptions():
        """
        Load rpmlint error/warning description texts from toml files.

        Detailed description for every rpmlint error/warning is stored in
        descriptions/<check_name>.toml file.

        Returns:
            A dictionary mapping error/warning/info names to their
            descriptions.
         """
        descriptions = {}
        descr_folder = Path(__file__).parent / 'descriptions'
        try:
            description_files = sorted(descr_folder.glob('*.toml'))
            descriptions = toml.load(description_files)
        except toml.decoder.TomlDecodeError as terr:
            print_warning(
                f'(none): W: unable to parse description files: {terr}')
        return descriptions
Esempio n. 30
0
    def load_config(self, config):
        """
        Load the configuration file and append it to local dictionary with the
        content of already loaded options.
        """
        if config not in self.conf_files:
            # just add the config for tracking purposes, someone injected
            # config file to us
            self.conf_files.append(config)

        # load and validate initial config
        val = Validator()
        configspec = ConfigObj(self.__configspecfilename, _inspec=True)
        cfg = ConfigObj(config, configspec=configspec)
        if not cfg.validate(val):
            print_warning(
                '(none): W: error parsing configuration file: {}'.format(
                    config))
        # load multiline defaults
        cfg = self._load_defaults(cfg, DEFAULTS)
        cfg = self._load_defaults(cfg, DICT_DEFAULTS)
        # convert all list items to real lists
        cfg = self._convert_known_lists(cfg, self.known_lists_merged)
        cfg = self._convert_known_lists(cfg, self.known_lists_override, True)
        # for merging we have duplicate object without filled in defaults
        result = ConfigObj(config)
        # conver the result stuff to lists too
        result = self._convert_known_lists(result, self.known_lists_merged)
        result = self._convert_known_lists(result, self.known_lists_override,
                                           True)
        # merge the dict on where we are merging lists
        for i in self.known_lists_merged:
            if self.configuration:
                if i in self.configuration and i in result:
                    result[i] = result[i] + self.configuration[i]

        # Merge stuff in a case we alrady have config
        if self.configuration:
            self.configuration.merge(result)
        else:
            self.configuration = cfg