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