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