Skip to content

Commit 43847b2

Browse files
committed
use openat when encountering ENAMETOOLONG
This adds a fallback to various fs methods that handles ENAMETOOLONG by splitting the path into relative segments and then opening each segment with openat(dirfd, segment, O_PATH). Limitations: * not all fs methods are covered. the primary motivation is to get remove_dir_all working to delete a deep directory structure created by accident * requires O_PATH, so this can currently only be a fallback and not the default due to our current minimum kernel version. If you're not dealing with long paths it won't be used. * currently linux-only. this could be extended to platforms which have either O_PATH or O_EXEC but there's no CI coverage for the BSDs so I don't want to foist it on them * O(n²) performance if remove_dir_all has to use the fallback, albeit a small constant factor due to the long path segments used but ideally it should be rewritten to use openat in its recursion steps. But to do it properly we need a higher minimum kernel version.
1 parent 1e836d1 commit 43847b2

File tree

6 files changed

+267
-75
lines changed

6 files changed

+267
-75
lines changed

library/std/src/fs/tests.rs

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1658,36 +1658,36 @@ fn test_file_times() {
16581658
let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(54321);
16591659
times = times.set_accessed(accessed).set_modified(modified);
16601660
#[cfg(any(
1661-
windows,
1662-
target_os = "macos",
1663-
target_os = "ios",
1664-
target_os = "watchos",
1665-
target_os = "tvos",
1661+
windows,
1662+
target_os = "macos",
1663+
target_os = "ios",
1664+
target_os = "watchos",
1665+
target_os = "tvos",
16661666
))]
1667-
let created = SystemTime::UNIX_EPOCH + Duration::from_secs(32123);
1667+
let created = SystemTime::UNIX_EPOCH + Duration::from_secs(32123);
16681668
#[cfg(any(
1669-
windows,
1670-
target_os = "macos",
1671-
target_os = "ios",
1672-
target_os = "watchos",
1673-
target_os = "tvos",
1669+
windows,
1670+
target_os = "macos",
1671+
target_os = "ios",
1672+
target_os = "watchos",
1673+
target_os = "tvos",
16741674
))]
16751675
{
16761676
times = times.set_created(created);
16771677
}
16781678
match file.set_times(times) {
16791679
// Allow unsupported errors on platforms which don't support setting times.
16801680
#[cfg(not(any(
1681-
windows,
1682-
all(
1683-
unix,
1684-
not(any(
1685-
target_os = "android",
1686-
target_os = "redox",
1687-
target_os = "espidf",
1688-
target_os = "horizon"
1689-
))
1690-
)
1681+
windows,
1682+
all(
1683+
unix,
1684+
not(any(
1685+
target_os = "android",
1686+
target_os = "redox",
1687+
target_os = "espidf",
1688+
target_os = "horizon"
1689+
))
1690+
)
16911691
)))]
16921692
Err(e) if e.kind() == ErrorKind::Unsupported => return,
16931693
Err(e) => panic!("error setting file times: {e:?}"),
@@ -1697,13 +1697,36 @@ fn test_file_times() {
16971697
assert_eq!(metadata.accessed().unwrap(), accessed);
16981698
assert_eq!(metadata.modified().unwrap(), modified);
16991699
#[cfg(any(
1700-
windows,
1701-
target_os = "macos",
1702-
target_os = "ios",
1703-
target_os = "watchos",
1704-
target_os = "tvos",
1700+
windows,
1701+
target_os = "macos",
1702+
target_os = "ios",
1703+
target_os = "watchos",
1704+
target_os = "tvos",
17051705
))]
17061706
{
17071707
assert_eq!(metadata.created().unwrap(), created);
17081708
}
17091709
}
1710+
1711+
#[test]
1712+
#[cfg(any(target_os = "linux", target_os = "android"))]
1713+
fn deep_traversal() -> crate::io::Result<()> {
1714+
use crate::fs::{create_dir_all, metadata, remove_dir_all, write};
1715+
use crate::iter::repeat;
1716+
1717+
let tmpdir = tmpdir();
1718+
1719+
let segment = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
1720+
1721+
let mut dir = tmpdir.join(segment);
1722+
repeat(segment).take(100).for_each(|name| dir.push(name));
1723+
assert!(dir.as_os_str().len() > libc::PATH_MAX as usize);
1724+
let file = dir.join("b");
1725+
1726+
create_dir_all(&dir).expect("deep create tailed");
1727+
write(&file, "foo").expect("deep write failed");
1728+
metadata(&file).expect("deep stat failed");
1729+
remove_dir_all(&dir).expect("deep remove failed");
1730+
1731+
Ok(())
1732+
}

library/std/src/sys/common/small_c_string.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use crate::ffi::{CStr, CString};
1+
use crate::ffi::{CStr, CString, OsStr};
22
use crate::mem::MaybeUninit;
3-
use crate::path::Path;
43
use crate::slice;
54
use crate::{io, ptr};
65

@@ -15,11 +14,11 @@ const NUL_ERR: io::Error =
1514
io::const_io_error!(io::ErrorKind::InvalidInput, "file name contained an unexpected NUL byte");
1615

1716
#[inline]
18-
pub fn run_path_with_cstr<T, F>(path: &Path, f: F) -> io::Result<T>
17+
pub fn run_path_with_cstr<T, F>(path: &(impl AsRef<OsStr> + ?Sized), f: F) -> io::Result<T>
1918
where
2019
F: FnOnce(&CStr) -> io::Result<T>,
2120
{
22-
run_with_cstr(path.as_os_str().as_os_str_bytes(), f)
21+
run_with_cstr(path.as_ref().as_os_str_bytes(), f)
2322
}
2423

2524
#[inline]

0 commit comments

Comments
 (0)