From: Tim Wickberg <tim@schedmd.com>
Date: Wed Oct 11 12:45:25 2023 -0600
Subject: [PATCH 6/19]Add rmdir_recursive().
Patch-mainline: Upstream
Git-repo: https://github.com/SchedMD/slurm
Git-commit: 52d40c541670bb8ebe3db3666272276f39bc6bc1
References: bsc##1216207, CVE-2023-41914
Signed-off-by: Egbert Eich <eich@suse.de>

---
 src/common/fd.c           | 92 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/common/fd.h           |  9 +++++
 src/common/slurm_xlator.h |  1 +
 3 files changed, 101 insertions(+), 1 deletion(-)

diff --git a/src/common/fd.c b/src/common/fd.c
index 5de2ecd307..0463a56cbf 100644
--- a/src/common/fd.c
+++ b/src/common/fd.c
@@ -35,6 +35,7 @@
  *  Refer to "fd.h" for documentation on public functions.
 \*****************************************************************************/
 
+#include <dirent.h>
 #include <assert.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -53,7 +54,7 @@ strong_alias(fd_read_n,		slurm_fd_read_n);
 strong_alias(fd_write_n,	slurm_fd_write_n);
 strong_alias(fd_set_blocking,	slurm_fd_set_blocking);
 strong_alias(fd_set_nonblocking,slurm_fd_set_nonblocking);
-
+strong_alias(rmdir_recursive, slurm_rmdir_recursive);
 
 static int fd_get_lock(int fd, int cmd, int type);
 static pid_t fd_test_lock(int fd, int type);
@@ -303,3 +304,92 @@ extern int wait_fd_readable(int fd, int time_limit)
 		}
 	}
 }
+
+static int _rmdir_recursive(int dirfd)
+{
+	int rc = 0;
+	DIR *dp;
+	struct dirent *ent;
+
+	if (!(dp = fdopendir(dirfd))) {
+		error("%s: can't open directory: %m", __func__);
+		return 1;
+	}
+
+	while ((ent = readdir(dp))) {
+		int childfd = -1;
+
+		/* skip special directories */
+		if (!strcmp(ent->d_name, ".") ||
+		    !strcmp(ent->d_name, "..")) {
+			continue;
+		}
+
+		/* try to remove entry, first as a file, then as a directory */
+		if (unlinkat(dirfd, ent->d_name, 0) != -1) {
+			debug("%s: removed file `%s`", __func__, ent->d_name);
+			continue;
+		} else if (unlinkat(dirfd, ent->d_name, AT_REMOVEDIR) != -1) {
+			debug("%s: removed empty directory `%s`",
+			      __func__, ent->d_name);
+			continue;
+		}
+
+		/* removal didn't work. assume it's a non-empty directory */
+		if ((childfd = openat(dirfd, ent->d_name,
+				      (O_DIRECTORY | O_NOFOLLOW))) < 0) {
+			debug("%s: openat() failed for `%s`: %m",
+			      __func__, ent->d_name);
+			rc++;
+			continue;
+		}
+
+		debug("%s: descending into directory `%s`",
+		      __func__, ent->d_name);
+		rc += _rmdir_recursive(childfd);
+		(void) close(childfd);
+
+		if (unlinkat(dirfd, ent->d_name, AT_REMOVEDIR) != -1) {
+			debug("%s: removed now-empty directory `%s`",
+			      __func__, ent->d_name);
+		} else {
+			debug("%s: unlinkat() failed for `%s`: %m",
+			      __func__, ent->d_name);
+			rc++;
+		}
+	}
+	closedir(dp);
+
+	return rc;
+}
+
+extern int rmdir_recursive(const char *path, bool remove_top)
+{
+	int rc = 0;
+	int dirfd;
+
+	if ((dirfd = open(path, O_DIRECTORY | O_NOFOLLOW)) < 0) {
+		error("%s: could not open %s", __func__, path);
+		return 1;
+	}
+
+	rc = _rmdir_recursive(dirfd);
+	close(dirfd);
+
+	if (remove_top) {
+		if (rmdir(path) < 0) {
+			debug("%s: rmdir() failed for `%s`: %m",
+			      __func__, path);
+			rc++;
+		} else {
+			debug("%s: removed now-empty top directory `%s`",
+			      __func__, path);
+		}
+	}
+
+	if (rc)
+		error("%s: could not completely remove `%s`, %d files left",
+		      __func__, path, rc);
+
+	return rc;
+}
diff --git a/src/common/fd.h b/src/common/fd.h
index f4be927533..d522602f19 100644
--- a/src/common/fd.h
+++ b/src/common/fd.h
@@ -150,4 +150,13 @@ extern int wait_fd_readable(int fd, int time_limit);
 /* Wait for a file descriptor to be readable (up to time_limit seconds).
  * Return 0 when readable or -1 on error */
 
+/*
+ * Recursively remove a directory and all contents.
+ * Takes care not to follow any symlinks outside the target directory.
+ *
+ * Returns the number of files/directories it failed to remove,
+ * or 0 on success.
+ */
+extern int rmdir_recursive(const char *path, bool remove_top);
+
 #endif /* !_FD_H */
diff --git a/src/common/slurm_xlator.h b/src/common/slurm_xlator.h
index c186383c22..286897662d 100644
--- a/src/common/slurm_xlator.h
+++ b/src/common/slurm_xlator.h
@@ -115,6 +115,7 @@
 #define fd_write_n		slurm_fd_write_n
 #define fd_set_blocking		slurm_fd_set_blocking
 #define fd_set_nonblocking	slurm_fd_set_nonblocking
+#define rmdir_recursive		slurm_rmdir_recursive
 
 /* hostlist.[ch] functions */
 #define	hostlist_create		slurm_hostlist_create
