From 154163238e933988a9876aac88848258c2da7380 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sat, 1 May 2010 22:46:30 +0000
Subject: [PATCH] Add code to InternalIpcMemoryCreate() to handle the case
 where shmget() returns EINVAL for an existing shared memory segment. 
 Although it's not terribly sensible, that behavior does meet the POSIX spec
 because EINVAL is the appropriate error code when the existing segment is
 smaller than the requested size, and the spec explicitly disclaims any
 particular ordering of error checks.  Moreover, it does in fact happen on OS
 X and probably other BSD-derived kernels.  (We were able to talk NetBSD into
 changing their code, but purging that behavior from the wild completely seems
 unlikely to happen.) We need to distinguish collision with a pre-existing
 segment from invalid size request in order to behave sensibly, so it's worth
 some extra code here to get it right.  Per report from Gavin Kistner and
 subsequent investigation.

Back-patch to all supported versions, since any of them could get used
with a kernel having the debatable behavior.
---
 src/backend/port/sysv_shmem.c | 44 ++++++++++++++++++++++++++++++++++-
 1 file changed, 43 insertions(+), 1 deletion(-)

diff --git a/src/backend/port/sysv_shmem.c b/src/backend/port/sysv_shmem.c
index 6829f3d4f67..8cdabf244b1 100644
--- a/src/backend/port/sysv_shmem.c
+++ b/src/backend/port/sysv_shmem.c
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/port/sysv_shmem.c,v 1.55 2010/01/02 16:57:50 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/port/sysv_shmem.c,v 1.56 2010/05/01 22:46:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -92,6 +92,48 @@ InternalIpcMemoryCreate(IpcMemoryKey memKey, Size size)
 			)
 			return NULL;
 
+		/*
+		 * Some BSD-derived kernels are known to return EINVAL, not EEXIST,
+		 * if there is an existing segment but it's smaller than "size"
+		 * (this is a result of poorly-thought-out ordering of error tests).
+		 * To distinguish between collision and invalid size in such cases,
+		 * we make a second try with size = 0.  These kernels do not test
+		 * size against SHMMIN in the preexisting-segment case, so we will
+		 * not get EINVAL a second time if there is such a segment.
+		 */
+		if (errno == EINVAL)
+		{
+			int		save_errno = errno;
+
+			shmid = shmget(memKey, 0, IPC_CREAT | IPC_EXCL | IPCProtection);
+
+			if (shmid < 0)
+			{
+				/* As above, fail quietly if we verify a collision */
+				if (errno == EEXIST || errno == EACCES
+#ifdef EIDRM
+					|| errno == EIDRM
+#endif
+					)
+					return NULL;
+				/* Otherwise, fall through to report the original error */
+			}
+			else
+			{
+				/*
+				 * On most platforms we cannot get here because SHMMIN is
+				 * greater than zero.  However, if we do succeed in creating
+				 * a zero-size segment, free it and then fall through to
+				 * report the original error.
+				 */
+				if (shmctl(shmid, IPC_RMID, NULL) < 0)
+					elog(LOG, "shmctl(%d, %d, 0) failed: %m",
+						 (int) shmid, IPC_RMID);
+			}
+
+			errno = save_errno;
+		}
+
 		/*
 		 * Else complain and abort
 		 */
-- 
GitLab