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