Skip to content

Commit 9f0e79c

Browse files
Handle failures when creating dummy sources (#271)
Handle failures when creating dummy sources This makes it possible to parse a spec file on a read-only filesystem for instance. At the cost of potentially incorrectly parsed %prep in case some sources are missing. Related to fedora-copr/copr#2839. RELEASE NOTES BEGIN specfile no longer tracebacks when some sources are missing and can't be emulated. In such case the spec file is parsed without them at the cost of %setup and %patch macros potentially expanding differently than with the sources present. RELEASE NOTES END Reviewed-by: Laura Barcziová Reviewed-by: Nikola Forró
2 parents 2965a6a + 3233d9a commit 9f0e79c

File tree

2 files changed

+58
-9
lines changed

2 files changed

+58
-9
lines changed

specfile/spec_parser.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,23 @@ def _make_dummy_sources(
9999
Yields:
100100
List of paths to each created dummy source.
101101
"""
102+
103+
def write_magic(path, magic):
104+
# create a file with a signature matching what RPM expects for this particular
105+
# file type; this affects how %setup and %patch macros are expanded
106+
107+
# number of bytes that RPM reads to determine the file type
108+
MAGIC_LENGTH = 13
109+
try:
110+
if magic:
111+
path.write_bytes(magic.ljust(MAGIC_LENGTH, b"\x00"))
112+
else:
113+
path.write_bytes(MAGIC_LENGTH * b"\x00")
114+
except (FileNotFoundError, OSError, PermissionError):
115+
logger.warning(f"Failed to create a dummy source: {path}")
116+
return False
117+
return True
118+
102119
# based on rpmFileIsCompressed() in rpmio/rpmfileutil.c in RPM source
103120
SIGNATURES = [
104121
(".bz2", b"BZh"),
@@ -110,32 +127,35 @@ def _make_dummy_sources(
110127
(".gz", b"\x1f\x8b"),
111128
(".7z", b"7z\xbc\xaf\x27\x1c"),
112129
]
113-
# number of bytes that RPM reads to determine the file type
114-
MAGIC_LENGTH = 13
115130
dummy_sources = []
116131
for source in sources:
117132
filename = get_filename_from_location(source)
118133
if not filename:
119134
continue
120135
path = self.sourcedir / filename
121-
if path.is_file():
136+
if path.exists():
122137
continue
123-
dummy_sources.append(path)
124138
for ext, magic in SIGNATURES:
125139
if filename.endswith(ext):
126-
path.write_bytes(magic.ljust(MAGIC_LENGTH, b"\x00"))
140+
if write_magic(path, magic):
141+
dummy_sources.append(path)
127142
break
128143
else:
129-
path.write_bytes(MAGIC_LENGTH * b"\x00")
144+
if write_magic(path, None):
145+
dummy_sources.append(path)
130146
for source in non_empty_sources:
131147
filename = get_filename_from_location(source)
132148
if not filename:
133149
continue
134150
path = self.sourcedir / filename
135-
if path.is_file():
151+
if path.exists():
136152
continue
137-
dummy_sources.append(path)
138-
path.write_text("DUMMY")
153+
try:
154+
path.write_text("DUMMY")
155+
except (FileNotFoundError, OSError, PermissionError):
156+
logger.warning(f"Failed to create a dummy source: {path}")
157+
else:
158+
dummy_sources.append(path)
139159
try:
140160
yield dummy_sources
141161
finally:

tests/unit/test_spec_parser.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,32 @@ def test_spec_parser_macros():
6464
"Test package\n"
6565
),
6666
)
67+
68+
69+
def test_spec_parser_make_dummy_sources(tmp_path):
70+
regular_source = "regular-source.zip"
71+
non_empty_source = "non-empty-source.tar.xz"
72+
existing_source = "existing-source.tar.gz"
73+
sourcedir = tmp_path / "sources"
74+
sourcedir.mkdir()
75+
(sourcedir / existing_source).write_text("...")
76+
parser = SpecParser(sourcedir)
77+
with parser._make_dummy_sources(
78+
{regular_source, existing_source}, {non_empty_source}
79+
) as dummy_sources:
80+
assert all(s.exists() for s in dummy_sources)
81+
assert sourcedir / regular_source in dummy_sources
82+
assert sourcedir / non_empty_source in dummy_sources
83+
assert sourcedir / existing_source not in dummy_sources
84+
assert all(not s.exists() for s in dummy_sources)
85+
assert (sourcedir / existing_source).exists()
86+
read_only_sourcedir = tmp_path / "read-only-sources"
87+
read_only_sourcedir.mkdir()
88+
(read_only_sourcedir / existing_source).write_text("...")
89+
read_only_sourcedir.chmod(0o555)
90+
parser = SpecParser(read_only_sourcedir)
91+
with parser._make_dummy_sources(
92+
{regular_source, existing_source}, {non_empty_source}
93+
) as dummy_sources:
94+
assert not dummy_sources
95+
assert (read_only_sourcedir / existing_source).exists()

0 commit comments

Comments
 (0)