class AcctCheck(Check): """Various checks for acct-* packages. Verify that acct-* packages do not use conflicting, invalid or out-of-range UIDs/GIDs. """ scope = base.repo_scope _restricted_source = (sources.RestrictionRepoSource, (packages.OrRestriction(*( restricts.CategoryDep('acct-user'), restricts.CategoryDep('acct-group'))),)) _source = (sources.RepositoryRepoSource, (), (('source', _restricted_source),)) known_results = frozenset([ MissingAccountIdentifier, ConflictingAccountIdentifiers, OutsideRangeAccountIdentifier, ]) def __init__(self, *args): super().__init__(*args) self.id_re = re.compile( r'ACCT_(?P<var>USER|GROUP)_ID=(?P<quot>[\'"]?)(?P<id>[0-9]+)(?P=quot)') self.seen_uids = defaultdict(partial(defaultdict, list)) self.seen_gids = defaultdict(partial(defaultdict, list)) self.category_map = { 'acct-user': (self.seen_uids, 'USER', (65534,)), 'acct-group': (self.seen_gids, 'GROUP', (65533, 65534)), } def feed(self, pkg): try: seen_id_map, expected_var, extra_allowed_ids = self.category_map[pkg.category] except KeyError: return for l in pkg.ebuild.text_fileobj(): m = self.id_re.match(l) if m is not None: if m.group('var') == expected_var: found_id = int(m.group('id')) break else: yield MissingAccountIdentifier(f"ACCT_{expected_var}_ID", pkg=pkg) return # all UIDs/GIDs must be in <500, with special exception # of nobody/nogroup which use 65534/65533 if found_id >= 500 and found_id not in extra_allowed_ids: yield OutsideRangeAccountIdentifier(expected_var.lower(), found_id, pkg=pkg) return seen_id_map[found_id][pkg.key].append(pkg) def finish(self): # report overlapping ID usage for seen, expected_var, _ids in self.category_map.values(): for found_id, pkgs in sorted(seen.items()): if len(pkgs) > 1: pkgs = (x.cpvstr for x in sorted(chain.from_iterable(pkgs.values()))) yield ConflictingAccountIdentifiers(expected_var.lower(), found_id, pkgs)
def restrictions(self): # ordering here matters; against 24702 ebuilds for # a non matchable atom with package as the first restriction # 10 loops, best of 3: 206 msec per loop # with category as the first(the obvious ordering) # 10 loops, best of 3: 209 msec per loop # why? because category is more likely to collide; # at the time of this profiling, there were 151 categories. # over 11k packages however. r = [restricts.PackageDep(self.package), restricts.CategoryDep(self.category)] if self.repo_id is not None: r.insert(0, restricts.RepositoryDep(self.repo_id)) if self.fullver is not None: if self.op == '=*': r.append(packages.PackageRestriction( "fullver", values.StrGlobMatch(self.fullver))) else: r.append(restricts.VersionMatch( self.op, self.version, self.revision, negate=self.negate_vers)) if self.slot is not None: r.append(restricts.SlotDep(self.slot)) if self.subslot is not None: r.append(restricts.SubSlotDep(self.subslot)) if self.use is not None: r.extend(restricts._parse_nontransitive_use(self.use)) return tuple(r)
def test_selected_targets(self, fakerepo): # selected repo options, _func = self.tool.parse_args(self.args + ['-r', 'stubrepo']) assert options.target_repo.repo_id == 'stubrepo' assert options.restrictions == [(base.repository_scope, packages.AlwaysTrue)] # dir path options, _func = self.tool.parse_args(self.args + [fakerepo]) assert options.target_repo.repo_id == 'fakerepo' assert options.restrictions == [(base.repository_scope, packages.AlwaysTrue)] # file path os.makedirs(pjoin(fakerepo, 'dev-util', 'foo')) ebuild_path = pjoin(fakerepo, 'dev-util', 'foo', 'foo-0.ebuild') touch(ebuild_path) options, _func = self.tool.parse_args(self.args + [ebuild_path]) restrictions = [ restricts.CategoryDep('dev-util'), restricts.PackageDep('foo'), restricts.VersionMatch('=', '0'), ] assert list(options.restrictions) == [ (base.version_scope, packages.AndRestriction(*restrictions)) ] assert options.target_repo.repo_id == 'fakerepo' # cwd path in unconfigured repo with chdir(pjoin(fakerepo, 'dev-util', 'foo')): options, _func = self.tool.parse_args(self.args) assert options.target_repo.repo_id == 'fakerepo' restrictions = [ restricts.CategoryDep('dev-util'), restricts.PackageDep('foo'), ] assert list(options.restrictions) == [ (base.package_scope, packages.AndRestriction(*restrictions)) ] # cwd path in configured repo stubrepo = pjoin(pkgcore_const.DATA_PATH, 'stubrepo') with chdir(stubrepo): options, _func = self.tool.parse_args(self.args) assert options.target_repo.repo_id == 'stubrepo' assert list(options.restrictions) == [(base.repository_scope, packages.AlwaysTrue)]
def path_restrict(self, path): """Return a restriction from a given path in a repo. :param path: full or partial path to an ebuild :return: a package restriction matching the given path if possible :raises ValueError: if the repo doesn't contain the given path, the path relates to a file that isn't an ebuild, or the ebuild isn't in the proper directory layout """ if path not in self: raise ValueError( f"{self.repo_id!r} repo doesn't contain: {path!r}") if not path.startswith(os.sep) and os.path.exists( pjoin(self.location, path)): path_chunks = path.split(os.path.sep) else: path = os.path.realpath(os.path.abspath(path)) relpath = path[len(os.path.realpath(self.location)):].strip('/') path_chunks = relpath.split(os.path.sep) if os.path.isfile(path): if not path.endswith('.ebuild'): raise ValueError(f"file is not an ebuild: {path!r}") elif len(path_chunks) != 3: # ebuild isn't in a category/PN directory raise ValueError( f"ebuild not in the correct directory layout: {path!r}") restrictions = [] # add restrictions until path components run out try: restrictions.append(restricts.RepositoryDep(self.repo_id)) if path_chunks[0] in self.categories: restrictions.append(restricts.CategoryDep(path_chunks[0])) restrictions.append(restricts.PackageDep(path_chunks[1])) base = cpv.VersionedCPV( f"{path_chunks[0]}/{os.path.splitext(path_chunks[2])[0]}") restrictions.append( restricts.VersionMatch('=', base.version, rev=base.revision)) except IndexError: pass return packages.AndRestriction(*restrictions)