aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-shim/src/fs.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-09 18:33:14 +0900
committernsfisis <nsfisis@gmail.com>2026-08-09 18:33:14 +0900
commit72cbecaec29cecc723ce29c87d10048114aeef5b (patch)
tree99ee7288595afbf1166503d603d7c5a7d08aaae0 /crates/shirabe-php-shim/src/fs.rs
parentf1215ade6e1d6e91a36c8b1c407f5a59a1c2ee0e (diff)
downloadphp-shirabe-72cbecaec29cecc723ce29c87d10048114aeef5b.tar.gz
php-shirabe-72cbecaec29cecc723ce29c87d10048114aeef5b.tar.zst
php-shirabe-72cbecaec29cecc723ce29c87d10048114aeef5b.zip
feat(symfony-filesystem): finish the Filesystem port and trim its API
doRemove() renames a directory to a random hidden name before emptying it, and undoes that rename when the final rmdir fails, so a concurrent process cannot recreate the path mid-removal. It also walks one level at a time through FilesystemIterator instead of flattening the whole tree, and lets an inner rmdir failure pass, both as upstream does. copy() keeps the mode fopen($targetFile, 'w') would have left rather than the origin's, symlink() and mirror() call readlink() and getLinkTarget() where upstream does, and a directory iterator that cannot be opened propagates its UnexpectedValueException instead of being swallowed or flattened into an IOException. Error message text is out of scope per docs/known-incompatibilities.md, so the three TODO(phase-c) markers that only tracked wording are gone, along with linkException()'s Windows-only branch. So are the arguments no caller varies -- symlink()'s copyOnWindows, mirror()'s iterator and options, copy()'s overwriteNewerFiles -- which removes the last TODO(phase-c) in the file. New shim functions: readlink, filesystem_iterator, stream_is_local, strrev and SplFileInfo::getLinkTarget. base64_encode takes bytes so random_bytes() can feed it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-shim/src/fs.rs')
-rw-r--r--crates/shirabe-php-shim/src/fs.rs43
1 files changed, 43 insertions, 0 deletions
diff --git a/crates/shirabe-php-shim/src/fs.rs b/crates/shirabe-php-shim/src/fs.rs
index bd5e650a..2142b0e7 100644
--- a/crates/shirabe-php-shim/src/fs.rs
+++ b/crates/shirabe-php-shim/src/fs.rs
@@ -43,6 +43,35 @@ pub struct FilesystemIterator;
impl FilesystemIterator {
pub const KEY_AS_PATHNAME: i64 = 256;
pub const CURRENT_AS_FILEINFO: i64 = 0;
+ pub const CURRENT_AS_PATHNAME: i64 = 32;
+ pub const SKIP_DOTS: i64 = 4096;
+}
+
+/// PHP `new \FilesystemIterator($path, $flags)` flattened to the entries it yields: the direct
+/// children of `path`, in readdir order, without descending into subdirectories.
+pub fn filesystem_iterator(
+ path: impl AsRef<std::path::Path>,
+ flags: i64,
+) -> Result<Vec<String>, UnexpectedValueException> {
+ assert!(
+ flags & FilesystemIterator::CURRENT_AS_PATHNAME != 0,
+ "filesystem_iterator yields pathnames, so CURRENT_AS_PATHNAME must be set"
+ );
+ assert!(
+ flags & FilesystemIterator::SKIP_DOTS != 0,
+ "filesystem_iterator does not model the \".\" and \"..\" entries, so SKIP_DOTS must be set"
+ );
+ let base = path.as_ref();
+ let rd = std::fs::read_dir(base).map_err(|_| {
+ UnexpectedValueException::new(format!(
+ "FilesystemIterator::__construct({}): Failed to open directory",
+ base.display()
+ ))
+ })?;
+ Ok(rd
+ .flatten()
+ .map(|entry| entry.path().to_string_lossy().into_owned())
+ .collect())
}
#[derive(Debug, Clone)]
@@ -161,6 +190,11 @@ impl RecursiveIteratorFileInfo {
self.path.to_string_lossy().into_owned()
}
+ // SplFileInfo::getLinkTarget(): readlink() on the entry. None is PHP's false-on-failure.
+ pub fn get_link_target(&self) -> Option<String> {
+ readlink(&self.path)
+ }
+
pub fn get_size(&self) -> i64 {
std::fs::metadata(&self.path)
.map(|m| m.len() as i64)
@@ -818,6 +852,15 @@ pub fn is_dir(path: impl AsRef<std::path::Path>) -> bool {
path.as_ref().is_dir()
}
+/// PHP `readlink()`: the target the link points at, without resolving it further.
+/// `None` is PHP's `false`-on-failure.
+/// TODO(phase-e): byte-string semantics -- PHP returns the raw bytes of the link target.
+pub fn readlink(path: impl AsRef<std::path::Path>) -> Option<String> {
+ std::fs::read_link(path)
+ .ok()
+ .map(|target| target.to_string_lossy().into_owned())
+}
+
pub fn fileatime(_filename: impl AsRef<std::path::Path>) -> Option<i64> {
std::fs::metadata(_filename.as_ref())
.ok()