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