1 /*-------------------------------------------------------------------------
2  *
3  * posix_sema.c
4  *	  Implement PGSemaphores using POSIX semaphore facilities
5  *
6  * We prefer the unnamed style of POSIX semaphore (the kind made with
7  * sem_init).  We can cope with the kind made with sem_open, however.
8  *
9  *
10  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
11  * Portions Copyright (c) 1994, Regents of the University of California
12  *
13  * IDENTIFICATION
14  *	  src/backend/port/posix_sema.c
15  *
16  *-------------------------------------------------------------------------
17  */
18 #include "postgres.h"
19 
20 #include <fcntl.h>
21 #include <signal.h>
22 #include <unistd.h>
23 
24 #include "miscadmin.h"
25 #include "storage/ipc.h"
26 #include "storage/pg_sema.h"
27 
28 
29 #ifdef USE_NAMED_POSIX_SEMAPHORES
30 /* PGSemaphore is pointer to pointer to sem_t */
31 #define PG_SEM_REF(x)	(*(x))
32 #else
33 /* PGSemaphore is pointer to sem_t */
34 #define PG_SEM_REF(x)	(x)
35 #endif
36 
37 
38 #define IPCProtection	(0600)	/* access/modify by user only */
39 
40 static sem_t **mySemPointers;	/* keep track of created semaphores */
41 static int	numSems;			/* number of semas acquired so far */
42 static int	maxSems;			/* allocated size of mySemaPointers array */
43 static int	nextSemKey;			/* next name to try */
44 
45 
46 static void ReleaseSemaphores(int status, Datum arg);
47 
48 
49 #ifdef USE_NAMED_POSIX_SEMAPHORES
50 
51 /*
52  * PosixSemaphoreCreate
53  *
54  * Attempt to create a new named semaphore.
55  *
56  * If we fail with a failure code other than collision-with-existing-sema,
57  * print out an error and abort.  Other types of errors suggest nonrecoverable
58  * problems.
59  */
60 static sem_t *
PosixSemaphoreCreate(void)61 PosixSemaphoreCreate(void)
62 {
63 	int			semKey;
64 	char		semname[64];
65 	sem_t	   *mySem;
66 
67 	for (;;)
68 	{
69 		semKey = nextSemKey++;
70 
71 		snprintf(semname, sizeof(semname), "/pgsql-%d", semKey);
72 
73 		mySem = sem_open(semname, O_CREAT | O_EXCL,
74 						 (mode_t) IPCProtection, (unsigned) 1);
75 
76 #ifdef SEM_FAILED
77 		if (mySem != (sem_t *) SEM_FAILED)
78 			break;
79 #else
80 		if (mySem != (sem_t *) (-1))
81 			break;
82 #endif
83 
84 		/* Loop if error indicates a collision */
85 		if (errno == EEXIST || errno == EACCES || errno == EINTR)
86 			continue;
87 
88 		/*
89 		 * Else complain and abort
90 		 */
91 		elog(FATAL, "sem_open(\"%s\") failed: %m", semname);
92 	}
93 
94 	/*
95 	 * Unlink the semaphore immediately, so it can't be accessed externally.
96 	 * This also ensures that it will go away if we crash.
97 	 */
98 	sem_unlink(semname);
99 
100 	return mySem;
101 }
102 #else							/* !USE_NAMED_POSIX_SEMAPHORES */
103 
104 /*
105  * PosixSemaphoreCreate
106  *
107  * Attempt to create a new unnamed semaphore.
108  */
109 static void
PosixSemaphoreCreate(sem_t * sem)110 PosixSemaphoreCreate(sem_t * sem)
111 {
112 	if (sem_init(sem, 1, 1) < 0)
113 		elog(FATAL, "sem_init failed: %m");
114 }
115 #endif   /* USE_NAMED_POSIX_SEMAPHORES */
116 
117 
118 /*
119  * PosixSemaphoreKill	- removes a semaphore
120  */
121 static void
PosixSemaphoreKill(sem_t * sem)122 PosixSemaphoreKill(sem_t * sem)
123 {
124 #ifdef USE_NAMED_POSIX_SEMAPHORES
125 	/* Got to use sem_close for named semaphores */
126 	if (sem_close(sem) < 0)
127 		elog(LOG, "sem_close failed: %m");
128 #else
129 	/* Got to use sem_destroy for unnamed semaphores */
130 	if (sem_destroy(sem) < 0)
131 		elog(LOG, "sem_destroy failed: %m");
132 #endif
133 }
134 
135 
136 /*
137  * PGReserveSemaphores --- initialize semaphore support
138  *
139  * This is called during postmaster start or shared memory reinitialization.
140  * It should do whatever is needed to be able to support up to maxSemas
141  * subsequent PGSemaphoreCreate calls.  Also, if any system resources
142  * are acquired here or in PGSemaphoreCreate, register an on_shmem_exit
143  * callback to release them.
144  *
145  * The port number is passed for possible use as a key (for Posix, we use
146  * it to generate the starting semaphore name).  In a standalone backend,
147  * zero will be passed.
148  *
149  * In the Posix implementation, we acquire semaphores on-demand; the
150  * maxSemas parameter is just used to size the array that keeps track of
151  * acquired semas for subsequent releasing.
152  */
153 void
PGReserveSemaphores(int maxSemas,int port)154 PGReserveSemaphores(int maxSemas, int port)
155 {
156 	mySemPointers = (sem_t **) malloc(maxSemas * sizeof(sem_t *));
157 	if (mySemPointers == NULL)
158 		elog(PANIC, "out of memory");
159 	numSems = 0;
160 	maxSems = maxSemas;
161 	nextSemKey = port * 1000;
162 
163 	on_shmem_exit(ReleaseSemaphores, 0);
164 }
165 
166 /*
167  * Release semaphores at shutdown or shmem reinitialization
168  *
169  * (called as an on_shmem_exit callback, hence funny argument list)
170  */
171 static void
ReleaseSemaphores(int status,Datum arg)172 ReleaseSemaphores(int status, Datum arg)
173 {
174 	int			i;
175 
176 	for (i = 0; i < numSems; i++)
177 		PosixSemaphoreKill(mySemPointers[i]);
178 	free(mySemPointers);
179 }
180 
181 /*
182  * PGSemaphoreCreate
183  *
184  * Initialize a PGSemaphore structure to represent a sema with count 1
185  */
186 void
PGSemaphoreCreate(PGSemaphore sema)187 PGSemaphoreCreate(PGSemaphore sema)
188 {
189 	sem_t	   *newsem;
190 
191 	/* Can't do this in a backend, because static state is postmaster's */
192 	Assert(!IsUnderPostmaster);
193 
194 	if (numSems >= maxSems)
195 		elog(PANIC, "too many semaphores created");
196 
197 #ifdef USE_NAMED_POSIX_SEMAPHORES
198 	*sema = newsem = PosixSemaphoreCreate();
199 #else
200 	PosixSemaphoreCreate(sema);
201 	newsem = sema;
202 #endif
203 
204 	/* Remember new sema for ReleaseSemaphores */
205 	mySemPointers[numSems++] = newsem;
206 }
207 
208 /*
209  * PGSemaphoreReset
210  *
211  * Reset a previously-initialized PGSemaphore to have count 0
212  */
213 void
PGSemaphoreReset(PGSemaphore sema)214 PGSemaphoreReset(PGSemaphore sema)
215 {
216 	/*
217 	 * There's no direct API for this in POSIX, so we have to ratchet the
218 	 * semaphore down to 0 with repeated trywait's.
219 	 */
220 	for (;;)
221 	{
222 		if (sem_trywait(PG_SEM_REF(sema)) < 0)
223 		{
224 			if (errno == EAGAIN || errno == EDEADLK)
225 				break;			/* got it down to 0 */
226 			if (errno == EINTR)
227 				continue;		/* can this happen? */
228 			elog(FATAL, "sem_trywait failed: %m");
229 		}
230 	}
231 }
232 
233 /*
234  * PGSemaphoreLock
235  *
236  * Lock a semaphore (decrement count), blocking if count would be < 0
237  */
238 void
PGSemaphoreLock(PGSemaphore sema)239 PGSemaphoreLock(PGSemaphore sema)
240 {
241 	int			errStatus;
242 
243 	/* See notes in sysv_sema.c's implementation of PGSemaphoreLock. */
244 	do
245 	{
246 		errStatus = sem_wait(PG_SEM_REF(sema));
247 	} while (errStatus < 0 && errno == EINTR);
248 
249 	if (errStatus < 0)
250 		elog(FATAL, "sem_wait failed: %m");
251 }
252 
253 /*
254  * PGSemaphoreUnlock
255  *
256  * Unlock a semaphore (increment count)
257  */
258 void
PGSemaphoreUnlock(PGSemaphore sema)259 PGSemaphoreUnlock(PGSemaphore sema)
260 {
261 	int			errStatus;
262 
263 	/*
264 	 * Note: if errStatus is -1 and errno == EINTR then it means we returned
265 	 * from the operation prematurely because we were sent a signal.  So we
266 	 * try and unlock the semaphore again. Not clear this can really happen,
267 	 * but might as well cope.
268 	 */
269 	do
270 	{
271 		errStatus = sem_post(PG_SEM_REF(sema));
272 	} while (errStatus < 0 && errno == EINTR);
273 
274 	if (errStatus < 0)
275 		elog(FATAL, "sem_post failed: %m");
276 }
277 
278 /*
279  * PGSemaphoreTryLock
280  *
281  * Lock a semaphore only if able to do so without blocking
282  */
283 bool
PGSemaphoreTryLock(PGSemaphore sema)284 PGSemaphoreTryLock(PGSemaphore sema)
285 {
286 	int			errStatus;
287 
288 	/*
289 	 * Note: if errStatus is -1 and errno == EINTR then it means we returned
290 	 * from the operation prematurely because we were sent a signal.  So we
291 	 * try and lock the semaphore again.
292 	 */
293 	do
294 	{
295 		errStatus = sem_trywait(PG_SEM_REF(sema));
296 	} while (errStatus < 0 && errno == EINTR);
297 
298 	if (errStatus < 0)
299 	{
300 		if (errno == EAGAIN || errno == EDEADLK)
301 			return false;		/* failed to lock it */
302 		/* Otherwise we got trouble */
303 		elog(FATAL, "sem_trywait failed: %m");
304 	}
305 
306 	return true;
307 }
308