From 9634227181029b0db73d63794459b6a4ec992a01 Mon Sep 17 00:00:00 2001
From: Justine Tunney <jtunney@gmail.com>
Date: Sun, 5 Feb 2023 16:50:11 -0800
Subject: [PATCH] Polyfill Linux unlink() EISDIR on POSIX platforms

---
 libc/calls/unlinkat.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/libc/calls/unlinkat.c b/libc/calls/unlinkat.c
index dfc93f506..c90f83e83 100644
--- a/libc/calls/unlinkat.c
+++ b/libc/calls/unlinkat.c
@@ -17,13 +17,16 @@
 │ PERFORMANCE OF THIS SOFTWARE.                                                │
 ╚─────────────────────────────────────────────────────────────────────────────*/
 #include "libc/calls/calls.h"
+#include "libc/calls/struct/stat.h"
 #include "libc/calls/syscall-nt.internal.h"
 #include "libc/calls/syscall-sysv.internal.h"
 #include "libc/dce.h"
+#include "libc/errno.h"
 #include "libc/intrin/asan.internal.h"
 #include "libc/intrin/describeflags.internal.h"
 #include "libc/intrin/strace.internal.h"
 #include "libc/intrin/weaken.h"
+#include "libc/sysv/consts/s.h"
 #include "libc/sysv/errfuns.h"
 #include "libc/zipos/zipos.internal.h"
 
@@ -40,6 +43,7 @@
  */
 int unlinkat(int dirfd, const char *path, int flags) {
   int rc;
+
   if (IsAsan() && !__asan_is_valid_str(path)) {
     rc = efault();
   } else if (_weaken(__zipos_notat) &&
@@ -50,6 +54,18 @@ int unlinkat(int dirfd, const char *path, int flags) {
   } else {
     rc = sys_unlinkat_nt(dirfd, path, flags);
   }
+
+  // POSIX.1 says unlink(directory) raises EPERM but on Linux
+  // it always raises EISDIR, which is so much less ambiguous
+  if (!IsLinux() && rc == -1 && !flags && errno == EPERM) {
+    struct stat st;
+    if (!fstatat(dirfd, path, &st, 0) && S_ISDIR(st.st_mode)) {
+      errno = EISDIR;
+    } else {
+      errno = EPERM;
+    }
+  }
+
   STRACE("unlinkat(%s, %#s, %#b) → %d% m", DescribeDirfd(dirfd), path, flags,
          rc);
   return rc;