From bb24bd82d4414cd54e37b7fdcd9cff8ed599d71f Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 20 Sep 2026 12:21:51 -0400
Subject: [PATCH] Map ERROR_UNTRUSTED_MOUNT_POINT to ENOENT on Windows

Recent Windows versions can fail path traversal with
ERROR_UNTRUSTED_MOUNT_POINT (448, "The path cannot be traversed because it
contains an untrusted mount point") when a reparse point is considered
untrusted.  Since PostgreSQL emulates symbolic links with junction points,
this is reported for paths under pg_tblspc, and because the code was not in
win32error.c's translation table it fell through to the catch-all EINVAL.

That produced confusing failures such as

  ERROR:  could not stat directory "pg_tblspc/16388/PG_19_202609165/5": Invalid argument
  ERROR:  could not create directory "pg_tblspc/...": Invalid argument

and made 13 TAP tests fail on a Windows 11 (build 26200) aarch64 animal,
including recovery/014_unlogged_reinit, recovery/018_wal_optimize,
test_misc/002_tablespace, initdb/001_initdb, pg_basebackup/010_pg_basebackup,
pg_verifybackup/003_corruption and 008_untar, pg_combinebackup/002_compare_backups,
pg_checksums/002_actions, pg_waldump/001_basic, pgbench/001_pgbench_with_server,
scripts/090_reindexdb and worker_spi/002_worker_terminate.

Map it to ENOENT, consistent with the existing treatment of the other
"this path cannot be resolved" conditions ERROR_INVALID_NAME and
ERROR_CANT_RESOLVE_FILENAME (the latter added by 387803d81d6 for broken
junction points).  Provide a fallback definition of the constant so that we
continue to build against older SDK and MinGW headers.
---
 src/port/win32error.c | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/src/port/win32error.c b/src/port/win32error.c
index 11d854c7370..6a4ab80a8d4 100644
--- a/src/port/win32error.c
+++ b/src/port/win32error.c
@@ -17,6 +17,14 @@
 #include "postgres_fe.h"
 #endif
 
+/*
+ * ERROR_UNTRUSTED_MOUNT_POINT was added in newer Windows SDKs; define it here
+ * so that we still build against older SDK or MinGW headers that lack it.
+ */
+#ifndef ERROR_UNTRUSTED_MOUNT_POINT
+#define ERROR_UNTRUSTED_MOUNT_POINT 448L
+#endif
+
 static const struct
 {
 	DWORD		winerr;
@@ -170,6 +178,21 @@ static const struct
 	},
 	{
 		ERROR_CANT_RESOLVE_FILENAME, ENOENT
+	},
+	{
+		/*
+		 * ERROR_UNTRUSTED_MOUNT_POINT ("The path cannot be traversed because
+		 * it contains an untrusted mount point") is reported by recent
+		 * Windows versions when a reparse point is considered untrusted, for
+		 * example a junction created by a non-administrative user that points
+		 * outside of that user's scope.  PostgreSQL uses junction points to
+		 * emulate symbolic links for tablespaces, so this can be reported for
+		 * paths under pg_tblspc.  Map it like the other "this path cannot be
+		 * resolved" errors above, so that callers using
+		 * errcode_for_file_access() report a sensible condition instead of the
+		 * EINVAL that an unmapped code falls back to.
+		 */
+		ERROR_UNTRUSTED_MOUNT_POINT, ENOENT
 	}
 };
 
-- 
2.54.0

