Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1waapC-001uhs-09 for pgsql-hackers@arkaria.postgresql.org; Fri, 19 Jun 2026 15:08:06 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1waapA-00GoPa-1p for pgsql-hackers@arkaria.postgresql.org; Fri, 19 Jun 2026 15:08:04 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1waapA-00GoPR-0t for pgsql-hackers@lists.postgresql.org; Fri, 19 Jun 2026 15:08:04 +0000 Received: from sss.pgh.pa.us ([68.162.161.243]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1waap8-000000019nM-3jdA for pgsql-hackers@lists.postgresql.org; Fri, 19 Jun 2026 15:08:03 +0000 Received: from sss1.sss.pgh.pa.us (localhost [127.0.0.1]) by sss.pgh.pa.us (8.18.1/8.18.1) with ESMTP id 65JF7w1L1528025; Fri, 19 Jun 2026 11:07:58 -0400 From: Tom Lane To: =?utf-8?Q?=C3=81lvaro?= Herrera cc: Andrew Dunstan , PostgreSQL Hackers Subject: Re: fix pg_mkdir_p to tolerate concurrent directory creation In-reply-to: References: Comments: In-reply-to =?utf-8?Q?=C3=81lvaro?= Herrera message dated "Fri, 19 Jun 2026 16:01:47 +0200" MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0" Content-ID: <1528019.1781881669.0@sss.pgh.pa.us> Date: Fri, 19 Jun 2026 11:07:58 -0400 Message-ID: <1528024.1781881678@sss.pgh.pa.us> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk ------- =_aaaaaaaaaa0 Content-Type: text/plain; charset="us-ascii" Content-ID: <1528019.1781881669.1@sss.pgh.pa.us> Actually ... given this change, why don't we drop the initial stat() call? Just try mkdir(), and move on if it succeeds. If not, but there's already a directory there, we're good. That eliminates the race condition without duplicating code. regards, tom lane ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name*0="v3-0001-Make-pg_mkdir_p-tolerant-of-a-concurrent-director.p"; name*1="atch"; charset="us-ascii" Content-ID: <1528019.1781881669.2@sss.pgh.pa.us> Content-Description: v3-0001-Make-pg_mkdir_p-tolerant-of-a-concurrent-director.patch diff --git a/src/port/pgmkdirp.c b/src/port/pgmkdirp.c index 7d7cea4dd0e..3e6b06fce79 100644 --- a/src/port/pgmkdirp.c +++ b/src/port/pgmkdirp.c @@ -56,7 +56,6 @@ int pg_mkdir_p(char *path, int omode) { - struct stat sb; mode_t numask, oumask; int last, @@ -119,24 +118,46 @@ pg_mkdir_p(char *path, int omode) if (last) (void) umask(oumask); - /* check for pre-existing directory */ - if (stat(path, &sb) == 0) + if (mkdir(path, last ? omode : S_IRWXU | S_IRWXG | S_IRWXO) < 0) { - if (!S_ISDIR(sb.st_mode)) + /* + * If we got EEXIST because there's already a directory there, + * don't complain. + */ +#ifndef WIN32 + int save_errno = errno; + struct stat sb; + + if (save_errno != EEXIST || + stat(path, &sb) != 0 || + !S_ISDIR(sb.st_mode)) { - if (last) - errno = EEXIST; - else - errno = ENOTDIR; + /* Don't let stat replace mkdir's errno */ + errno = save_errno; retval = -1; break; } +#else /* WIN32 */ + /* + * On Windows, stat() opens a handle and can transiently fail on a + * directory another process is concurrently creating. Probe with + * a path-based attribute query instead: it requests only + * FILE_READ_ATTRIBUTES and is exempt from share-mode denial, so + * it reliably sees a concurrently-created directory. We assume + * GetFileAttributes() won't change errno. + */ + DWORD attr = GetFileAttributes(path); + + if (errno != EEXIST || + attr == INVALID_FILE_ATTRIBUTES || + !(attr & FILE_ATTRIBUTE_DIRECTORY)) + { + retval = -1; + break; + } +#endif /* WIN32 */ } - else if (mkdir(path, last ? omode : S_IRWXU | S_IRWXG | S_IRWXO) < 0) - { - retval = -1; - break; - } + if (!last) *p = '/'; } ------- =_aaaaaaaaaa0--