xref: /dragonfly/lib/libc/sysvipc/shm.c (revision 0ca59c34)
1 /*
2  * Copyright (c) 2013 Larisa Grigore <larisagrigore@gmail.com>.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  * 3. All advertising materials mentioning features or use of this software
13  *    must display the following acknowledgement:
14  *	This product includes software developed by Adam Glass and Charles
15  *	Hannum.
16  * 4. The names of the authors may not be used to endorse or promote products
17  *    derived from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include "namespace.h"
32 #include <sys/param.h>
33 #include <sys/queue.h>
34 #include <sys/mman.h>
35 #include <sys/shm.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <err.h>
41 #include <pthread.h>
42 #include <unistd.h>
43 #include "un-namespace.h"
44 
45 #include "sysvipc_lock.h"
46 #include "sysvipc_ipc.h"
47 #include "sysvipc_sockets.h"
48 #include "sysvipc_shm.h"
49 #include "sysvipc_hash.h"
50 
51 #define SYSV_MUTEX_LOCK(x)		if (__isthreaded) _pthread_mutex_lock(x)
52 #define SYSV_MUTEX_UNLOCK(x)	if (__isthreaded) _pthread_mutex_unlock(x)
53 #define SYSV_MUTEX_DESTROY(x)	if (__isthreaded) _pthread_mutex_destroy(x)
54 
55 struct hashtable *shmres = NULL;
56 struct hashtable *shmaddrs = NULL;
57 pthread_mutex_t lock_resources = PTHREAD_MUTEX_INITIALIZER;
58 
59 /* File descriptor used to communicate with the daemon. */
60 extern int daemon_fd;
61 /* Structure used to save semaphore operation with SEMUNDO flag. */
62 extern struct sem_undo *undos;
63 
64 static int
65 shminit(void) {
66 	if (shmres) {
67 		errno = EPERM;
68 		return (-1);
69 	}
70 
71 	shmres = _hash_init(MAXSIZE);
72 	if (!shmres)
73 		goto out_resources;
74 
75 	shmaddrs = _hash_init(MAXSIZE);
76 	if (!shmaddrs)
77 		goto out_addrs;
78 
79 	return 0;
80 
81 out_addrs:
82 	_hash_destroy(shmres);
83 out_resources:
84 	return -1;
85 }
86 
87 /*static int
88 shmexit(void) {
89 	if (!shmres)
90 		return -EPERM;
91 
92 	_hash_destroy(shmres);
93 	_hash_destroy(shmaddrs);
94 	SYSV_MUTEX_DESTROY(lock_resources);
95 
96 	return 0;
97 }*/
98 
99 /* Init sysv ipc resources and those used for shared memory. */
100 static int
101 shmcheck(void) {
102 	int ret;
103 
104 	/* Init sysv resources. */
105 	if ((ret = sysvinit()) != 0)
106 		return (ret);
107 	/* Init resorces used for shared memory. */
108 	if ((ret = shminit()) < 0)
109 		return (ret);
110 	return (0);
111 }
112 
113 /* Check if sysv ipc resources are initialized. */
114 static int
115 is_shm_started(void) {
116 	if (!is_sysvinit())
117 		return (0);
118 	if (!shmres)
119 		return (0);
120 	return (1);
121 }
122 
123 /* OBS: It is used only a rwlock for both hashtables and
124  * socket. I've made that choice because is I considered to
125  * be much expensive to acquire/release more than one especially
126  * as the daemon is not multithreading.
127  */
128 
129 /* This function has another parameter apart from shmget.
130  * The parameter has information about the type of sysv
131  * ipc resource (shm, sem, msg, undo).
132  * The undo segment is used for sem ops with UNDO flag set.
133  */
134 int
135 _shmget(key_t key, size_t size, int shmflg, int type) {
136 	struct shmget_msg msg;
137 	struct shm_data *data;
138 	int shmid, fd;
139 	int flags;
140 
141 	SYSV_MUTEX_LOCK(&lock_resources);
142 	if (shmcheck() < 0) {
143 		sysv_print_err("init sysv ipc\n");
144 		goto done;
145 	}
146 
147 	msg.key = key;
148 	msg.size = size;
149 	msg.shmflg = shmflg;
150 	msg.type = type;
151 
152 	send_message(daemon_fd, type, (char *)&msg, sizeof(msg));
153 
154 	/* Accept a file installed by the daemon.
155 	 * The file is used as shared memory. */
156 	fd = receive_fd(daemon_fd);
157 	if (fd < 0) {
158 		shmid = -1;
159 		goto done;
160 	}
161 
162 	flags = _fcntl(fd, F_GETFD, 0);
163 	if (_fcntl(fd, F_SETFD, flags & FD_CLOEXEC) == -1) {
164 		sysv_print_err("fcntl error\n");
165 		shmid = -1;
166 		goto done;
167 	}
168 
169 	/* Receive the resource id or error. */
170 	receive_message(daemon_fd, (char *)&shmid, sizeof(shmid));
171 
172 	if (shmid < 0) {
173 		errno = -shmid;
174 		shmid = -1;
175 		goto done;
176 	}
177 
178 	/* Do we already have an entry for this resource? */
179 	data = _hash_lookup(shmres, shmid);
180 	if (data)
181 		goto done;
182 
183 	/* If not, add necessary data about it. */
184 	data = malloc(sizeof(struct shm_data));
185 	data->fd = fd;
186 	data->size = size;
187 	data->shmid = shmid;
188 	data->type = type;
189 	data->used = 0;
190 	data->removed = 0;
191 	data->access = 0; /* Used for sems. */
192 
193 	/* Insert data in hashtable using the shmid. */
194 	_hash_insert(shmres, shmid, data);
195 done:
196 	SYSV_MUTEX_UNLOCK(&lock_resources);
197 	return (shmid);
198 }
199 
200 int
201 sysvipc_shmget(key_t key, size_t size, int shmflg) {
202 	return (_shmget(key, size, shmflg, SHMGET));
203 }
204 
205 void *
206 sysvipc_shmat(int shmid, const void *shmaddr, int shmflg) {
207 	struct shmat_msg msg;
208 	void *addr = NULL;
209 	int error;
210 	int flags, prot;
211 	size_t size;
212 	struct shm_data *data;
213 
214 	SYSV_MUTEX_LOCK(&lock_resources);
215 	if (!is_shm_started()) {
216 		errno = EINVAL;
217 		goto done;
218 	}
219 
220 	/* Get data using shmid. */
221 	data = _hash_lookup(shmres, shmid);
222 	if (data == NULL) {
223 		errno = EINVAL;
224 		goto done;
225 	}
226 
227 	size = round_page(data->size);
228 
229 #ifdef VM_PROT_READ_IS_EXEC
230 	prot = PROT_READ | PROT_EXECUTE;
231 #else
232 	prot = PROT_READ;
233 #endif
234 	if ((shmflg & SHM_RDONLY) == 0)
235 		prot |= PROT_WRITE;
236 
237 	flags = MAP_SHARED;
238 	if (shmaddr) {
239 		if (shmflg & SHM_RND) {
240 			addr = (void *)((vm_offset_t)shmaddr & ~(SHMLBA-1));
241 		} else if (((vm_offset_t)shmaddr & (SHMLBA-1)) == 0) {
242 			addr = __DECONST(void *, shmaddr);
243 		} else {
244 			errno = EINVAL;
245 			goto done;
246 		}
247 	}
248 
249 	msg.shmid = shmid;
250 	msg.shmaddr = shmaddr;
251 	msg.shmflg = shmflg;
252 	msg.size = data->size; /* For undo segment. */
253 
254 	send_message(daemon_fd, SHMAT, (char *)&msg, sizeof(msg));
255 	receive_message(daemon_fd, (char *)&error, sizeof(error));
256 	if (error) {
257 		errno = error;
258 		goto done;
259 	}
260 
261 	addr = mmap(addr, size, prot, flags, data->fd, 0);
262 	if (!addr) {
263 		sysv_print_err("mmap\n");
264 		/* Detach ourselves from the segment. */
265 		send_message(daemon_fd, SHMDT, (char *)&shmid, sizeof(shmid));
266 		goto done;
267 	}
268 
269 	/* Necessary for SEMGET, MSGGET, UNDOGET. */
270 	data->internal = addr;
271 
272 	/* Save the mapped address for munmap call. */
273 	_hash_insert(shmaddrs, (u_long)addr, data);
274 done:
275 	SYSV_MUTEX_UNLOCK(&lock_resources);
276 	return (addr);
277 }
278 
279 /* Remove a sysv ipc resource. */
280 static
281 void shmremove(int shmid) {
282 	struct shm_data *data;
283 	data = _hash_remove(shmres, shmid);
284 
285 	//TODO nu trebuie demapat?
286 	_close(data->fd);
287 	free(data);
288 	data = NULL;
289 }
290 
291 int
292 sysvipc_shmctl(int shmid, int cmd, struct shmid_ds *buf) {
293 	int size, ret;
294 	struct shmctl_msg *msg;
295 
296 /*	if (cmd == IPC_SET)
297 		size = sizeof(struct shmctl_msg) + sizeof(struct shmid_ds);
298 	else
299 		size = sizeof(struct shmctl_msg);
300 */
301 	SYSV_MUTEX_LOCK(&lock_resources);
302 
303 	ret = -1;
304 
305 	if (!is_shm_started()) {
306 		errno = EINVAL;
307 		goto done;
308 	}
309 
310 	size = sizeof(struct shmctl_msg);
311 	msg = malloc(size);
312 	msg->shmid = shmid;
313 	msg->cmd = cmd;
314 
315 	if (cmd == IPC_SET)
316 		msg->buf = *buf;
317 
318 	send_message(daemon_fd, SHMCTL, (char *)msg, sizeof(*msg));
319 
320 	receive_message(daemon_fd, (char *)&ret, sizeof(ret));
321 
322 	/* Get data in IPC_STAT case. */
323 	if (ret == 0 && cmd == IPC_STAT)
324 		receive_message(daemon_fd, (char *)buf, sizeof(*buf));
325 
326 	/* Free all resources specific to a shmid in IPC_RMID case. */
327 	if (ret == 0 && cmd == IPC_RMID)
328 		shmremove(shmid);
329 
330 	errno = ret;
331 done:
332 	SYSV_MUTEX_UNLOCK(&lock_resources);
333 	return (ret == 0 ? 0 : -1);
334 }
335 
336 /* Functionality of shmdt with the possibility to inform or not
337  * the daemon.
338  * Inform the daemon when shmdt is called and not when an error
339  * occurs and the daemon doesn't know that the process is attaced.
340  */
341 static int
342 _shmdt(const void *shmaddr, int send_to_daemon) {
343 	int ret;
344 	size_t size;
345 	struct shm_data *data;
346 
347 	ret = -1;
348 
349 	SYSV_MUTEX_LOCK(&lock_resources);
350 	if (!is_shm_started()) {
351 		errno = EINVAL;
352 		goto done;
353 	}
354 
355 	/* Verify if shmaddr was returned from a shmat call. */
356 	data = _hash_remove(shmaddrs, (u_long)shmaddr);
357 	if (data == NULL) {
358 		errno = EINVAL;
359 		goto done;
360 	}
361 
362 	size = round_page(data->size);
363 
364 	ret = munmap(__DECONST(void *, shmaddr), size);
365 	if (ret)
366 		goto done;
367 
368 	if (send_to_daemon)
369 		send_message(daemon_fd, SHMDT, (char *)&data->shmid, sizeof(int));
370 
371 	shmaddr = NULL;
372 	free(data);
373 	data = NULL;
374 done:
375 	SYSV_MUTEX_UNLOCK(&lock_resources);
376 	return (ret);
377 }
378 
379 int
380 sysvipc_shmdt(const void *shmaddr) {
381 	return (_shmdt(shmaddr, 1));
382 }
383 
384 void
385 shmchild(void) {
386 	int i;
387 	struct entries_list *list;
388 	struct hashentry *tmp, *ttmp;
389 	struct shmat_msg msg;
390 	struct shm_data *data;
391 	int error;
392 
393 /* OBS: no locking is necessary because this function is called
394  * after the child is created and at that moment only one thread
395  * exists in the process.
396  */
397 	for (i=0; i<get_hash_size(MAXSIZE); i++) {
398 		list = &shmaddrs->entries[i];
399 		if (LIST_EMPTY(list))
400 			continue;
401 		LIST_FOREACH_MUTABLE(tmp, list, entry_link, ttmp) {
402 			data = (struct shm_data*)tmp->value;
403 			/* Inform daemon that we are attached. */
404 
405 			if (data->type == UNDOGET) {
406 				continue;
407 			}
408 
409 			msg.shmid = data->shmid;
410 			msg.shmaddr = data->internal;
411 			msg.shmflg = 0; /* This is enough at this moment. */
412 			msg.size = data->size;
413 			/* Last field is not necessary because it is used only
414 			 * for undo segments.
415 			 */
416 
417 			send_message(daemon_fd, SHMAT, (char *)&msg, sizeof(msg));
418 			receive_message(daemon_fd, (char *)&error, sizeof(error));
419 
420 			/* If the daemon returned error munmap the region. */
421 			if (error) {
422 				errno = error;
423 				_shmdt(data->internal, 0);
424 				shmremove(data->shmid);
425 				sysv_print_err(" %d shmchild\n", error);
426 				sleep(20);
427 			}
428 
429 		}
430 	}
431 
432 	/* Remove semundo structures. Those are specific only for the parent.
433 	 * The child must create for itself a new one.
434 	 */
435 	data = _hash_remove(shmaddrs, (u_long)undos);
436 	if (undos) {
437 		munmap(undos, round_page(data->size));
438 		undos = NULL;
439 	}
440 }
441 
442 /* Called each time a thread tries to access the sem/msg.
443  * It is used in order to protect data against its removal
444  * by another thread.
445  */
446 struct shm_data*
447 get_shmdata(int id, int to_remove, int shm_access) {
448 	struct shm_data *data = NULL;
449 
450 	SYSV_MUTEX_LOCK(&lock_resources);
451 	if (!is_shm_started()) {
452 		errno = EINVAL;
453 		goto done;
454 	}
455 
456 	data = _hash_lookup(shmres, id);
457 	if (!data) {
458 		errno = EINVAL;
459 		goto done;
460 	}
461 
462 	/* If segment was removed by another thread we can't use it. */
463 	if (data->removed) {
464 		sysv_print("segment already removed\n");
465 		errno = EINVAL;
466 		data = NULL;
467 		goto done;
468 	}
469 
470 	/* Mark for removal. Inform the other threads from the
471 	 * same address space. */
472 	if (to_remove) {
473 		sysv_print("segment is removed\n");
474 		data->removed = to_remove; /* 1 if it is removed by
475 		the current process and 2 if it was removed by
476 		another one. */
477 
478 		/* No need for any rights check because this is
479 		 * done by daemon if this is the process that removes
480 		 * the sem/msg.
481 		 * If not, there is no need for any right to clean
482 		 * internal resources.
483 		 */
484 		goto done2;
485 	}
486 
487 	/* Avoid segmentation fault if the memory zone
488 	 * is accessed without necessary permissions
489 	 * (it was mapped according to them).
490 	 */
491 	if (!(data->access & shm_access)) {
492 #if 0
493 		sysv_print("no access rights has %o and wants %o\n",
494 				data->access, shm_access);
495 		errno = EACCES;
496 		data = NULL;
497 		goto done;
498 #endif
499 	}
500 
501 done2:
502 	data->used++;
503 done:
504 	SYSV_MUTEX_UNLOCK(&lock_resources);
505 	return (data);
506 }
507 
508 /* Set the shm_access type (IPC_R, IPC_W) for sem/msg. */
509 int
510 set_shmdata_access(int id, int shm_access) {
511 	struct shm_data *data;
512 	int ret = -1;
513 
514 	SYSV_MUTEX_LOCK(&lock_resources);
515 	if (!is_shm_started()) {
516 		errno = EINVAL;
517 		goto done;
518 	}
519 
520 	data = _hash_lookup(shmres, id);
521 	if (!data) {
522 		errno = EINVAL;
523 		goto done;
524 	}
525 
526 	/* If segment was removed by another thread we can't use it. */
527 	if (data->removed) {
528 		errno = EINVAL;
529 		goto done;
530 	}
531 
532 	data->access = shm_access;
533 	ret = 0;
534 done:
535 	SYSV_MUTEX_UNLOCK(&lock_resources);
536 
537 	return (ret);
538 }
539