shm.c revision 269847
1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "apr_arch_shm.h" 18 19#include "apr_general.h" 20#include "apr_errno.h" 21#include "apr_user.h" 22#include "apr_strings.h" 23#include "apr_hash.h" 24 25#if APR_USE_SHMEM_MMAP_SHM 26/* 27 * For portable use, a shared memory object should be identified by a name of 28 * the form /somename; that is, a null-terminated string of up to NAME_MAX 29 * (i.e., 255) characters consisting of an initial slash, followed by one or 30 * more characters, none of which are slashes. 31 */ 32#ifndef NAME_MAX 33#define NAME_MAX 255 34#endif 35 36/* See proc_mutex.c and sem_open for the reason for all this! */ 37static unsigned int rshash (const char *p) { 38 /* hash function from Robert Sedgwicks 'Algorithms in C' book */ 39 unsigned int b = 378551; 40 unsigned int a = 63689; 41 unsigned int retval = 0; 42 43 for( ; *p; p++) { 44 retval = retval * a + (*p); 45 a *= b; 46 } 47 48 return retval; 49} 50 51static const char *make_shm_open_safe_name(const char *filename, 52 apr_pool_t *pool) 53{ 54 apr_ssize_t flen; 55 unsigned int h1, h2; 56 57 if (filename == NULL) { 58 return NULL; 59 } 60 61 flen = strlen(filename); 62 h1 = (apr_hashfunc_default(filename, &flen) & 0xffffffff); 63 h2 = (rshash(filename) & 0xffffffff); 64 return apr_psprintf(pool, "/ShM.%xH%x", h1, h2); 65 66} 67#endif 68 69#if APR_USE_SHMEM_SHMGET 70static key_t our_ftok(const char *filename) 71{ 72 /* to help avoid collisions while still using 73 * an easily recreated proj_id */ 74 apr_ssize_t slen = strlen(filename); 75 return ftok(filename, 76 (int)apr_hashfunc_default(filename, &slen)); 77} 78#endif 79 80static apr_status_t shm_cleanup_owner(void *m_) 81{ 82 apr_shm_t *m = (apr_shm_t *)m_; 83 84 /* anonymous shared memory */ 85 if (m->filename == NULL) { 86#if APR_USE_SHMEM_MMAP_ZERO || APR_USE_SHMEM_MMAP_ANON 87 if (munmap(m->base, m->realsize) == -1) { 88 return errno; 89 } 90 return APR_SUCCESS; 91#elif APR_USE_SHMEM_SHMGET_ANON 92 if (shmdt(m->base) == -1) { 93 return errno; 94 } 95 /* This segment will automatically remove itself after all 96 * references have detached. */ 97 return APR_SUCCESS; 98#endif 99 } 100 101 /* name-based shared memory */ 102 else { 103#if APR_USE_SHMEM_MMAP_TMP 104 if (munmap(m->base, m->realsize) == -1) { 105 return errno; 106 } 107 if (access(m->filename, F_OK)) { 108 return APR_SUCCESS; 109 } 110 else { 111 return apr_file_remove(m->filename, m->pool); 112 } 113#elif APR_USE_SHMEM_MMAP_SHM 114 if (munmap(m->base, m->realsize) == -1) { 115 return errno; 116 } 117 if (shm_unlink(make_shm_open_safe_name(m->filename, m->pool)) == -1 && errno != ENOENT) { 118 return errno; 119 } 120 return APR_SUCCESS; 121#elif APR_USE_SHMEM_SHMGET 122 /* Indicate that the segment is to be destroyed as soon 123 * as all processes have detached. This also disallows any 124 * new attachments to the segment. */ 125 if (shmctl(m->shmid, IPC_RMID, NULL) == -1 && errno != EINVAL) { 126 return errno; 127 } 128 if (shmdt(m->base) == -1) { 129 return errno; 130 } 131 if (access(m->filename, F_OK)) { 132 return APR_SUCCESS; 133 } 134 else { 135 return apr_file_remove(m->filename, m->pool); 136 } 137#else 138 return APR_ENOTIMPL; 139#endif 140 } 141} 142 143APR_DECLARE(apr_status_t) apr_shm_create(apr_shm_t **m, 144 apr_size_t reqsize, 145 const char *filename, 146 apr_pool_t *pool) 147{ 148 apr_shm_t *new_m; 149 apr_status_t status; 150#if APR_USE_SHMEM_SHMGET || APR_USE_SHMEM_SHMGET_ANON 151 struct shmid_ds shmbuf; 152 apr_uid_t uid; 153 apr_gid_t gid; 154#endif 155#if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM || \ 156 APR_USE_SHMEM_MMAP_ZERO 157 int tmpfd; 158#endif 159#if APR_USE_SHMEM_SHMGET 160 apr_size_t nbytes; 161 key_t shmkey; 162#endif 163#if APR_USE_SHMEM_MMAP_ZERO || APR_USE_SHMEM_SHMGET || \ 164 APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM 165 apr_file_t *file; /* file where metadata is stored */ 166#endif 167 168 /* Check if they want anonymous or name-based shared memory */ 169 if (filename == NULL) { 170#if APR_USE_SHMEM_MMAP_ZERO || APR_USE_SHMEM_MMAP_ANON 171 new_m = apr_palloc(pool, sizeof(apr_shm_t)); 172 new_m->pool = pool; 173 new_m->reqsize = reqsize; 174 new_m->realsize = reqsize + 175 APR_ALIGN_DEFAULT(sizeof(apr_size_t)); /* room for metadata */ 176 new_m->filename = NULL; 177 178#if APR_USE_SHMEM_MMAP_ZERO 179 status = apr_file_open(&file, "/dev/zero", APR_READ | APR_WRITE, 180 APR_OS_DEFAULT, pool); 181 if (status != APR_SUCCESS) { 182 return status; 183 } 184 status = apr_os_file_get(&tmpfd, file); 185 if (status != APR_SUCCESS) { 186 return status; 187 } 188 189 new_m->base = mmap(NULL, new_m->realsize, PROT_READ|PROT_WRITE, 190 MAP_SHARED, tmpfd, 0); 191 if (new_m->base == (void *)MAP_FAILED) { 192 return errno; 193 } 194 195 status = apr_file_close(file); 196 if (status != APR_SUCCESS) { 197 return status; 198 } 199 200 /* store the real size in the metadata */ 201 *(apr_size_t*)(new_m->base) = new_m->realsize; 202 /* metadata isn't usable */ 203 new_m->usable = (char *)new_m->base + APR_ALIGN_DEFAULT(sizeof(apr_size_t)); 204 205 apr_pool_cleanup_register(new_m->pool, new_m, shm_cleanup_owner, 206 apr_pool_cleanup_null); 207 *m = new_m; 208 return APR_SUCCESS; 209 210#elif APR_USE_SHMEM_MMAP_ANON 211 new_m->base = mmap(NULL, new_m->realsize, PROT_READ|PROT_WRITE, 212 MAP_ANON|MAP_SHARED, -1, 0); 213 if (new_m->base == (void *)MAP_FAILED) { 214 return errno; 215 } 216 217 /* store the real size in the metadata */ 218 *(apr_size_t*)(new_m->base) = new_m->realsize; 219 /* metadata isn't usable */ 220 new_m->usable = (char *)new_m->base + APR_ALIGN_DEFAULT(sizeof(apr_size_t)); 221 222 apr_pool_cleanup_register(new_m->pool, new_m, shm_cleanup_owner, 223 apr_pool_cleanup_null); 224 *m = new_m; 225 return APR_SUCCESS; 226 227#endif /* APR_USE_SHMEM_MMAP_ZERO */ 228#elif APR_USE_SHMEM_SHMGET_ANON 229 new_m = apr_palloc(pool, sizeof(apr_shm_t)); 230 new_m->pool = pool; 231 new_m->reqsize = reqsize; 232 new_m->realsize = reqsize; 233 new_m->filename = NULL; 234 235 if ((new_m->shmid = shmget(IPC_PRIVATE, new_m->realsize, 236 SHM_R | SHM_W | IPC_CREAT)) < 0) { 237 return errno; 238 } 239 240 if ((new_m->base = shmat(new_m->shmid, NULL, 0)) == (void *)-1) { 241 return errno; 242 } 243 new_m->usable = new_m->base; 244 245 if (shmctl(new_m->shmid, IPC_STAT, &shmbuf) == -1) { 246 return errno; 247 } 248 apr_uid_current(&uid, &gid, pool); 249 shmbuf.shm_perm.uid = uid; 250 shmbuf.shm_perm.gid = gid; 251 if (shmctl(new_m->shmid, IPC_SET, &shmbuf) == -1) { 252 return errno; 253 } 254 255 /* Remove the segment once use count hits zero. 256 * We will not attach to this segment again, since it is 257 * anonymous memory, so it is ok to mark it for deletion. 258 */ 259 if (shmctl(new_m->shmid, IPC_RMID, NULL) == -1) { 260 return errno; 261 } 262 263 apr_pool_cleanup_register(new_m->pool, new_m, shm_cleanup_owner, 264 apr_pool_cleanup_null); 265 *m = new_m; 266 return APR_SUCCESS; 267#else 268 /* It is an error if they want anonymous memory but we don't have it. */ 269 return APR_ENOTIMPL; /* requested anonymous but we don't have it */ 270#endif 271 } 272 273 /* Name-based shared memory */ 274 else { 275 new_m = apr_palloc(pool, sizeof(apr_shm_t)); 276 new_m->pool = pool; 277 new_m->reqsize = reqsize; 278 new_m->filename = apr_pstrdup(pool, filename); 279#if APR_USE_SHMEM_MMAP_SHM 280 const char *shm_name = make_shm_open_safe_name(filename, pool); 281#endif 282#if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM 283 new_m->realsize = reqsize + 284 APR_ALIGN_DEFAULT(sizeof(apr_size_t)); /* room for metadata */ 285 /* FIXME: Ignore error for now. * 286 * status = apr_file_remove(file, pool);*/ 287 status = APR_SUCCESS; 288 289#if APR_USE_SHMEM_MMAP_TMP 290 /* FIXME: Is APR_OS_DEFAULT sufficient? */ 291 status = apr_file_open(&file, filename, 292 APR_READ | APR_WRITE | APR_CREATE | APR_EXCL, 293 APR_OS_DEFAULT, pool); 294 if (status != APR_SUCCESS) { 295 return status; 296 } 297 298 status = apr_os_file_get(&tmpfd, file); 299 if (status != APR_SUCCESS) { 300 apr_file_close(file); /* ignore errors, we're failing */ 301 apr_file_remove(new_m->filename, new_m->pool); 302 return status; 303 } 304 305 status = apr_file_trunc(file, new_m->realsize); 306 if (status != APR_SUCCESS && status != APR_ESPIPE) { 307 apr_file_close(file); /* ignore errors, we're failing */ 308 apr_file_remove(new_m->filename, new_m->pool); 309 return status; 310 } 311 312 new_m->base = mmap(NULL, new_m->realsize, PROT_READ | PROT_WRITE, 313 MAP_SHARED, tmpfd, 0); 314 /* FIXME: check for errors */ 315 316 status = apr_file_close(file); 317 if (status != APR_SUCCESS) { 318 return status; 319 } 320#endif /* APR_USE_SHMEM_MMAP_TMP */ 321#if APR_USE_SHMEM_MMAP_SHM 322 /* FIXME: SysV uses 0600... should we? */ 323 tmpfd = shm_open(shm_name, O_RDWR | O_CREAT | O_EXCL, 0644); 324 if (tmpfd == -1) { 325 return errno; 326 } 327 328 status = apr_os_file_put(&file, &tmpfd, 329 APR_READ | APR_WRITE | APR_CREATE | APR_EXCL, 330 pool); 331 if (status != APR_SUCCESS) { 332 return status; 333 } 334 335 status = apr_file_trunc(file, new_m->realsize); 336 if (status != APR_SUCCESS && status != APR_ESPIPE) { 337 shm_unlink(shm_name); /* we're failing, remove the object */ 338 return status; 339 } 340 new_m->base = mmap(NULL, new_m->realsize, PROT_READ | PROT_WRITE, 341 MAP_SHARED, tmpfd, 0); 342 343 /* FIXME: check for errors */ 344 345 status = apr_file_close(file); 346 if (status != APR_SUCCESS) { 347 return status; 348 } 349#endif /* APR_USE_SHMEM_MMAP_SHM */ 350 351 /* store the real size in the metadata */ 352 *(apr_size_t*)(new_m->base) = new_m->realsize; 353 /* metadata isn't usable */ 354 new_m->usable = (char *)new_m->base + APR_ALIGN_DEFAULT(sizeof(apr_size_t)); 355 356 apr_pool_cleanup_register(new_m->pool, new_m, shm_cleanup_owner, 357 apr_pool_cleanup_null); 358 *m = new_m; 359 return APR_SUCCESS; 360 361#elif APR_USE_SHMEM_SHMGET 362 new_m->realsize = reqsize; 363 364 /* FIXME: APR_OS_DEFAULT is too permissive, switch to 600 I think. */ 365 status = apr_file_open(&file, filename, 366 APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_EXCL, 367 APR_OS_DEFAULT, pool); 368 if (status != APR_SUCCESS) { 369 return status; 370 } 371 372 /* ftok() (on solaris at least) requires that the file actually 373 * exist before calling ftok(). */ 374 shmkey = our_ftok(filename); 375 if (shmkey == (key_t)-1) { 376 apr_file_close(file); 377 return errno; 378 } 379 380 if ((new_m->shmid = shmget(shmkey, new_m->realsize, 381 SHM_R | SHM_W | IPC_CREAT | IPC_EXCL)) < 0) { 382 apr_file_close(file); 383 return errno; 384 } 385 386 if ((new_m->base = shmat(new_m->shmid, NULL, 0)) == (void *)-1) { 387 apr_file_close(file); 388 return errno; 389 } 390 new_m->usable = new_m->base; 391 392 if (shmctl(new_m->shmid, IPC_STAT, &shmbuf) == -1) { 393 apr_file_close(file); 394 return errno; 395 } 396 apr_uid_current(&uid, &gid, pool); 397 shmbuf.shm_perm.uid = uid; 398 shmbuf.shm_perm.gid = gid; 399 if (shmctl(new_m->shmid, IPC_SET, &shmbuf) == -1) { 400 apr_file_close(file); 401 return errno; 402 } 403 404 nbytes = sizeof(reqsize); 405 status = apr_file_write(file, (const void *)&reqsize, 406 &nbytes); 407 if (status != APR_SUCCESS) { 408 apr_file_close(file); 409 return status; 410 } 411 status = apr_file_close(file); 412 if (status != APR_SUCCESS) { 413 return status; 414 } 415 416 apr_pool_cleanup_register(new_m->pool, new_m, shm_cleanup_owner, 417 apr_pool_cleanup_null); 418 *m = new_m; 419 return APR_SUCCESS; 420 421#else 422 return APR_ENOTIMPL; 423#endif 424 } 425} 426 427APR_DECLARE(apr_status_t) apr_shm_create_ex(apr_shm_t **m, 428 apr_size_t reqsize, 429 const char *filename, 430 apr_pool_t *p, 431 apr_int32_t flags) 432{ 433 return apr_shm_create(m, reqsize, filename, p); 434} 435 436APR_DECLARE(apr_status_t) apr_shm_remove(const char *filename, 437 apr_pool_t *pool) 438{ 439#if APR_USE_SHMEM_SHMGET 440 apr_status_t status; 441 apr_file_t *file; 442 key_t shmkey; 443 int shmid; 444#endif 445 446#if APR_USE_SHMEM_MMAP_TMP 447 return apr_file_remove(filename, pool); 448#elif APR_USE_SHMEM_MMAP_SHM 449 const char *shm_name = make_shm_open_safe_name(filename, pool); 450 if (shm_unlink(shm_name) == -1) { 451 return errno; 452 } 453 return APR_SUCCESS; 454#elif APR_USE_SHMEM_SHMGET 455 /* Presume that the file already exists; just open for writing */ 456 status = apr_file_open(&file, filename, APR_FOPEN_WRITE, 457 APR_OS_DEFAULT, pool); 458 if (status) { 459 return status; 460 } 461 462 /* ftok() (on solaris at least) requires that the file actually 463 * exist before calling ftok(). */ 464 shmkey = our_ftok(filename); 465 if (shmkey == (key_t)-1) { 466 goto shm_remove_failed; 467 } 468 469 apr_file_close(file); 470 471 if ((shmid = shmget(shmkey, 0, SHM_R | SHM_W)) < 0) { 472 goto shm_remove_failed; 473 } 474 475 /* Indicate that the segment is to be destroyed as soon 476 * as all processes have detached. This also disallows any 477 * new attachments to the segment. */ 478 if (shmctl(shmid, IPC_RMID, NULL) == -1) { 479 goto shm_remove_failed; 480 } 481 return apr_file_remove(filename, pool); 482 483shm_remove_failed: 484 status = errno; 485 /* ensure the file has been removed anyway. */ 486 apr_file_remove(filename, pool); 487 return status; 488#else 489 490 /* No support for anonymous shm */ 491 return APR_ENOTIMPL; 492#endif 493} 494 495APR_DECLARE(apr_status_t) apr_shm_destroy(apr_shm_t *m) 496{ 497 return apr_pool_cleanup_run(m->pool, m, shm_cleanup_owner); 498} 499 500static apr_status_t shm_cleanup_attach(void *m_) 501{ 502 apr_shm_t *m = (apr_shm_t *)m_; 503 504 if (m->filename == NULL) { 505 /* It doesn't make sense to detach from an anonymous memory segment. */ 506 return APR_EINVAL; 507 } 508 else { 509#if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM 510 if (munmap(m->base, m->realsize) == -1) { 511 return errno; 512 } 513 return APR_SUCCESS; 514#elif APR_USE_SHMEM_SHMGET 515 if (shmdt(m->base) == -1) { 516 return errno; 517 } 518 return APR_SUCCESS; 519#else 520 return APR_ENOTIMPL; 521#endif 522 } 523} 524 525APR_DECLARE(apr_status_t) apr_shm_attach(apr_shm_t **m, 526 const char *filename, 527 apr_pool_t *pool) 528{ 529 if (filename == NULL) { 530 /* It doesn't make sense to attach to a segment if you don't know 531 * the filename. */ 532 return APR_EINVAL; 533 } 534 else { 535#if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM 536 apr_shm_t *new_m; 537 apr_status_t status; 538 int tmpfd; 539 apr_file_t *file; /* file where metadata is stored */ 540 apr_size_t nbytes; 541 542 new_m = apr_palloc(pool, sizeof(apr_shm_t)); 543 new_m->pool = pool; 544 new_m->filename = apr_pstrdup(pool, filename); 545#if APR_USE_SHMEM_MMAP_SHM 546 const char *shm_name = make_shm_open_safe_name(filename, pool); 547 548 /* FIXME: SysV uses 0600... should we? */ 549 tmpfd = shm_open(shm_name, O_RDWR, 0644); 550 if (tmpfd == -1) { 551 return errno; 552 } 553 554 status = apr_os_file_put(&file, &tmpfd, 555 APR_READ | APR_WRITE, 556 pool); 557 if (status != APR_SUCCESS) { 558 return status; 559 } 560 561#elif APR_USE_SHMEM_MMAP_TMP 562 status = apr_file_open(&file, filename, 563 APR_READ | APR_WRITE, 564 APR_OS_DEFAULT, pool); 565 if (status != APR_SUCCESS) { 566 return status; 567 } 568 status = apr_os_file_get(&tmpfd, file); 569 if (status != APR_SUCCESS) { 570 return status; 571 } 572#else 573 return APR_ENOTIMPL; 574#endif 575 576 nbytes = sizeof(new_m->realsize); 577 status = apr_file_read(file, (void *)&(new_m->realsize), 578 &nbytes); 579 if (status != APR_SUCCESS) { 580 return status; 581 } 582 583 status = apr_os_file_get(&tmpfd, file); 584 if (status != APR_SUCCESS) { 585 apr_file_close(file); /* ignore errors, we're failing */ 586 apr_file_remove(new_m->filename, new_m->pool); 587 return status; 588 } 589 590 new_m->reqsize = new_m->realsize - sizeof(apr_size_t); 591 592 new_m->base = mmap(NULL, new_m->realsize, PROT_READ | PROT_WRITE, 593 MAP_SHARED, tmpfd, 0); 594 /* FIXME: check for errors */ 595 596 status = apr_file_close(file); 597 if (status != APR_SUCCESS) { 598 return status; 599 } 600 601 /* metadata isn't part of the usable segment */ 602 new_m->usable = (char *)new_m->base + APR_ALIGN_DEFAULT(sizeof(apr_size_t)); 603 604 apr_pool_cleanup_register(new_m->pool, new_m, shm_cleanup_attach, 605 apr_pool_cleanup_null); 606 *m = new_m; 607 return APR_SUCCESS; 608 609#elif APR_USE_SHMEM_SHMGET 610 apr_shm_t *new_m; 611 apr_status_t status; 612 apr_file_t *file; /* file where metadata is stored */ 613 apr_size_t nbytes; 614 key_t shmkey; 615 616 new_m = apr_palloc(pool, sizeof(apr_shm_t)); 617 618 status = apr_file_open(&file, filename, 619 APR_FOPEN_READ, APR_OS_DEFAULT, pool); 620 if (status != APR_SUCCESS) { 621 return status; 622 } 623 624 nbytes = sizeof(new_m->reqsize); 625 status = apr_file_read(file, (void *)&(new_m->reqsize), 626 &nbytes); 627 if (status != APR_SUCCESS) { 628 return status; 629 } 630 status = apr_file_close(file); 631 if (status != APR_SUCCESS) { 632 return status; 633 } 634 635 new_m->filename = apr_pstrdup(pool, filename); 636 new_m->pool = pool; 637 shmkey = our_ftok(filename); 638 if (shmkey == (key_t)-1) { 639 return errno; 640 } 641 if ((new_m->shmid = shmget(shmkey, 0, SHM_R | SHM_W)) == -1) { 642 return errno; 643 } 644 if ((new_m->base = shmat(new_m->shmid, NULL, 0)) == (void *)-1) { 645 return errno; 646 } 647 new_m->usable = new_m->base; 648 new_m->realsize = new_m->reqsize; 649 650 apr_pool_cleanup_register(new_m->pool, new_m, shm_cleanup_attach, 651 apr_pool_cleanup_null); 652 *m = new_m; 653 return APR_SUCCESS; 654 655#else 656 return APR_ENOTIMPL; 657#endif 658 } 659} 660 661APR_DECLARE(apr_status_t) apr_shm_attach_ex(apr_shm_t **m, 662 const char *filename, 663 apr_pool_t *pool, 664 apr_int32_t flags) 665{ 666 return apr_shm_attach(m, filename, pool); 667} 668 669APR_DECLARE(apr_status_t) apr_shm_detach(apr_shm_t *m) 670{ 671 apr_status_t rv = shm_cleanup_attach(m); 672 apr_pool_cleanup_kill(m->pool, m, shm_cleanup_attach); 673 return rv; 674} 675 676APR_DECLARE(void *) apr_shm_baseaddr_get(const apr_shm_t *m) 677{ 678 return m->usable; 679} 680 681APR_DECLARE(apr_size_t) apr_shm_size_get(const apr_shm_t *m) 682{ 683 return m->reqsize; 684} 685 686APR_POOL_IMPLEMENT_ACCESSOR(shm) 687 688APR_DECLARE(apr_status_t) apr_os_shm_get(apr_os_shm_t *osshm, 689 apr_shm_t *shm) 690{ 691 return APR_ENOTIMPL; 692} 693 694APR_DECLARE(apr_status_t) apr_os_shm_put(apr_shm_t **m, 695 apr_os_shm_t *osshm, 696 apr_pool_t *pool) 697{ 698 return APR_ENOTIMPL; 699} 700 701