Skip to content

Commit 3be93cf

Browse files
authored
Merge pull request #195 from desultory/dev
resolve source symlinks too
2 parents b43c555 + ffba25a commit 3be93cf

File tree

3 files changed

+59
-24
lines changed

3 files changed

+59
-24
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ classifiers = [
2020

2121
dependencies = [
2222
"zenlib >= 3.0.2",
23-
"pycpio >= 1.4.0"
23+
"pycpio >= 1.4.2"
2424
]
2525

2626
[options]

src/ugrd/base/core.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__author__ = "desultory"
2-
__version__ = "4.0.1"
2+
__version__ = "4.1.0"
33

44
from pathlib import Path
55
from shutil import rmtree, which
@@ -112,17 +112,22 @@ def calculate_dependencies(self, binary: str) -> list[Path]:
112112

113113
@contains("merge_usr", "Skipping /usr merge", log_level=30)
114114
def handle_usr_symlinks(self) -> None:
115-
"""Adds symlinks for /usr/bin and /usr/sbin to /bin and /sbin."""
115+
"""Adds symlinks for /usr/bin and /usr/sbin to /bin and /sbin.
116+
Warns if the symlink path is a directory on the host system.
117+
"""
116118
build_dir = self._get_build_path("/")
117-
bin_dir = build_dir / "bin"
118-
sbin_dir = build_dir / "sbin"
119-
usr_sbin_dir = build_dir / "usr" / "sbin"
119+
bin_dir = Path("bin")
120+
sbin_dir = Path("sbin")
121+
usr_sbin_dir = Path("usr/sbin")
120122

121123
for d in [bin_dir, sbin_dir, usr_sbin_dir]:
122-
if not d.is_dir() and not d.is_symlink():
123-
target = d.relative_to(build_dir)
124-
self.logger.debug("Creating merged-usr symlink to /usr/bin: %s" % target)
125-
self._symlink("/usr/bin", target)
124+
if d.is_dir() and not d.is_symlink():
125+
self.logger.warning("Merged-usr symlink target is a directory: %s" % d)
126+
self.logger.warning("Set `merge_usr = false` to disable /usr merge.")
127+
build_d = build_dir / d
128+
if not build_d.is_dir() and not build_d.is_symlink():
129+
self.logger.log(5, "Creating merged-usr symlink to /usr/bin: %s" % build_d)
130+
self._symlink("/usr/bin", d)
126131

127132

128133
def deploy_dependencies(self) -> None:

src/ugrd/generator_helpers.py

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from zenlib.util import pretty_print, colorize
66

7-
__version__ = "1.5.0"
7+
__version__ = "1.5.3"
88
__author__ = "desultory"
99

1010

@@ -39,9 +39,6 @@ def _mkdir(self, path: Path, resolve_build=True) -> None:
3939
If resolve_build is True, the path is resolved to the build directory.
4040
If not, the provided path is used as-is.
4141
"""
42-
from os import mkdir
43-
from os.path import isdir
44-
4542
if resolve_build:
4643
path = self._get_build_path(path)
4744

@@ -52,12 +49,15 @@ def _mkdir(self, path: Path, resolve_build=True) -> None:
5249
else:
5350
path_dir = path
5451

55-
if not isdir(path_dir.parent):
52+
if path_dir.is_symlink():
53+
return self.logger.debug("Skipping symlink directory: %s" % path_dir)
54+
55+
if not path_dir.parent.is_dir():
5656
self.logger.debug("Parent directory does not exist: %s" % path_dir.parent)
5757
self._mkdir(path_dir.parent, resolve_build=False)
5858

59-
if not isdir(path_dir):
60-
mkdir(path)
59+
if not path_dir.is_dir():
60+
path_dir.mkdir()
6161
self.logger.log(self["_build_log_level"], "Created directory: %s" % path)
6262
else:
6363
self.logger.debug("Directory already exists: %s" % path_dir)
@@ -100,7 +100,14 @@ def _write(self, file_name: Union[Path, str], contents: list[str], chmod_mask=0o
100100
self.logger.debug("[%s] Set file permissions: %s" % (file_path, chmod_mask))
101101

102102
def _copy(self, source: Union[Path, str], dest=None) -> None:
103-
"""Copies a file into the initramfs build directory."""
103+
"""Copies a file into the initramfs build directory.
104+
If a destination is not provided, the source is used, under the build directory.
105+
106+
If the destination parent is a symlink, the symlink is resolved.
107+
Crates parent directories if they do not exist
108+
109+
Raises a RuntimeError if the destination path is not within the build directory.
110+
"""
104111
from shutil import copy2
105112

106113
if not isinstance(source, Path):
@@ -111,10 +118,12 @@ def _copy(self, source: Union[Path, str], dest=None) -> None:
111118
dest = source
112119

113120
dest_path = self._get_build_path(dest)
121+
build_base = self._get_build_path("/")
114122

115123
while dest_path.parent.is_symlink():
116-
self.logger.debug("Resolving symlink: %s" % dest_path.parent)
117-
dest_path = self._get_build_path(dest_path.parent.resolve() / dest_path.name)
124+
resolved_path = dest_path.parent.resolve() / dest_path.name
125+
self.logger.debug("Resolved symlink: %s -> %s" % (dest_path.parent, resolved_path))
126+
dest_path = self._get_build_path(resolved_path)
118127

119128
if not dest_path.parent.is_dir():
120129
self.logger.debug("Parent directory for '%s' does not exist: %s" % (dest_path.name, dest_path.parent))
@@ -126,23 +135,41 @@ def _copy(self, source: Union[Path, str], dest=None) -> None:
126135
self.logger.debug("Destination is a directory, adding source filename: %s" % source.name)
127136
dest_path = dest_path / source.name
128137

138+
try: # Ensure the target is in the build directory
139+
dest_path.relative_to(build_base)
140+
except ValueError as e:
141+
raise RuntimeError("Destination path is not within the build directory: %s" % dest_path) from e
142+
129143
self.logger.log(self["_build_log_level"], "Copying '%s' to '%s'" % (source, dest_path))
130144
copy2(source, dest_path)
131145

132146
def _symlink(self, source: Union[Path, str], target: Union[Path, str]) -> None:
133-
"""Creates a symlink"""
147+
"""Creates a symlink in the build directory.
148+
If the target is a directory, the source filename is appended to the target path.
149+
150+
Creates parent directories if they do not exist.
151+
If the symlink path is under a symlink, resolve to the actual path.
152+
153+
If the symlink source is under a symlink in the build directory, resolve to the actual path.
154+
"""
134155
if not isinstance(source, Path):
135156
source = Path(source)
136157

137158
target = self._get_build_path(target)
138159

160+
while target.parent.is_symlink():
161+
self.logger.debug("Resolving target parent symlink: %s" % target.parent)
162+
target = self._get_build_path(target.parent.resolve() / target.name)
163+
139164
if not target.parent.is_dir():
140165
self.logger.debug("Parent directory for '%s' does not exist: %s" % (target.name, target.parent))
141166
self._mkdir(target.parent, resolve_build=False)
142167

143-
while target.parent.is_symlink():
144-
self.logger.debug("Resolving symlink: %s" % target.parent)
145-
target = self._get_build_path(target.parent.resolve() / target.name)
168+
build_source = self._get_build_path(source)
169+
while build_source.parent.is_symlink():
170+
self.logger.debug("Resolving source parent symlink: %s" % build_source.parent)
171+
build_source = self._get_build_path(build_source.parent.resolve() / build_source.name)
172+
source = build_source.relative_to(self._get_build_path("/"))
146173

147174
if target.is_symlink():
148175
if target.resolve() == source:
@@ -153,6 +180,9 @@ def _symlink(self, source: Union[Path, str], target: Union[Path, str]) -> None:
153180
else:
154181
raise RuntimeError("Symlink already exists: %s -> %s" % (target, target.resolve()))
155182

183+
if target.relative_to(self._get_build_path("/")) == source:
184+
return self.logger.debug("Cannot symlink to self: %s -> %s" % (target, source))
185+
156186
self.logger.debug("Creating symlink: %s -> %s" % (target, source))
157187
target.symlink_to(source)
158188

0 commit comments

Comments
 (0)