From f41d708fb685c3c53cd2c017570b5c1fb1264bc6 Mon Sep 17 00:00:00 2001 From: Adam_Feiss Date: Mon, 18 May 2026 23:02:35 +0200 Subject: [PATCH 1/2] fix: add functions for reading symlink metadata and removing directories recursively --- packages/app-lib/src/util/io.rs | 89 ++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/packages/app-lib/src/util/io.rs b/packages/app-lib/src/util/io.rs index ea40bd9f79..fc08953703 100644 --- a/packages/app-lib/src/util/io.rs +++ b/packages/app-lib/src/util/io.rs @@ -3,6 +3,7 @@ use eyre::{Context, ContextCompat, Result, eyre}; use std::{ + fs::Metadata, io::{ErrorKind, Write}, path::Path, }; @@ -55,6 +56,35 @@ pub fn canonicalize( }) } +pub async fn read_symlink_metadata( + path: impl AsRef, +) -> Result { + let path = path.as_ref(); + + tokio::fs::symlink_metadata(path) + .await + .map_err(|e| IOError::IOPathError { + source: e, + path: path.to_string_lossy().to_string(), + }) +} + +pub fn check_is_symlink(metadata: &Metadata) -> bool { + #[cfg(windows)] + { + use std::os::windows::fs::MetadataExt; + + const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0400; + + metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0 + } + + #[cfg(not(windows))] + { + metadata.file_type().is_symlink() + } +} + pub async fn read_dir( path: impl AsRef, ) -> Result { @@ -91,16 +121,63 @@ pub async fn create_dir_all( }) } + pub async fn remove_dir_all( path: impl AsRef, ) -> Result<(), IOError> { - let path = path.as_ref(); - tokio::fs::remove_dir_all(path) + remove_dir_all_inner(path.as_ref()).await +} + +async fn remove_link( + path: &std::path::Path, + _metadata: &std::fs::Metadata, +) -> Result<(), IOError> { + remove_file(path).await +} + +#[async_recursion::async_recursion] +async fn remove_dir_all_inner( + path: &std::path::Path, +) -> Result<(), IOError> { + let metadata = read_symlink_metadata(path) .await - .map_err(|e| IOError::IOPathError { - source: e, - path: path.to_string_lossy().to_string(), - }) + .wrap_err_with(|| eyre!("reading metadata for {path:?}"))?; + + if check_is_symlink(&metadata) { + return remove_link(path, &metadata).await; + } + + let mut dir = read_dir(path) + .await + .wrap_err_with(|| eyre!("reading directory {path:?}"))?; + + while let Some(entry) = dir + .next_entry() + .await + .wrap_err_with(|| eyre!("reading directory entry in {path:?}"))? + { + let entry_path = entry.path(); + + let entry_metadata = read_symlink_metadata(&entry_path) + .await + .wrap_err_with(|| eyre!("reading metadata for {entry_path:?}"))?; + + if check_is_symlink(&entry_metadata) { + remove_link(&entry_path, &entry_metadata).await?; + } else if entry_metadata.is_dir() { + remove_dir_all_inner(&entry_path).await?; + } else { + remove_file(&entry_path) + .await + .wrap_err_with(|| eyre!("removing file {entry_path:?}"))?; + } + } + + remove_dir(path) + .await + .wrap_err_with(|| eyre!("removing directory {path:?}"))?; + + Ok(()) } /// Reads a text file to a string, automatically detecting its encoding and From 4633fd6e3a5943eb9aa57d2bbf017d80ce14f8f9 Mon Sep 17 00:00:00 2001 From: Adam_Feiss Date: Mon, 18 May 2026 23:02:57 +0200 Subject: [PATCH 2/2] fix: pass path by reference to remove_dir_all in auto_install_java and delete_world functions --- packages/app-lib/src/api/jre.rs | 2 +- packages/app-lib/src/api/worlds.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app-lib/src/api/jre.rs b/packages/app-lib/src/api/jre.rs index d0a2e74572..6a1af96e7c 100644 --- a/packages/app-lib/src/api/jre.rs +++ b/packages/app-lib/src/api/jre.rs @@ -113,7 +113,7 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result { let path = path.join(dir); if path.exists() { - io::remove_dir_all(path).await?; + io::remove_dir_all(&path).await?; } } diff --git a/packages/app-lib/src/api/worlds.rs b/packages/app-lib/src/api/worlds.rs index 3801424646..5e5e43291b 100644 --- a/packages/app-lib/src/api/worlds.rs +++ b/packages/app-lib/src/api/worlds.rs @@ -652,7 +652,7 @@ pub async fn delete_world(instance: &Path, world: &str) -> Result<()> { while let Some(entry) = dir.next_entry().await? { let path = entry.path(); if entry.file_type().await?.is_dir() { - io::remove_dir_all(path).await?; + io::remove_dir_all(&path).await?; continue; } if path != lock_path {