Skip to content

Commit b76976f

Browse files
Fix dublicate Requires issue in pcscd.service
1 parent 48741ea commit b76976f

3 files changed

Lines changed: 125 additions & 10 deletions

File tree

SCAutolib/models/file.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from __future__ import annotations
1212

1313
import os
14-
from configparser import ConfigParser
1514
from pathlib import Path
1615
from shutil import copy2
1716
from traceback import format_exc
@@ -21,7 +20,7 @@
2120
from SCAutolib import logger, TEMPLATES_DIR, LIB_BACKUP, LIB_DUMP_CONFS, run
2221
from SCAutolib.exceptions import SCAutolibFileExists, SCAutolibWrongConfig, \
2322
SCAutolibNoTemplate
24-
from SCAutolib.utils import isDistro
23+
from SCAutolib.utils import isDistro, CustomConfigParser
2524

2625

2726
class File:
@@ -85,7 +84,7 @@ def create(self):
8584
logger.warning(f"Create error: {self._conf_file} already exists.")
8685
raise SCAutolibFileExists(f'{self._conf_file} already exists')
8786
else:
88-
self._default_parser = ConfigParser()
87+
self._default_parser = CustomConfigParser()
8988
self._default_parser.optionxform = str
9089
if self._template is None:
9190
raise SCAutolibNoTemplate("Template file was not provided.")
@@ -171,7 +170,7 @@ def set(
171170
else:
172171
# configparser compatible config files (with sections)
173172
if self._default_parser is None:
174-
self._default_parser = ConfigParser()
173+
self._default_parser = CustomConfigParser()
175174
self._default_parser.optionxform = str
176175
with self._conf_file.open() as config:
177176
self._default_parser.read_file(config)
@@ -223,7 +222,7 @@ def get(self, key, section: str = None, separator: str = "=") -> str:
223222
raise SCAutolibWrongConfig(f"Key '{key}' doesn't present in the "
224223
f"file {self._conf_file}")
225224
elif self._default_parser is None:
226-
self._default_parser = ConfigParser()
225+
self._default_parser = CustomConfigParser()
227226
self._default_parser.optionxform = str
228227
with self._conf_file.open() as config:
229228
self._default_parser.read_file(config)
@@ -347,7 +346,7 @@ def __init__(self):
347346
self._template = TEMPLATES_DIR.joinpath("sssd.conf-10")
348347

349348
# _default_parser object stores default content of config file
350-
self._default_parser = ConfigParser()
349+
self._default_parser = CustomConfigParser()
351350
# avoid problems with inserting some 'specific' values
352351
self._default_parser.optionxform = str
353352

@@ -356,7 +355,7 @@ def __init__(self):
356355
self._default_parser.read_file(config)
357356

358357
# _changes parser object reflects modifications imposed by set method
359-
self._changes = ConfigParser()
358+
self._changes = CustomConfigParser()
360359
self._changes.optionxform = str
361360

362361
if self.dump_file.exists():
@@ -530,7 +529,7 @@ def save(self):
530529
self._changes.write(config)
531530
# re-initialization because I did not find other simple way
532531
# to empty parser object
533-
self._changes = ConfigParser()
532+
self._changes = CustomConfigParser()
534533
self._changes.optionxform = str
535534
os.chmod(self._conf_file, 0o600)
536535

@@ -569,7 +568,7 @@ def update_default_content(self):
569568
:return: None
570569
:rtype: None
571570
"""
572-
self._default_parser = ConfigParser()
571+
self._default_parser = CustomConfigParser()
573572
self._default_parser.optionxform = str
574573
with self._conf_file.open() as config:
575574
self._default_parser.read_file(config)

SCAutolib/utils.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,126 @@
1313
from cryptography.hazmat.primitives.asymmetric import rsa
1414
from typing import Union
1515
from pathlib import Path
16+
from configparser import ConfigParser
17+
from io import TextIOWrapper
1618

1719
from SCAutolib import run, logger, TEMPLATES_DIR, LIB_BACKUP
1820

1921

22+
class MultiEntryDict(dict):
23+
"""
24+
A dictionary subclass that merges list values for duplicate keys.
25+
26+
This dictionary is customized for use with `ConfigParser` where multi-line
27+
or duplicate options may be processed as lists. Instead of
28+
overwriting an existing list value, it appends new list items to the
29+
existing ones.
30+
"""
31+
32+
def __setitem__(self, key: str, value: list[str]):
33+
"""
34+
Set the value for a given key, merging lists if the key already exists.
35+
36+
If both the existing value and the incoming value are lists, they are
37+
concatenated. Otherwise, the standard dictionary assignment is used.
38+
39+
:param key: The key to set.
40+
:type key: str
41+
:param value: The value to associate with the key.
42+
:type value: list[str]
43+
:return: None
44+
:rtype: None
45+
"""
46+
# ConfigParser temporarily stores values as a list of lines during
47+
# parsing. If the key already exists, we merge the lists instead of
48+
# overwriting.
49+
if (
50+
key in self and
51+
isinstance(value, list) and
52+
isinstance(self[key], list)
53+
):
54+
super().__setitem__(key, self[key] + value)
55+
else:
56+
super().__setitem__(key, value)
57+
58+
59+
class CustomConfigParser(ConfigParser):
60+
"""
61+
A custom ConfigParser that supports duplicate keys and continuations.
62+
63+
By utilizing `MultiEntryDict` as its internal dictionary type and disabling
64+
strict mode, this parser handles configuration files containing multiple
65+
identical keys within a single section without overwriting them.
66+
"""
67+
68+
def __init__(self, *args, **kwargs):
69+
"""
70+
Initialize the CustomConfigParser with non-strict multi-entry support.
71+
72+
:param args: Positional arguments passed to the parent `ConfigParser`.
73+
:type args: list
74+
:param kwargs: Keyword arguments passed to the parent `ConfigParser`.
75+
:type kwargs: dict
76+
:return: None
77+
:rtype: None
78+
"""
79+
super().__init__(
80+
*args, **kwargs, dict_type=MultiEntryDict, strict=False,
81+
)
82+
83+
def _write_section(
84+
self, fp: TextIOWrapper, section_name: str, section_items: list[str],
85+
delimiter: str
86+
):
87+
r"""
88+
Write a single section and its items to a file-like object.
89+
90+
This overrides the default section writing behavior to match formats
91+
like systemd configuration files. It duplicates key names for
92+
multi-line entries unless a line explicitly ends with a backslash
93+
(`\\`), which triggers a standard indented continuation line.
94+
95+
:param fp: A file-like object open for writing.
96+
:type fp: TextIOWrapper
97+
:param section_name: The name of the section being written.
98+
:type section_name: str
99+
:param section_items: An iterable of (key, value) pairs to write.
100+
:type section_items: list[str]
101+
:param delimiter: The delimiter string separating keys and values.
102+
:type delimiter: str
103+
:return: None
104+
:rtype: None
105+
"""
106+
fp.write("[{}]\n".format(section_name))
107+
for key, value in section_items:
108+
value = self._interpolation.before_write(
109+
self, section_name, key, value)
110+
if value is not None:
111+
lines = str(value).split('\n')
112+
is_continuation = False
113+
114+
for line in lines:
115+
if is_continuation:
116+
# Keep it as an indented continuation line if the
117+
# previous line ended in \
118+
fp.write("\t{}\n".format(line.strip()))
119+
else:
120+
# Otherwise, repeat the key name for duplicate options
121+
fp.write("{}{}{}\n".format(key, delimiter, line))
122+
123+
# Systemd uses a trailing backslash to denote a split
124+
# single line
125+
if line.strip().endswith('\\'):
126+
is_continuation = True
127+
else:
128+
is_continuation = False
129+
elif not self._allow_no_value:
130+
fp.write("{}{}\n".format(key, delimiter))
131+
else:
132+
fp.write("{}\n".format(key))
133+
fp.write("\n")
134+
135+
20136
def _check_selinux():
21137
"""
22138
Check if the 'virtcacard' SELinux module is active.

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
setup(
2626
name="SCAutolib",
27-
version="3.5.5",
27+
version="3.5.6",
2828
description=description,
2929
long_description=long_description,
3030
long_description_content_type='text/markdown',

0 commit comments

Comments
 (0)