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 
shm_cleanup_owner(void * m_)24 static apr_status_t shm_cleanup_owner(void *m_)
25 {
26     apr_shm_t *m = (apr_shm_t *)m_;
27 
28     /* anonymous shared memory */
29     if (m->filename == NULL) {
30 #if APR_USE_SHMEM_MMAP_ZERO || APR_USE_SHMEM_MMAP_ANON
31         if (munmap(m->base, m->realsize) == -1) {
32             return errno;
33         }
34         return APR_SUCCESS;
35 #endif
36 #if APR_USE_SHMEM_SHMGET_ANON
37         if (shmdt(m->base) == -1) {
38             return errno;
39         }
40         /* This segment will automatically remove itself after all
41          * references have detached. */
42         return APR_SUCCESS;
43 #endif
44     }
45 
46     /* name-based shared memory */
47     else {
48 #if APR_USE_SHMEM_MMAP_TMP
49         if (munmap(m->base, m->realsize) == -1) {
50             return errno;
51         }
52         return apr_file_remove(m->filename, m->pool);
53 #endif
54 #if APR_USE_SHMEM_MMAP_SHM
55         if (munmap(m->base, m->realsize) == -1) {
56             return errno;
57         }
58         if (shm_unlink(m->filename) == -1) {
59             return errno;
60         }
61         return APR_SUCCESS;
62 #endif
63 #if APR_USE_SHMEM_SHMGET
64         /* Indicate that the segment is to be destroyed as soon
65          * as all processes have detached. This also disallows any
66          * new attachments to the segment. */
67         if (shmctl(m->shmid, IPC_RMID, NULL) == -1) {
68             return errno;
69         }
70         if (shmdt(m->base) == -1) {
71             return errno;
72         }
73         return apr_file_remove(m->filename, m->pool);
74 #endif
75     }
76 
77     return APR_ENOTIMPL;
78 }
79 
apr_shm_create(apr_shm_t ** m,apr_size_t reqsize,const char * filename,apr_pool_t * pool)80 APR_DECLARE(apr_status_t) apr_shm_create(apr_shm_t **m,
81                                          apr_size_t reqsize,
82                                          const char *filename,
83                                          apr_pool_t *pool)
84 {
85     apr_shm_t *new_m;
86     apr_status_t status;
87 #if APR_USE_SHMEM_SHMGET || APR_USE_SHMEM_SHMGET_ANON
88     struct shmid_ds shmbuf;
89     apr_uid_t uid;
90     apr_gid_t gid;
91 #endif
92 #if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM || \
93     APR_USE_SHMEM_MMAP_ZERO
94     int tmpfd;
95 #endif
96 #if APR_USE_SHMEM_SHMGET
97     apr_size_t nbytes;
98     key_t shmkey;
99 #endif
100 #if APR_USE_SHMEM_MMAP_ZERO || APR_USE_SHMEM_SHMGET || \
101     APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM
102     apr_file_t *file;   /* file where metadata is stored */
103 #endif
104 
105     /* Check if they want anonymous or name-based shared memory */
106     if (filename == NULL) {
107 #if APR_USE_SHMEM_MMAP_ZERO || APR_USE_SHMEM_MMAP_ANON
108         new_m = apr_palloc(pool, sizeof(apr_shm_t));
109         new_m->pool = pool;
110         new_m->reqsize = reqsize;
111         new_m->realsize = reqsize +
112             APR_ALIGN_DEFAULT(sizeof(apr_size_t)); /* room for metadata */
113         new_m->filename = NULL;
114 
115 #if APR_USE_SHMEM_MMAP_ZERO
116         status = apr_file_open(&file, "/dev/zero", APR_READ | APR_WRITE,
117                                APR_OS_DEFAULT, pool);
118         if (status != APR_SUCCESS) {
119             return status;
120         }
121         status = apr_os_file_get(&tmpfd, file);
122         if (status != APR_SUCCESS) {
123             return status;
124         }
125 
126         new_m->base = mmap(NULL, new_m->realsize, PROT_READ|PROT_WRITE,
127                            MAP_SHARED, tmpfd, 0);
128         if (new_m->base == (void *)MAP_FAILED) {
129             return errno;
130         }
131 
132         status = apr_file_close(file);
133         if (status != APR_SUCCESS) {
134             return status;
135         }
136 
137         /* store the real size in the metadata */
138         *(apr_size_t*)(new_m->base) = new_m->realsize;
139         /* metadata isn't usable */
140         new_m->usable = (char *)new_m->base + APR_ALIGN_DEFAULT(sizeof(apr_size_t));
141 
142         apr_pool_cleanup_register(new_m->pool, new_m, shm_cleanup_owner,
143                                   apr_pool_cleanup_null);
144         *m = new_m;
145         return APR_SUCCESS;
146 
147 #elif APR_USE_SHMEM_MMAP_ANON
148         new_m->base = mmap(NULL, new_m->realsize, PROT_READ|PROT_WRITE,
149                            MAP_ANON|MAP_SHARED, -1, 0);
150         if (new_m->base == (void *)MAP_FAILED) {
151             return errno;
152         }
153 
154         /* store the real size in the metadata */
155         *(apr_size_t*)(new_m->base) = new_m->realsize;
156         /* metadata isn't usable */
157         new_m->usable = (char *)new_m->base + APR_ALIGN_DEFAULT(sizeof(apr_size_t));
158 
159         apr_pool_cleanup_register(new_m->pool, new_m, shm_cleanup_owner,
160                                   apr_pool_cleanup_null);
161         *m = new_m;
162         return APR_SUCCESS;
163 
164 #endif /* APR_USE_SHMEM_MMAP_ZERO */
165 #endif /* APR_USE_SHMEM_MMAP_ZERO || APR_USE_SHMEM_MMAP_ANON */
166 #if APR_USE_SHMEM_SHMGET_ANON
167 
168         new_m = apr_palloc(pool, sizeof(apr_shm_t));
169         new_m->pool = pool;
170         new_m->reqsize = reqsize;
171         new_m->realsize = reqsize;
172         new_m->filename = NULL;
173 
174         if ((new_m->shmid = shmget(IPC_PRIVATE, new_m->realsize,
175                                    SHM_R | SHM_W | IPC_CREAT)) < 0) {
176             return errno;
177         }
178 
179         if ((new_m->base = shmat(new_m->shmid, NULL, 0)) == (void *)-1) {
180             return errno;
181         }
182         new_m->usable = new_m->base;
183 
184         if (shmctl(new_m->shmid, IPC_STAT, &shmbuf) == -1) {
185             return errno;
186         }
187         apr_uid_current(&uid, &gid, pool);
188         shmbuf.shm_perm.uid = uid;
189         shmbuf.shm_perm.gid = gid;
190         if (shmctl(new_m->shmid, IPC_SET, &shmbuf) == -1) {
191             return errno;
192         }
193 
194         /* Remove the segment once use count hits zero.
195          * We will not attach to this segment again, since it is
196          * anonymous memory, so it is ok to mark it for deletion.
197          */
198         if (shmctl(new_m->shmid, IPC_RMID, NULL) == -1) {
199             return errno;
200         }
201 
202         apr_pool_cleanup_register(new_m->pool, new_m, shm_cleanup_owner,
203                                   apr_pool_cleanup_null);
204         *m = new_m;
205         return APR_SUCCESS;
206 #endif /* APR_USE_SHMEM_SHMGET_ANON */
207         /* It is an error if they want anonymous memory but we don't have it. */
208         return APR_ENOTIMPL; /* requested anonymous but we don't have it */
209     }
210 
211     /* Name-based shared memory */
212     else {
213         new_m = apr_palloc(pool, sizeof(apr_shm_t));
214         new_m->pool = pool;
215         new_m->reqsize = reqsize;
216         new_m->filename = apr_pstrdup(pool, filename);
217 
218 #if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM
219         new_m->realsize = reqsize +
220             APR_ALIGN_DEFAULT(sizeof(apr_size_t)); /* room for metadata */
221         /* FIXME: Ignore error for now. *
222          * status = apr_file_remove(file, pool);*/
223         status = APR_SUCCESS;
224 
225 #if APR_USE_SHMEM_MMAP_TMP
226         /* FIXME: Is APR_OS_DEFAULT sufficient? */
227         status = apr_file_open(&file, filename,
228                                APR_READ | APR_WRITE | APR_CREATE | APR_EXCL,
229                                APR_OS_DEFAULT, pool);
230         if (status != APR_SUCCESS) {
231             return status;
232         }
233 
234         status = apr_os_file_get(&tmpfd, file);
235         if (status != APR_SUCCESS) {
236             apr_file_close(file); /* ignore errors, we're failing */
237             apr_file_remove(new_m->filename, new_m->pool);
238             return status;
239         }
240 
241         status = apr_file_trunc(file, new_m->realsize);
242         if (status != APR_SUCCESS) {
243             apr_file_close(file); /* ignore errors, we're failing */
244             apr_file_remove(new_m->filename, new_m->pool);
245             return status;
246         }
247 
248         new_m->base = mmap(NULL, new_m->realsize, PROT_READ | PROT_WRITE,
249                            MAP_SHARED, tmpfd, 0);
250         /* FIXME: check for errors */
251 
252         status = apr_file_close(file);
253         if (status != APR_SUCCESS) {
254             return status;
255         }
256 #endif /* APR_USE_SHMEM_MMAP_TMP */
257 #if APR_USE_SHMEM_MMAP_SHM
258         tmpfd = shm_open(filename, O_RDWR | O_CREAT | O_EXCL, 0644);
259         if (tmpfd == -1) {
260             return errno;
261         }
262 
263         status = apr_os_file_put(&file, &tmpfd,
264                                  APR_READ | APR_WRITE | APR_CREATE | APR_EXCL,
265                                  pool);
266         if (status != APR_SUCCESS) {
267             return status;
268         }
269 
270         status = apr_file_trunc(file, new_m->realsize);
271         if (status != APR_SUCCESS) {
272             shm_unlink(filename); /* we're failing, remove the object */
273             return status;
274         }
275         new_m->base = mmap(NULL, reqsize, PROT_READ | PROT_WRITE,
276                            MAP_SHARED, tmpfd, 0);
277 
278         /* FIXME: check for errors */
279 
280         status = apr_file_close(file);
281         if (status != APR_SUCCESS) {
282             return status;
283         }
284 #endif /* APR_USE_SHMEM_MMAP_SHM */
285 
286         /* store the real size in the metadata */
287         *(apr_size_t*)(new_m->base) = new_m->realsize;
288         /* metadata isn't usable */
289         new_m->usable = (char *)new_m->base + APR_ALIGN_DEFAULT(sizeof(apr_size_t));
290 
291         apr_pool_cleanup_register(new_m->pool, new_m, shm_cleanup_owner,
292                                   apr_pool_cleanup_null);
293         *m = new_m;
294         return APR_SUCCESS;
295 
296 #endif /* APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM */
297 
298 #if APR_USE_SHMEM_SHMGET
299         new_m->realsize = reqsize;
300 
301         /* FIXME: APR_OS_DEFAULT is too permissive, switch to 600 I think. */
302         status = apr_file_open(&file, filename,
303                                APR_WRITE | APR_CREATE | APR_EXCL,
304                                APR_OS_DEFAULT, pool);
305         if (status != APR_SUCCESS) {
306             return status;
307         }
308 
309         /* ftok() (on solaris at least) requires that the file actually
310          * exist before calling ftok(). */
311         shmkey = ftok(filename, 1);
312         if (shmkey == (key_t)-1) {
313             return errno;
314         }
315 
316         if ((new_m->shmid = shmget(shmkey, new_m->realsize,
317                                    SHM_R | SHM_W | IPC_CREAT | IPC_EXCL)) < 0) {
318             return errno;
319         }
320 
321         if ((new_m->base = shmat(new_m->shmid, NULL, 0)) == (void *)-1) {
322             return errno;
323         }
324         new_m->usable = new_m->base;
325 
326         if (shmctl(new_m->shmid, IPC_STAT, &shmbuf) == -1) {
327             return errno;
328         }
329         apr_uid_current(&uid, &gid, pool);
330         shmbuf.shm_perm.uid = uid;
331         shmbuf.shm_perm.gid = gid;
332         if (shmctl(new_m->shmid, IPC_SET, &shmbuf) == -1) {
333             return errno;
334         }
335 
336         nbytes = sizeof(reqsize);
337         status = apr_file_write(file, (const void *)&reqsize,
338                                 &nbytes);
339         if (status != APR_SUCCESS) {
340             return status;
341         }
342         status = apr_file_close(file);
343         if (status != APR_SUCCESS) {
344             return status;
345         }
346 
347         apr_pool_cleanup_register(new_m->pool, new_m, shm_cleanup_owner,
348                                   apr_pool_cleanup_null);
349         *m = new_m;
350         return APR_SUCCESS;
351 
352 #endif /* APR_USE_SHMEM_SHMGET */
353     }
354 
355     return APR_ENOTIMPL;
356 }
357 
apr_shm_remove(const char * filename,apr_pool_t * pool)358 APR_DECLARE(apr_status_t) apr_shm_remove(const char *filename,
359                                          apr_pool_t *pool)
360 {
361 #if APR_USE_SHMEM_SHMGET
362     apr_status_t status;
363     apr_file_t *file;
364     key_t shmkey;
365     int shmid;
366 #endif
367 
368 #if APR_USE_SHMEM_MMAP_TMP
369     return apr_file_remove(filename, pool);
370 #endif
371 #if APR_USE_SHMEM_MMAP_SHM
372     if (shm_unlink(filename) == -1) {
373         return errno;
374     }
375     return APR_SUCCESS;
376 #endif
377 #if APR_USE_SHMEM_SHMGET
378     /* Presume that the file already exists; just open for writing */
379     status = apr_file_open(&file, filename, APR_WRITE,
380                            APR_OS_DEFAULT, pool);
381     if (status) {
382         return status;
383     }
384 
385     /* ftok() (on solaris at least) requires that the file actually
386      * exist before calling ftok(). */
387     shmkey = ftok(filename, 1);
388     if (shmkey == (key_t)-1) {
389         goto shm_remove_failed;
390     }
391 
392     apr_file_close(file);
393 
394     if ((shmid = shmget(shmkey, 0, SHM_R | SHM_W)) < 0) {
395         goto shm_remove_failed;
396     }
397 
398     /* Indicate that the segment is to be destroyed as soon
399      * as all processes have detached. This also disallows any
400      * new attachments to the segment. */
401     if (shmctl(shmid, IPC_RMID, NULL) == -1) {
402         goto shm_remove_failed;
403     }
404     return apr_file_remove(filename, pool);
405 
406 shm_remove_failed:
407     status = errno;
408     /* ensure the file has been removed anyway. */
409     apr_file_remove(filename, pool);
410     return status;
411 #endif
412 
413     /* No support for anonymous shm */
414     return APR_ENOTIMPL;
415 }
416 
apr_shm_destroy(apr_shm_t * m)417 APR_DECLARE(apr_status_t) apr_shm_destroy(apr_shm_t *m)
418 {
419     return apr_pool_cleanup_run(m->pool, m, shm_cleanup_owner);
420 }
421 
shm_cleanup_attach(void * m_)422 static apr_status_t shm_cleanup_attach(void *m_)
423 {
424     apr_shm_t *m = (apr_shm_t *)m_;
425 
426     if (m->filename == NULL) {
427         /* It doesn't make sense to detach from an anonymous memory segment. */
428         return APR_EINVAL;
429     }
430     else {
431 #if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM
432         if (munmap(m->base, m->realsize) == -1) {
433             return errno;
434         }
435         return APR_SUCCESS;
436 #endif /* APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM */
437 #if APR_USE_SHMEM_SHMGET
438         if (shmdt(m->base) == -1) {
439             return errno;
440         }
441         return APR_SUCCESS;
442 #endif
443     }
444 
445     return APR_ENOTIMPL;
446 }
447 
apr_shm_attach(apr_shm_t ** m,const char * filename,apr_pool_t * pool)448 APR_DECLARE(apr_status_t) apr_shm_attach(apr_shm_t **m,
449                                          const char *filename,
450                                          apr_pool_t *pool)
451 {
452     if (filename == NULL) {
453         /* It doesn't make sense to attach to a segment if you don't know
454          * the filename. */
455         return APR_EINVAL;
456     }
457     else {
458 #if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM
459         apr_shm_t *new_m;
460         apr_status_t status;
461         int tmpfd;
462         apr_file_t *file;   /* file where metadata is stored */
463         apr_size_t nbytes;
464 
465         new_m = apr_palloc(pool, sizeof(apr_shm_t));
466         new_m->pool = pool;
467         new_m->filename = apr_pstrdup(pool, filename);
468 
469         status = apr_file_open(&file, filename,
470                                APR_READ | APR_WRITE,
471                                APR_OS_DEFAULT, pool);
472         if (status != APR_SUCCESS) {
473             return status;
474         }
475         status = apr_os_file_get(&tmpfd, file);
476         if (status != APR_SUCCESS) {
477             return status;
478         }
479 
480         nbytes = sizeof(new_m->realsize);
481         status = apr_file_read(file, (void *)&(new_m->realsize),
482                                &nbytes);
483         if (status != APR_SUCCESS) {
484             return status;
485         }
486 
487         status = apr_os_file_get(&tmpfd, file);
488         if (status != APR_SUCCESS) {
489             apr_file_close(file); /* ignore errors, we're failing */
490             apr_file_remove(new_m->filename, new_m->pool);
491             return status;
492         }
493 
494         new_m->reqsize = new_m->realsize - sizeof(apr_size_t);
495 
496         new_m->base = mmap(NULL, new_m->realsize, PROT_READ | PROT_WRITE,
497                            MAP_SHARED, tmpfd, 0);
498         /* FIXME: check for errors */
499 
500         status = apr_file_close(file);
501         if (status != APR_SUCCESS) {
502             return status;
503         }
504 
505         /* metadata isn't part of the usable segment */
506         new_m->usable = (char *)new_m->base + APR_ALIGN_DEFAULT(sizeof(apr_size_t));
507 
508         apr_pool_cleanup_register(new_m->pool, new_m, shm_cleanup_attach,
509                                   apr_pool_cleanup_null);
510         *m = new_m;
511         return APR_SUCCESS;
512 
513 #endif /* APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM */
514 #if APR_USE_SHMEM_SHMGET
515         apr_shm_t *new_m;
516         apr_status_t status;
517         apr_file_t *file;   /* file where metadata is stored */
518         apr_size_t nbytes;
519         key_t shmkey;
520 
521         new_m = apr_palloc(pool, sizeof(apr_shm_t));
522 
523         status = apr_file_open(&file, filename,
524                                APR_READ, APR_OS_DEFAULT, pool);
525         if (status != APR_SUCCESS) {
526             return status;
527         }
528 
529         nbytes = sizeof(new_m->reqsize);
530         status = apr_file_read(file, (void *)&(new_m->reqsize),
531                                &nbytes);
532         if (status != APR_SUCCESS) {
533             return status;
534         }
535         status = apr_file_close(file);
536         if (status != APR_SUCCESS) {
537             return status;
538         }
539 
540         new_m->filename = apr_pstrdup(pool, filename);
541         new_m->pool = pool;
542         shmkey = ftok(filename, 1);
543         if (shmkey == (key_t)-1) {
544             return errno;
545         }
546         if ((new_m->shmid = shmget(shmkey, 0, SHM_R | SHM_W)) == -1) {
547             return errno;
548         }
549         if ((new_m->base = shmat(new_m->shmid, NULL, 0)) == (void *)-1) {
550             return errno;
551         }
552         new_m->usable = new_m->base;
553         new_m->realsize = new_m->reqsize;
554 
555         apr_pool_cleanup_register(new_m->pool, new_m, shm_cleanup_attach,
556                                   apr_pool_cleanup_null);
557         *m = new_m;
558         return APR_SUCCESS;
559 
560 #endif /* APR_USE_SHMEM_SHMGET */
561     }
562 
563     return APR_ENOTIMPL;
564 }
565 
apr_shm_detach(apr_shm_t * m)566 APR_DECLARE(apr_status_t) apr_shm_detach(apr_shm_t *m)
567 {
568     apr_status_t rv = shm_cleanup_attach(m);
569     apr_pool_cleanup_kill(m->pool, m, shm_cleanup_attach);
570     return rv;
571 }
572 
apr_shm_baseaddr_get(const apr_shm_t * m)573 APR_DECLARE(void *) apr_shm_baseaddr_get(const apr_shm_t *m)
574 {
575     return m->usable;
576 }
577 
apr_shm_size_get(const apr_shm_t * m)578 APR_DECLARE(apr_size_t) apr_shm_size_get(const apr_shm_t *m)
579 {
580     return m->reqsize;
581 }
582 
583 APR_POOL_IMPLEMENT_ACCESSOR(shm)
584 
APR_DECLARE(apr_status_t)585 APR_DECLARE(apr_status_t) apr_os_shm_get(apr_os_shm_t *osshm,
586                                          apr_shm_t *shm)
587 {
588     return APR_ENOTIMPL;
589 }
590 
apr_os_shm_put(apr_shm_t ** m,apr_os_shm_t * osshm,apr_pool_t * pool)591 APR_DECLARE(apr_status_t) apr_os_shm_put(apr_shm_t **m,
592                                          apr_os_shm_t *osshm,
593                                          apr_pool_t *pool)
594 {
595     return APR_ENOTIMPL;
596 }
597 
598