xref: /dragonfly/lib/libc/sysvipc/shm.c (revision 16dd80e4)
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 {
67 	if (shmres) {
68 		errno = EPERM;
69 		return (-1);
70 	}
71 
72 	shmres = _hash_init(MAXSIZE);
73 	if (!shmres)
74 		goto out_resources;
75 
76 	shmaddrs = _hash_init(MAXSIZE);
77 	if (!shmaddrs)
78 		goto out_addrs;
79 
80 	return 0;
81 
82 out_addrs:
83 	_hash_destroy(shmres);
84 out_resources:
85 	return -1;
86 }
87 
88 /*static int
89 shmexit(void)
90 {
91 	if (!shmres)
92 		return -EPERM;
93 
94 	_hash_destroy(shmres);
95 	_hash_destroy(shmaddrs);
96 	SYSV_MUTEX_DESTROY(lock_resources);
97 
98 	return 0;
99 }*/
100 
101 /* Init sysv ipc resources and those used for shared memory. */
102 static int
103 shmcheck(void)
104 {
105 	int ret;
106 
107 	/* Init sysv resources. */
108 	if ((ret = sysvinit()) != 0)
109 		return (ret);
110 	/* Init resorces used for shared memory. */
111 	if ((ret = shminit()) < 0)
112 		return (ret);
113 	return (0);
114 }
115 
116 /* Check if sysv ipc resources are initialized. */
117 static int
118 is_shm_started(void)
119 {
120 	if (!is_sysvinit())
121 		return (0);
122 	if (!shmres)
123 		return (0);
124 	return (1);
125 }
126 
127 /* OBS: It is used only a rwlock for both hashtables and
128  * socket. I've made that choice because is I considered to
129  * be much expensive to acquire/release more than one especially
130  * as the daemon is not multithreading.
131  */
132 
133 /* This function has another parameter apart from shmget.
134  * The parameter has information about the type of sysv
135  * ipc resource (shm, sem, msg, undo).
136  * The undo segment is used for sem ops with UNDO flag set.
137  */
138 int
139 _shmget(key_t key, size_t size, int shmflg, int type)
140 {
141 	struct shmget_msg msg;
142 	struct shm_data *data;
143 	int shmid, fd;
144 	int flags;
145 
146 	SYSV_MUTEX_LOCK(&lock_resources);
147 	if (shmcheck() < 0) {
148 		sysv_print_err("init sysv ipc\n");
149 		goto done;
150 	}
151 
152 	msg.key = key;
153 	msg.size = size;
154 	msg.shmflg = shmflg;
155 	msg.type = type;
156 
157 	send_message(daemon_fd, type, (char *)&msg, sizeof(msg));
158 
159 	/* Accept a file installed by the daemon.
160 	 * The file is used as shared memory. */
161 	fd = receive_fd(daemon_fd);
162 	if (fd < 0) {
163 		shmid = -1;
164 		goto done;
165 	}
166 
167 	flags = _fcntl(fd, F_GETFD, 0);
168 	if (_fcntl(fd, F_SETFD, flags & FD_CLOEXEC) == -1) {
169 		sysv_print_err("fcntl error\n");
170 		shmid = -1;
171 		goto done;
172 	}
173 
174 	/* Receive the resource id or error. */
175 	receive_message(daemon_fd, (char *)&shmid, sizeof(shmid));
176 
177 	if (shmid < 0) {
178 		errno = -shmid;
179 		shmid = -1;
180 		goto done;
181 	}
182 
183 	/* Do we already have an entry for this resource? */
184 	data = _hash_lookup(shmres, shmid);
185 	if (data)
186 		goto done;
187 
188 	/* If not, add necessary data about it. */
189 	data = malloc(sizeof(struct shm_data));
190 	data->fd = fd;
191 	data->size = size;
192 	data->shmid = shmid;
193 	data->type = type;
194 	data->used = 0;
195 	data->removed = 0;
196 	data->access = 0; /* Used for sems. */
197 
198 	/* Insert data in hashtable using the shmid. */
199 	_hash_insert(shmres, shmid, data);
200 done:
201 	SYSV_MUTEX_UNLOCK(&lock_resources);
202 	return (shmid);
203 }
204 
205 int
206 sysvipc_shmget(key_t key, size_t size, int shmflg)
207 {
208 	return (_shmget(key, size, shmflg, SHMGET));
209 }
210 
211 void *
212 sysvipc_shmat(int shmid, const void *shmaddr, int shmflg)
213 {
214 	struct shmat_msg msg;
215 	void *addr = NULL;
216 	int error;
217 	int flags, prot;
218 	size_t size;
219 	struct shm_data *data;
220 
221 	SYSV_MUTEX_LOCK(&lock_resources);
222 	if (!is_shm_started()) {
223 		errno = EINVAL;
224 		goto done;
225 	}
226 
227 	/* Get data using shmid. */
228 	data = _hash_lookup(shmres, shmid);
229 	if (data == NULL) {
230 		errno = EINVAL;
231 		goto done;
232 	}
233 
234 	size = round_page(data->size);
235 
236 #ifdef VM_PROT_READ_IS_EXEC
237 	prot = PROT_READ | PROT_EXECUTE;
238 #else
239 	prot = PROT_READ;
240 #endif
241 	if ((shmflg & SHM_RDONLY) == 0)
242 		prot |= PROT_WRITE;
243 
244 	flags = MAP_SHARED;
245 	if (shmaddr) {
246 		if (shmflg & SHM_RND) {
247 			addr = (void *)((vm_offset_t)shmaddr & ~(SHMLBA-1));
248 		} else if (((vm_offset_t)shmaddr & (SHMLBA-1)) == 0) {
249 			addr = __DECONST(void *, shmaddr);
250 		} else {
251 			errno = EINVAL;
252 			goto done;
253 		}
254 	}
255 
256 	msg.shmid = shmid;
257 	msg.shmaddr = shmaddr;
258 	msg.shmflg = shmflg;
259 	msg.size = data->size; /* For undo segment. */
260 
261 	send_message(daemon_fd, SHMAT, (char *)&msg, sizeof(msg));
262 	receive_message(daemon_fd, (char *)&error, sizeof(error));
263 	if (error) {
264 		errno = error;
265 		goto done;
266 	}
267 
268 	addr = mmap(addr, size, prot, flags, data->fd, 0);
269 	if (!addr) {
270 		sysv_print_err("mmap\n");
271 		/* Detach ourselves from the segment. */
272 		send_message(daemon_fd, SHMDT, (char *)&shmid, sizeof(shmid));
273 		goto done;
274 	}
275 
276 	/* Necessary for SEMGET, MSGGET, UNDOGET. */
277 	data->internal = addr;
278 
279 	/* Save the mapped address for munmap call. */
280 	_hash_insert(shmaddrs, (u_long)addr, data);
281 done:
282 	SYSV_MUTEX_UNLOCK(&lock_resources);
283 	return (addr);
284 }
285 
286 /* Remove a sysv ipc resource. */
287 static
288 void shmremove(int shmid)
289 {
290 	struct shm_data *data;
291 	data = _hash_remove(shmres, shmid);
292 
293 	//TODO nu trebuie demapat?
294 	_close(data->fd);
295 	free(data);
296 	data = NULL;
297 }
298 
299 int
300 sysvipc_shmctl(int shmid, int cmd, struct shmid_ds *buf)
301 {
302 	int size, ret;
303 	struct shmctl_msg *msg;
304 
305 /*	if (cmd == IPC_SET)
306 		size = sizeof(struct shmctl_msg) + sizeof(struct shmid_ds);
307 	else
308 		size = sizeof(struct shmctl_msg);
309 */
310 	SYSV_MUTEX_LOCK(&lock_resources);
311 
312 	ret = -1;
313 
314 	if (!is_shm_started()) {
315 		errno = EINVAL;
316 		goto done;
317 	}
318 
319 	size = sizeof(struct shmctl_msg);
320 	msg = malloc(size);
321 	msg->shmid = shmid;
322 	msg->cmd = cmd;
323 
324 	if (cmd == IPC_SET)
325 		msg->buf = *buf;
326 
327 	send_message(daemon_fd, SHMCTL, (char *)msg, sizeof(*msg));
328 
329 	receive_message(daemon_fd, (char *)&ret, sizeof(ret));
330 
331 	/* Get data in IPC_STAT case. */
332 	if (ret == 0 && cmd == IPC_STAT)
333 		receive_message(daemon_fd, (char *)buf, sizeof(*buf));
334 
335 	/* Free all resources specific to a shmid in IPC_RMID case. */
336 	if (ret == 0 && cmd == IPC_RMID)
337 		shmremove(shmid);
338 
339 	errno = ret;
340 done:
341 	SYSV_MUTEX_UNLOCK(&lock_resources);
342 	return (ret == 0 ? 0 : -1);
343 }
344 
345 /* Functionality of shmdt with the possibility to inform or not
346  * the daemon.
347  * Inform the daemon when shmdt is called and not when an error
348  * occurs and the daemon doesn't know that the process is attaced.
349  */
350 static int
351 _shmdt(const void *shmaddr, int send_to_daemon)
352 {
353 	int ret;
354 	size_t size;
355 	struct shm_data *data;
356 
357 	ret = -1;
358 
359 	SYSV_MUTEX_LOCK(&lock_resources);
360 	if (!is_shm_started()) {
361 		errno = EINVAL;
362 		goto done;
363 	}
364 
365 	/* Verify if shmaddr was returned from a shmat call. */
366 	data = _hash_remove(shmaddrs, (u_long)shmaddr);
367 	if (data == NULL) {
368 		errno = EINVAL;
369 		goto done;
370 	}
371 
372 	size = round_page(data->size);
373 
374 	ret = munmap(__DECONST(void *, shmaddr), size);
375 	if (ret)
376 		goto done;
377 
378 	if (send_to_daemon)
379 		send_message(daemon_fd, SHMDT, (char *)&data->shmid, sizeof(int));
380 
381 	shmaddr = NULL;
382 	free(data);
383 	data = NULL;
384 done:
385 	SYSV_MUTEX_UNLOCK(&lock_resources);
386 	return (ret);
387 }
388 
389 int
390 sysvipc_shmdt(const void *shmaddr)
391 {
392 	return (_shmdt(shmaddr, 1));
393 }
394 
395 void
396 shmchild(void)
397 {
398 	int i;
399 	struct entries_list *list;
400 	struct hashentry *tmp, *ttmp;
401 	struct shmat_msg msg;
402 	struct shm_data *data;
403 	int error;
404 
405 /* OBS: no locking is necessary because this function is called
406  * after the child is created and at that moment only one thread
407  * exists in the process.
408  */
409 	for (i=0; i<get_hash_size(MAXSIZE); i++) {
410 		list = &shmaddrs->entries[i];
411 		if (LIST_EMPTY(list))
412 			continue;
413 		LIST_FOREACH_MUTABLE(tmp, list, entry_link, ttmp) {
414 			data = (struct shm_data*)tmp->value;
415 			/* Inform daemon that we are attached. */
416 
417 			if (data->type == UNDOGET) {
418 				continue;
419 			}
420 
421 			msg.shmid = data->shmid;
422 			msg.shmaddr = data->internal;
423 			msg.shmflg = 0; /* This is enough at this moment. */
424 			msg.size = data->size;
425 			/* Last field is not necessary because it is used only
426 			 * for undo segments.
427 			 */
428 
429 			send_message(daemon_fd, SHMAT, (char *)&msg, sizeof(msg));
430 			receive_message(daemon_fd, (char *)&error, sizeof(error));
431 
432 			/* If the daemon returned error munmap the region. */
433 			if (error) {
434 				errno = error;
435 				_shmdt(data->internal, 0);
436 				shmremove(data->shmid);
437 				sysv_print_err(" %d shmchild\n", error);
438 				sleep(20);
439 			}
440 
441 		}
442 	}
443 
444 	/* Remove semundo structures. Those are specific only for the parent.
445 	 * The child must create for itself a new one.
446 	 */
447 	data = _hash_remove(shmaddrs, (u_long)undos);
448 	if (undos) {
449 		munmap(undos, round_page(data->size));
450 		undos = NULL;
451 	}
452 }
453 
454 /* Called each time a thread tries to access the sem/msg.
455  * It is used in order to protect data against its removal
456  * by another thread.
457  */
458 struct shm_data *
459 get_shmdata(int id, int to_remove, int shm_access)
460 {
461 	struct shm_data *data = NULL;
462 
463 	SYSV_MUTEX_LOCK(&lock_resources);
464 	if (!is_shm_started()) {
465 		errno = EINVAL;
466 		goto done;
467 	}
468 
469 	data = _hash_lookup(shmres, id);
470 	if (!data) {
471 		errno = EINVAL;
472 		goto done;
473 	}
474 
475 	/* If segment was removed by another thread we can't use it. */
476 	if (data->removed) {
477 		sysv_print("segment already removed\n");
478 		errno = EINVAL;
479 		data = NULL;
480 		goto done;
481 	}
482 
483 	/* Mark for removal. Inform the other threads from the
484 	 * same address space. */
485 	if (to_remove) {
486 		sysv_print("segment is removed\n");
487 		data->removed = to_remove; /* 1 if it is removed by
488 		the current process and 2 if it was removed by
489 		another one. */
490 
491 		/* No need for any rights check because this is
492 		 * done by daemon if this is the process that removes
493 		 * the sem/msg.
494 		 * If not, there is no need for any right to clean
495 		 * internal resources.
496 		 */
497 		goto done2;
498 	}
499 
500 	/* Avoid segmentation fault if the memory zone
501 	 * is accessed without necessary permissions
502 	 * (it was mapped according to them).
503 	 */
504 	if (!(data->access & shm_access)) {
505 #if 0
506 		sysv_print("no access rights has %o and wants %o\n",
507 				data->access, shm_access);
508 		errno = EACCES;
509 		data = NULL;
510 		goto done;
511 #endif
512 	}
513 
514 done2:
515 	data->used++;
516 done:
517 	SYSV_MUTEX_UNLOCK(&lock_resources);
518 	return (data);
519 }
520 
521 /* Set the shm_access type (IPC_R, IPC_W) for sem/msg. */
522 int
523 set_shmdata_access(int id, int shm_access)
524 {
525 	struct shm_data *data;
526 	int ret = -1;
527 
528 	SYSV_MUTEX_LOCK(&lock_resources);
529 	if (!is_shm_started()) {
530 		errno = EINVAL;
531 		goto done;
532 	}
533 
534 	data = _hash_lookup(shmres, id);
535 	if (!data) {
536 		errno = EINVAL;
537 		goto done;
538 	}
539 
540 	/* If segment was removed by another thread we can't use it. */
541 	if (data->removed) {
542 		errno = EINVAL;
543 		goto done;
544 	}
545 
546 	data->access = shm_access;
547 	ret = 0;
548 done:
549 	SYSV_MUTEX_UNLOCK(&lock_resources);
550 
551 	return (ret);
552 }
553