From c8f1efebeaca041959859f2547931bcd108a35fc Mon Sep 17 00:00:00 2001
From: sfan5 <sfan5@live.de>
Date: Thu, 10 Oct 2024 17:40:06 +0200
Subject: [PATCH] Use execvp in fs::RecursiveDelete()

---
 src/filesys.cpp               | 73 ++++++++++++++++++-----------------
 src/unittest/test_filesys.cpp | 31 +++++++++++++++
 2 files changed, 69 insertions(+), 35 deletions(-)

diff --git a/src/filesys.cpp b/src/filesys.cpp
index 196aca080..b0a1f318e 100644
--- a/src/filesys.cpp
+++ b/src/filesys.cpp
@@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <IFileArchive.h>
 #include <IFileSystem.h>
 #endif
+
 #ifdef __linux__
 #include <fcntl.h>
 #include <sys/ioctl.h>
@@ -43,6 +44,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #endif
 #endif
 
+#ifdef _WIN32
+#include <windows.h>
+#include <shlwapi.h>
+#include <io.h>
+#include <direct.h>
+#else
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#endif
+
 // Error from last OS call as string
 #ifdef _WIN32
 #define LAST_OS_ERROR() porting::ConvertError(GetLastError())
@@ -59,11 +73,6 @@ namespace fs
  * Windows *
  ***********/
 
-#include <windows.h>
-#include <shlwapi.h>
-#include <io.h>
-#include <direct.h>
-
 std::vector<DirListNode> GetDirListing(const std::string &pathstring)
 {
 	std::vector<DirListNode> listing;
@@ -273,12 +282,6 @@ bool CopyFileContents(const std::string &source, const std::string &target)
  * POSIX *
  *********/
 
-#include <sys/types.h>
-#include <dirent.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
 std::vector<DirListNode> GetDirListing(const std::string &pathstring)
 {
 	std::vector<DirListNode> listing;
@@ -381,41 +384,41 @@ bool RecursiveDelete(const std::string &path)
 		Execute the 'rm' command directly, by fork() and execve()
 	*/
 
-	infostream<<"Removing \""<<path<<"\""<<std::endl;
+	infostream << "Removing \"" << path << "\"" << std::endl;
 
-	pid_t child_pid = fork();
+	assert(IsPathAbsolute(path));
 
-	if(child_pid == 0)
-	{
+	const pid_t child_pid = fork();
+
+	if (child_pid == -1) {
+		errorstream << "fork errno: " << errno << ": " << strerror(errno)
+			<< std::endl;
+		return false;
+	}
+
+	if (child_pid == 0) {
 		// Child
-		const char *argv[4] = {
-#ifdef __ANDROID__
-			"/system/bin/rm",
-#else
-			"/bin/rm",
-#endif
+		std::array<const char*, 4> argv = {
+			"rm",
 			"-rf",
 			path.c_str(),
-			NULL
+			nullptr
 		};
 
-		verbosestream<<"Executing '"<<argv[0]<<"' '"<<argv[1]<<"' '"
-				<<argv[2]<<"'"<<std::endl;
+		execvp(argv[0], const_cast<char**>(argv.data()));
 
-		execv(argv[0], const_cast<char**>(argv));
-
-		// Execv shouldn't return. Failed.
+		// note: use cerr because our logging won't flush in forked process
+		std::cerr << "exec errno: " << errno << ": " << strerror(errno)
+			<< std::endl;
 		_exit(1);
-	}
-	else
-	{
+	} else {
 		// Parent
-		int child_status;
+		int status;
 		pid_t tpid;
-		do{
-			tpid = wait(&child_status);
-		}while(tpid != child_pid);
-		return (child_status == 0);
+		do
+			tpid = waitpid(child_pid, &status, 0);
+		while (tpid != child_pid);
+		return WIFEXITED(status) && WEXITSTATUS(status) == 0;
 	}
 }
 
diff --git a/src/unittest/test_filesys.cpp b/src/unittest/test_filesys.cpp
index fd25d2de9..e24e79374 100644
--- a/src/unittest/test_filesys.cpp
+++ b/src/unittest/test_filesys.cpp
@@ -42,6 +42,7 @@ public:
 	void testSafeWriteToFile();
 	void testCopyFileContents();
 	void testNonExist();
+	void testRecursiveDelete();
 };
 
 static TestFileSys g_test_instance;
@@ -56,6 +57,7 @@ void TestFileSys::runTests(IGameDef *gamedef)
 	TEST(testSafeWriteToFile);
 	TEST(testCopyFileContents);
 	TEST(testNonExist);
+	TEST(testRecursiveDelete);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -338,3 +340,32 @@ void TestFileSys::testNonExist()
 	auto ifs = open_ifstream(path.c_str(), false);
 	UASSERT(!ifs.good());
 }
+
+void TestFileSys::testRecursiveDelete()
+{
+	std::string dirs[2];
+	dirs[0] = getTestTempDirectory() + DIR_DELIM "a";
+	dirs[1] = dirs[0] + DIR_DELIM "b";
+
+	std::string files[2] = {
+		dirs[0] + DIR_DELIM "file1",
+		dirs[1] + DIR_DELIM "file2"
+	};
+
+	for (auto &it : dirs)
+		fs::CreateDir(it);
+	for (auto &it : files)
+		open_ofstream(it.c_str(), false).close();
+
+	for (auto &it : dirs)
+		UASSERT(fs::IsDir(it));
+	for (auto &it : files)
+		UASSERT(fs::IsFile(it));
+
+	UASSERT(fs::RecursiveDelete(dirs[0]));
+
+	for (auto &it : dirs)
+		UASSERT(!fs::IsDir(it));
+	for (auto &it : files)
+		UASSERT(!fs::IsFile(it));
+}