1/*
2   +----------------------------------------------------------------------+
3   | Zend OPcache                                                         |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1998-2014 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Andi Gutmans <andi@zend.com>                                |
16   |          Zeev Suraski <zeev@zend.com>                                |
17   |          Stanislav Malyshev <stas@zend.com>                          |
18   |          Dmitry Stogov <dmitry@zend.com>                             |
19   +----------------------------------------------------------------------+
20*/
21
22#include "zend_shared_alloc.h"
23
24#ifdef USE_SHM
25
26#if defined(__FreeBSD__)
27# include <machine/param.h>
28#endif
29#include <sys/types.h>
30#include <sys/shm.h>
31#include <sys/ipc.h>
32#include <dirent.h>
33#include <signal.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <unistd.h>
37#include <errno.h>
38
39#include <sys/stat.h>
40#include <fcntl.h>
41
42#ifndef MIN
43# define MIN(x, y) ((x) > (y)? (y) : (x))
44#endif
45
46#define SEG_ALLOC_SIZE_MAX 32*1024*1024
47#define SEG_ALLOC_SIZE_MIN 2*1024*1024
48
49typedef struct  {
50    zend_shared_segment common;
51    int shm_id;
52} zend_shared_segment_shm;
53
54static int create_segments(size_t requested_size, zend_shared_segment_shm ***shared_segments_p, int *shared_segments_count, char **error_in)
55{
56    int i;
57    size_t allocate_size = 0, remaining_bytes = requested_size, seg_allocate_size;
58    int first_segment_id = -1;
59    key_t first_segment_key = -1;
60    struct shmid_ds sds;
61    int shmget_flags;
62    zend_shared_segment_shm *shared_segments;
63
64    seg_allocate_size = SEG_ALLOC_SIZE_MAX;
65    /* determine segment size we _really_ need:
66     * no more than to include requested_size
67     */
68    while (requested_size * 2 <= seg_allocate_size && seg_allocate_size > SEG_ALLOC_SIZE_MIN) {
69        seg_allocate_size >>= 1;
70    }
71
72    shmget_flags = IPC_CREAT|SHM_R|SHM_W|IPC_EXCL;
73
74    /* try allocating this much, if not - try shrinking */
75    while (seg_allocate_size >= SEG_ALLOC_SIZE_MIN) {
76        allocate_size = MIN(requested_size, seg_allocate_size);
77        first_segment_id = shmget(first_segment_key, allocate_size, shmget_flags);
78        if (first_segment_id != -1) {
79            break;
80        }
81        seg_allocate_size >>= 1; /* shrink the allocated block */
82    }
83
84    if (first_segment_id == -1) {
85        *error_in = "shmget";
86        return ALLOC_FAILURE;
87    }
88
89    *shared_segments_count = ((requested_size - 1) / seg_allocate_size) + 1;
90    *shared_segments_p = (zend_shared_segment_shm **) calloc(1, (*shared_segments_count) * sizeof(zend_shared_segment_shm) + sizeof(void *) * (*shared_segments_count));
91    if (!*shared_segments_p) {
92        *error_in = "calloc";
93        return ALLOC_FAILURE;
94    }
95    shared_segments = (zend_shared_segment_shm *)((char *)(*shared_segments_p) + sizeof(void *) * (*shared_segments_count));
96    for (i = 0; i < *shared_segments_count; i++) {
97        (*shared_segments_p)[i] = shared_segments + i;
98    }
99
100    remaining_bytes = requested_size;
101    for (i = 0; i < *shared_segments_count; i++) {
102        allocate_size = MIN(remaining_bytes, seg_allocate_size);
103        if (i != 0) {
104            shared_segments[i].shm_id = shmget(IPC_PRIVATE, allocate_size, shmget_flags);
105        } else {
106            shared_segments[i].shm_id = first_segment_id;
107        }
108
109        if (shared_segments[i].shm_id == -1) {
110            return ALLOC_FAILURE;
111        }
112
113        shared_segments[i].common.p = shmat(shared_segments[i].shm_id, NULL, 0);
114        if (shared_segments[i].common.p == (void *)-1) {
115            *error_in = "shmat";
116            shmctl(shared_segments[i].shm_id, IPC_RMID, &sds);
117            return ALLOC_FAILURE;
118        }
119        shmctl(shared_segments[i].shm_id, IPC_RMID, &sds);
120
121        shared_segments[i].common.pos = 0;
122        shared_segments[i].common.size = allocate_size;
123        remaining_bytes -= allocate_size;
124    }
125    return ALLOC_SUCCESS;
126}
127
128static int detach_segment(zend_shared_segment_shm *shared_segment)
129{
130    shmdt(shared_segment->common.p);
131    return 0;
132}
133
134static size_t segment_type_size(void)
135{
136    return sizeof(zend_shared_segment_shm);
137}
138
139zend_shared_memory_handlers zend_alloc_shm_handlers = {
140    (create_segments_t)create_segments,
141    (detach_segment_t)detach_segment,
142    segment_type_size
143};
144
145#endif /* USE_SHM */
146