diff --git a/problemtools/verifyproblem.py b/problemtools/verifyproblem.py index 65d39fb..e87f556 100644 --- a/problemtools/verifyproblem.py +++ b/problemtools/verifyproblem.py @@ -1851,6 +1851,8 @@ def check(self, args: argparse.Namespace) -> tuple[int, int]: if not re.match('^[a-z0-9]+$', self.shortname): self.error(f"Invalid shortname '{self.shortname}' (must be [a-z0-9]+)") + self._check_symlinks() + run.limit.check_limit_capabilities(self) for part in args.parts: @@ -1861,6 +1863,30 @@ def check(self, args: argparse.Namespace) -> tuple[int, int]: pass return ProblemAspect.errors, ProblemAspect.warnings + def _check_symlinks(self): + """Check that all symlinks point to something existing within the problem package""" + probdir = os.path.realpath(self.probdir) + for root, dirs, files in os.walk(probdir): + for file in dirs + files: + filename = os.path.join(root, file) + if os.path.islink(filename): + target = os.path.realpath(filename) + # relfile is the filename of the symlink, relative to the problem root (only used for nicer error messages) + relfile = os.path.relpath(filename, self.probdir) + # reltarget is what the symlink points to (absolute, or relative to where the symlink is) + reltarget = os.readlink(filename) + if not os.path.exists(target): + self.error( + f"Symlink {relfile} links to {reltarget} which does not exist" + ) + if os.path.commonpath([probdir, target]) != probdir: + self.error( + f"Symlink {relfile} links to {reltarget} which is outside of problem package" + ) + if os.path.isabs(reltarget): + self.error( + f"Symlink {relfile} links to {reltarget} which is an absolute path. Symlinks must be relative." + ) def re_argument(s: str) -> Pattern[str]: try: