Skip to content

Commit 4f5d55a

Browse files
authored
Merge pull request #1243 from cgwalters/cap-std-ext-walkdir
Update to use walk from cap-std-ext
2 parents 7b7eff6 + efb6461 commit 4f5d55a

File tree

6 files changed

+81
-60
lines changed

6 files changed

+81
-60
lines changed

Cargo.lock

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/src/lints.rs

+38-23
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ use std::collections::BTreeSet;
99
use std::env::consts::ARCH;
1010
use std::fmt::Write as WriteFmt;
1111
use std::os::unix::ffi::OsStrExt;
12+
use std::path::Path;
1213

1314
use anyhow::Result;
1415
use bootc_utils::PathQuotedDisplay;
1516
use camino::{Utf8Path, Utf8PathBuf};
1617
use cap_std::fs::Dir;
1718
use cap_std_ext::cap_std;
1819
use cap_std_ext::cap_std::fs::MetadataExt;
19-
use cap_std_ext::dirext::CapStdExtDirExt as _;
20+
use cap_std_ext::dirext::{CapStdExtDirExt as _, WalkConfiguration};
2021
use fn_error_context::context;
2122
use indoc::indoc;
2223
use linkme::distributed_slice;
@@ -338,30 +339,44 @@ UTF-8 filenames. Non-UTF8 filenames will cause a fatal error.
338339
check_utf8,
339340
);
340341
fn check_utf8(dir: &Dir) -> LintResult {
341-
for entry in dir.entries()? {
342-
let entry = entry?;
343-
let name = entry.file_name();
344-
345-
let Some(strname) = &name.to_str() else {
346-
// will escape nicely like "abc\xFFdéf"
347-
return lint_err(format!("/: Found non-utf8 filename {name:?}"));
348-
};
349-
350-
let ifmt = entry.file_type()?;
351-
if ifmt.is_symlink() {
352-
let target = dir.read_link_contents(&name)?;
353-
if !target.to_str().is_some() {
354-
return lint_err(format!("/{strname}: Found non-utf8 symlink target"));
342+
let mut err = None;
343+
dir.walk(
344+
&WalkConfiguration::default()
345+
.noxdev()
346+
.path_base(Path::new("/")),
347+
|e| -> std::io::Result<_> {
348+
// Right now we stop iteration on the first non-UTF8 filename found.
349+
// However in the future it'd make sense to handle multiple.
350+
if err.is_some() {
351+
return Ok(std::ops::ControlFlow::Break(()));
355352
}
356-
} else if ifmt.is_dir() {
357-
let Some(subdir) = dir.open_dir_noxdev(entry.file_name())? else {
358-
continue;
353+
let path = e.path;
354+
let filename = e.filename;
355+
let dirname = path.parent().unwrap_or(Path::new("/"));
356+
if filename.to_str().is_none() {
357+
// This escapes like "abc\xFFdéf"
358+
err = Some(format!(
359+
"{}: Found non-utf8 filename {filename:?}",
360+
PathQuotedDisplay::new(&dirname)
361+
));
362+
return Ok(std::ops::ControlFlow::Break(()));
359363
};
360-
if let Err(err) = check_utf8(&subdir)? {
361-
// Try to do the path pasting only in the event of an error
362-
return lint_err(format!("/{strname}{err}"));
364+
365+
if e.file_type.is_symlink() {
366+
let target = e.dir.read_link_contents(filename)?;
367+
if !target.to_str().is_some() {
368+
err = Some(format!(
369+
"{}: Found non-utf8 symlink target",
370+
PathQuotedDisplay::new(&path)
371+
));
372+
return Ok(std::ops::ControlFlow::Break(()));
373+
}
363374
}
364-
}
375+
Ok(std::ops::ControlFlow::Continue(()))
376+
},
377+
)?;
378+
if let Some(err) = err {
379+
return lint_err(err);
365380
}
366381
lint_ok()
367382
}
@@ -875,7 +890,7 @@ mod tests {
875890
};
876891
assert_eq!(
877892
err.to_string(),
878-
r#"/subdir/2/: Found non-utf8 filename "bad\xFFdir""#
893+
r#"/subdir/2: Found non-utf8 filename "bad\xFFdir""#
879894
);
880895
root.remove_dir(baddir).unwrap(); // Get rid of the problem
881896
check_utf8(root).unwrap().unwrap(); // Check it

tests-integration/src/install.rs

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
use std::{
2-
os::fd::AsRawFd,
3-
path::{Path, PathBuf},
4-
};
1+
use std::os::fd::AsRawFd;
2+
use std::path::Path;
53

64
use anyhow::Result;
75
use camino::Utf8Path;
@@ -147,8 +145,7 @@ pub(crate) fn run_alongside(image: &str, mut testargs: libtest_mimic::Arguments)
147145
cmd!(sh, "sudo {BASE_ARGS...} {image} bootc install to-existing-root --acknowledge-destructive {generic_inst_args...}").run()?;
148146
generic_post_install_verification()?;
149147
let root = &Dir::open_ambient_dir("/ostree", cap_std::ambient_authority()).unwrap();
150-
let mut path = PathBuf::from(".");
151-
crate::selinux::verify_selinux_recurse(root, &mut path, false)?;
148+
crate::selinux::verify_selinux_recurse(root, false)?;
152149
Ok(())
153150
}),
154151
Trial::test("Install to non-default stateroot", move || {

tests-integration/src/selinux.rs

+24-21
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,38 @@
1+
use std::ffi::OsStr;
2+
use std::ops::ControlFlow;
13
use std::os::fd::AsRawFd;
2-
use std::path::{Path, PathBuf};
4+
use std::path::PathBuf;
35

46
use anyhow::{Context, Result};
57
use cap_std_ext::cap_std::fs::Dir;
8+
use cap_std_ext::dirext::{CapStdExtDirExt, WalkConfiguration};
9+
use rustix::path::DecInt;
610

7-
fn verify_selinux_label_exists(root: &Dir, path: &Path, warn: bool) -> Result<()> {
11+
fn verify_selinux_label_exists(d: &Dir, filename: &OsStr) -> Result<bool> {
812
let mut buf = [0u8; 1024];
9-
let fdpath = format!("/proc/self/fd/{}/", root.as_raw_fd());
10-
let fdpath = &Path::new(&fdpath).join(path);
13+
let mut fdpath = PathBuf::from("/proc/self/fd");
14+
fdpath.push(DecInt::new(d.as_raw_fd()));
15+
fdpath.push(filename);
1116
match rustix::fs::lgetxattr(fdpath, "security.selinux", &mut buf) {
1217
// Ignore EOPNOTSUPPORTED
13-
Ok(_) | Err(rustix::io::Errno::OPNOTSUPP) => Ok(()),
14-
Err(rustix::io::Errno::NODATA) if warn => {
15-
eprintln!("No SELinux label found for: {path:?}");
16-
Ok(())
17-
}
18-
Err(e) => Err(e).with_context(|| format!("Failed to look up context for {path:?}")),
18+
Ok(_) | Err(rustix::io::Errno::OPNOTSUPP) => Ok(true),
19+
Err(rustix::io::Errno::NODATA) => Ok(false),
20+
Err(e) => Err(e.into()),
1921
}
2022
}
2123

22-
pub(crate) fn verify_selinux_recurse(root: &Dir, path: &mut PathBuf, warn: bool) -> Result<()> {
23-
for ent in root.read_dir(&path)? {
24-
let ent = ent?;
25-
let name = ent.file_name();
26-
path.push(name);
27-
verify_selinux_label_exists(root, &path, warn)?;
28-
let file_type = ent.file_type()?;
29-
if file_type.is_dir() {
30-
verify_selinux_recurse(root, path, warn)?;
24+
pub(crate) fn verify_selinux_recurse(root: &Dir, warn: bool) -> Result<()> {
25+
root.walk(&WalkConfiguration::default().noxdev(), |e| {
26+
let exists = verify_selinux_label_exists(e.dir, e.filename)
27+
.with_context(|| format!("Failed to look up context for {:?}", e.path))?;
28+
if !exists {
29+
if warn {
30+
eprintln!("No SELinux label found for: {:?}", e.path);
31+
} else {
32+
anyhow::bail!("No SELinux label found for: {:?}", e.path);
33+
}
3134
}
32-
path.pop();
33-
}
35+
anyhow::Ok(ControlFlow::Continue(()))
36+
})?;
3437
Ok(())
3538
}

tests-integration/src/tests-integration.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
//! Integration tests.
22
3-
use std::path::PathBuf;
4-
53
use camino::Utf8PathBuf;
64
use cap_std_ext::cap_std::{self, fs::Dir};
75
use clap::Parser;
@@ -53,8 +51,7 @@ fn main() {
5351
Opt::RunVM(opts) => runvm::run(opts),
5452
Opt::VerifySELinux { rootfs, warn } => {
5553
let root = &Dir::open_ambient_dir(&rootfs, cap_std::ambient_authority()).unwrap();
56-
let mut path = PathBuf::from(".");
57-
selinux::verify_selinux_recurse(root, &mut path, warn)
54+
selinux::verify_selinux_recurse(root, warn)
5855
}
5956
};
6057
if let Err(e) = r {

utils/src/path.rs

+12-3
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ impl<'a> Display for PathQuotedDisplay<'a> {
1818
}
1919
}
2020
if let Ok(r) = shlex::bytes::try_quote(self.path.as_os_str().as_bytes()) {
21-
if let Ok(s) = std::str::from_utf8(&r) {
22-
return f.write_str(s);
23-
}
21+
let s = String::from_utf8_lossy(&r);
22+
return f.write_str(&s);
2423
}
2524
// Should not happen really
2625
return Err(std::fmt::Error);
@@ -40,6 +39,8 @@ impl<'a> PathQuotedDisplay<'a> {
4039

4140
#[cfg(test)]
4241
mod tests {
42+
use std::ffi::OsStr;
43+
4344
use super::*;
4445

4546
#[test]
@@ -61,4 +62,12 @@ mod tests {
6162
assert_eq!(quoted, format!("{}", PathQuotedDisplay::new(&v)));
6263
}
6364
}
65+
66+
#[test]
67+
fn test_nonutf8() {
68+
let p = Path::new(OsStr::from_bytes(b"/foo/somenonutf8\xEE/bar"));
69+
assert!(p.to_str().is_none());
70+
let q = PathQuotedDisplay::new(&p).to_string();
71+
assert_eq!(q, r#"'/foo/somenonutf8�/bar'"#);
72+
}
6473
}

0 commit comments

Comments
 (0)