Skip to content

Commit 1ff6af9

Browse files
authored
Merge pull request science-computing#370 from primeos-work/fix-pkg-patches-handling-and-example-repo
Fix the handling of package patches and building the included example repo
2 parents 1b2ec43 + 97f0cee commit 1ff6af9

File tree

33 files changed

+113
-15
lines changed

33 files changed

+113
-15
lines changed

doc/containers.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ There are some conventions regarding packages, dependencies, sources and so
1414
on. Those are listed here.
1515

1616
1. Dependencies are named `/inputs/<packagename>-<packageversion>.pkg` inside the container
17-
2. Sources are named `/inputs/src-<hashsum>.source`
17+
2. Sources are named `/inputs/src.source`
1818
3. Outputs are expected to be written to the `/outputs` directory
1919

2020
The reason for the names lies in the artifact parsing mechanism.
2121
If the package is named differently, the artifact parsing mechanism is not able
2222
to recognize the package and might fault, which causes butido to stop running.
23-

examples/packages/repo/j/pkg.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name = "j"
22
version = "10"
33

44
[dependencies]
5-
runtime = ["s =19.0", "t =20"]
5+
runtime = ["s =19.0", "s =19.1", "s =19.2", "s =19.3", "t =20"]
66

77
[sources.src]
88
url = "https://example.com"

examples/packages/repo/pkg.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ download_manually = false
1212
[phases]
1313

1414
sourcecheck.script = '''
15-
filename="/inputs/src-{{this.sources.src.hash.hash}}.source"
15+
filename="/inputs/src.source"
1616
[[ -e $filename ]] || {
1717
echo "MISSING: $filename"
1818
{{state "ERR" "Missing input"}}
@@ -50,4 +50,3 @@ build.script = '''
5050
5151
{{state "OK"}}
5252
'''
53-
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
19

src/repository/repository.rs

Lines changed: 109 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
//
1010

1111
use std::collections::BTreeMap;
12+
use std::path::Component;
1213
use std::path::Path;
1314
use std::path::PathBuf;
1415

@@ -38,6 +39,62 @@ impl From<BTreeMap<(PackageName, PackageVersion), Package>> for Repository {
3839
}
3940
}
4041

42+
// A helper function to normalize relative Unix paths (ensures that one cannot escape using `..`):
43+
fn normalize_relative_path(path: PathBuf) -> Result<PathBuf> {
44+
let mut normalized_path = PathBuf::new();
45+
for component in path.components() {
46+
match component {
47+
Component::Prefix(_) => {
48+
// "A Windows path prefix, e.g., C: or \\server\share."
49+
// "Does not occur on Unix."
50+
return Err(anyhow!(
51+
"The relative path \"{}\" starts with a Windows path prefix",
52+
path.display()
53+
));
54+
}
55+
Component::RootDir => {
56+
// "The root directory component, appears after any prefix and before anything else.
57+
// It represents a separator that designates that a path starts from root."
58+
return Err(anyhow!(
59+
"The relative path \"{}\" starts from the root directory",
60+
path.display()
61+
));
62+
}
63+
Component::CurDir => {
64+
// "A reference to the current directory, i.e., `.`."
65+
// Also (from `Path.components()`): "Occurrences of . are normalized away, except
66+
// if they are at the beginning of the path. For example, a/./b, a/b/, a/b/. and
67+
// a/b all have a and b as components, but ./a/b starts with an additional CurDir
68+
// component."
69+
// -> May only occur as the first path component and we can ignore it / normalize
70+
// it away (we should just ensure that it's not the only path component in which
71+
// case the path would be empty).
72+
}
73+
Component::ParentDir => {
74+
// "A reference to the parent directory, i.e., `..`."
75+
if !normalized_path.pop() {
76+
return Err(anyhow!(
77+
"The relative path \"{}\" uses `..` to escape the base directory",
78+
path.display()
79+
));
80+
}
81+
}
82+
Component::Normal(component) => {
83+
// "A normal component, e.g., a and b in a/b. This variant is the most common one,
84+
// it represents references to files or directories."
85+
normalized_path.push(component);
86+
}
87+
}
88+
}
89+
90+
if normalized_path.parent().is_none() {
91+
// Optional: Convert "" to ".":
92+
normalized_path.push(Component::CurDir);
93+
}
94+
95+
Ok(normalized_path)
96+
}
97+
4198
impl Repository {
4299
fn new(inner: BTreeMap<(PackageName, PackageVersion), Package>) -> Self {
43100
Repository { inner }
@@ -76,6 +133,7 @@ impl Repository {
76133
}
77134
}
78135

136+
let cwd = std::env::current_dir()?;
79137
let leaf_files = fsr
80138
.files()
81139
.par_iter()
@@ -113,9 +171,27 @@ impl Repository {
113171
// `path` is only available in this "iteration".
114172
patches_after_merge
115173
.into_iter()
116-
// Prepend the path of the directory of the `pkg.toml` file to the name of the patch:
117174
.map(|p| if let Some(current_dir) = path.parent() {
118-
Ok(current_dir.join(p))
175+
// Prepend the path of the directory of the `pkg.toml` file to
176+
// the name of the patch file:
177+
let mut path = current_dir.join(p);
178+
// Ensure that we use relative paths for the patches (the rest
179+
// of the code that uses the patches doesn't work correctly
180+
// with absolute paths):
181+
if path.is_absolute() {
182+
// We assume that cwd is part of the prefix (currently, the
183+
// path comes from `git2::Repository::workdir()` and should
184+
// always be absolute and start from cwd so this is fine):
185+
path = path
186+
.strip_prefix(&cwd)
187+
.map(|p| p.to_owned())
188+
.with_context(|| anyhow!("Cannot strip the prefix {} form path {}", cwd.display(), current_dir.display()))?;
189+
}
190+
if path.is_absolute() {
191+
Err(anyhow!("The path {} is absolute but it should be a relative path.", path.display()))
192+
} else {
193+
normalize_relative_path(path)
194+
}
119195
} else {
120196
Err(anyhow!("Path should point to path with parent, but doesn't: {}", path.display()))
121197
})
@@ -125,11 +201,11 @@ impl Repository {
125201
.and_then_ok(|patch| if patch.exists() {
126202
Ok(Some(patch))
127203
} else {
128-
Err(anyhow!("Patch does not exist: {}", patch.display()))
129-
.with_context(|| anyhow!("The patch is declared here: {}", path.display()))
204+
Err(anyhow!("The following patch does not exist: {}", patch.display()))
130205
})
131206
.filter_map_ok(|o| o)
132-
.collect::<Result<Vec<_>>>()?
207+
.collect::<Result<Vec<_>>>()
208+
.with_context(|| anyhow!("Could not process the patches declared here: {}", path.display()))?
133209
};
134210

135211
trace!("Patches after postprocessing merge: {:?}", patches);
@@ -319,19 +395,19 @@ pub mod tests {
319395
assert_pkg(&repo, "s", "19.1");
320396
assert_pkg(&repo, "z", "26");
321397

322-
// Verify the paths of the patches (and "merging"):
398+
// Verify the paths of the patches (and the base directory "merging"/joining logic plus the
399+
// normalization of relative paths):
323400
// The patches are defined as follows:
324401
// s/pkg.toml: patches = [ "./foo.patch" ]
325402
// s/19.0/pkg.toml: patches = ["./foo.patch","s190.patch"]
326403
// s/19.1/pkg.toml: - (no `patches` entry)
327404
// s/19.2/pkg.toml: patches = ["../foo.patch"]
328405
// s/19.3/pkg.toml: patches = ["s190.patch"]
329406
let p = get_pkg(&repo, "s", "19.0");
330-
// Ideally we'd normalize the `./` away:
331407
assert_eq!(
332408
p.patches(),
333409
&vec![
334-
PathBuf::from("examples/packages/repo/s/19.0/./foo.patch"),
410+
PathBuf::from("examples/packages/repo/s/19.0/foo.patch"),
335411
PathBuf::from("examples/packages/repo/s/19.0/s190.patch")
336412
]
337413
);
@@ -341,10 +417,9 @@ pub mod tests {
341417
&vec![PathBuf::from("examples/packages/repo/s/foo.patch")]
342418
);
343419
let p = get_pkg(&repo, "s", "19.2");
344-
// We might want to normalize the `19.2/../` away:
345420
assert_eq!(
346421
p.patches(),
347-
&vec![PathBuf::from("examples/packages/repo/s/19.2/../foo.patch")]
422+
&vec![PathBuf::from("examples/packages/repo/s/foo.patch")]
348423
);
349424
let p = get_pkg(&repo, "s", "19.3");
350425
assert_eq!(
@@ -354,4 +429,28 @@ pub mod tests {
354429

355430
Ok(())
356431
}
432+
433+
#[test]
434+
fn test_relative_path_normalization() -> Result<()> {
435+
assert!(normalize_relative_path(PathBuf::from("/root")).is_err());
436+
assert!(normalize_relative_path(PathBuf::from("a/../../root")).is_err());
437+
assert_eq!(
438+
normalize_relative_path(PathBuf::from(""))?,
439+
PathBuf::from(".")
440+
);
441+
assert_eq!(
442+
normalize_relative_path(PathBuf::from("."))?,
443+
PathBuf::from(".")
444+
);
445+
assert_eq!(
446+
normalize_relative_path(PathBuf::from("./a//b/../b/./c/."))?,
447+
PathBuf::from("a/b/c")
448+
);
449+
assert_eq!(
450+
normalize_relative_path(PathBuf::from("./a//../b/"))?,
451+
PathBuf::from("b")
452+
);
453+
454+
Ok(())
455+
}
357456
}

0 commit comments

Comments
 (0)