1 /* Copyright 2016 The Chromium OS Authors. All rights reserved.
2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file.
4 */
5
6 #include <sys/cdefs.h>
7 #include <sys/mman.h>
8 #ifdef __BIONIC__
9 #include <cutils/ashmem.h>
10 #else
11 #include <sys/shm.h>
12 #endif
13 #include <errno.h>
14 #include <syslog.h>
15 #include <stdio.h>
16 #include <string.h>
17 #include <unistd.h>
18
19 #include "cras_shm.h"
20
cras_shm_info_init(const char * stream_name,uint32_t length,struct cras_shm_info * info_out)21 int cras_shm_info_init(const char *stream_name, uint32_t length,
22 struct cras_shm_info *info_out)
23 {
24 struct cras_shm_info info;
25
26 if (!info_out)
27 return -EINVAL;
28
29 strncpy(info.name, stream_name, sizeof(info.name) - 1);
30 info.name[sizeof(info.name) - 1] = '\0';
31 info.length = length;
32 info.fd = cras_shm_open_rw(info.name, info.length);
33 if (info.fd < 0)
34 return info.fd;
35
36 *info_out = info;
37
38 return 0;
39 }
40
cras_shm_info_init_with_fd(int fd,size_t length,struct cras_shm_info * info_out)41 int cras_shm_info_init_with_fd(int fd, size_t length,
42 struct cras_shm_info *info_out)
43 {
44 struct cras_shm_info info;
45
46 if (!info_out)
47 return -EINVAL;
48
49 info.name[0] = '\0';
50 info.length = length;
51 info.fd = dup(fd);
52 if (info.fd < 0)
53 return info.fd;
54
55 *info_out = info;
56
57 return 0;
58 }
59
60 /* Move the resources from the cras_shm_info 'from' into the cras_shm_info 'to'.
61 * The owner of 'to' will be responsible for cleaning up those resources with
62 * cras_shm_info_cleanup.
63 */
cras_shm_info_move(struct cras_shm_info * from,struct cras_shm_info * to)64 static int cras_shm_info_move(struct cras_shm_info *from,
65 struct cras_shm_info *to)
66 {
67 if (!from || !to)
68 return -EINVAL;
69
70 *to = *from;
71 from->fd = -1;
72 from->name[0] = '\0';
73 return 0;
74 }
75
cras_shm_info_cleanup(struct cras_shm_info * info)76 void cras_shm_info_cleanup(struct cras_shm_info *info)
77 {
78 if (!info)
79 return;
80
81 if (info->name[0] != '\0')
82 cras_shm_close_unlink(info->name, info->fd);
83 else
84 close(info->fd);
85
86 info->fd = -1;
87 info->name[0] = '\0';
88 }
89
cras_audio_shm_create(struct cras_shm_info * header_info,struct cras_shm_info * samples_info,int samples_prot,struct cras_audio_shm ** shm_out)90 int cras_audio_shm_create(struct cras_shm_info *header_info,
91 struct cras_shm_info *samples_info, int samples_prot,
92 struct cras_audio_shm **shm_out)
93 {
94 struct cras_audio_shm *shm;
95 int ret;
96
97 if (!header_info || !samples_info || !shm_out) {
98 ret = -EINVAL;
99 goto cleanup_info;
100 }
101
102 if (samples_prot != PROT_READ && samples_prot != PROT_WRITE) {
103 ret = -EINVAL;
104 syslog(LOG_ERR,
105 "cras_shm: samples must be mapped read or write only");
106 goto cleanup_info;
107 }
108
109 shm = calloc(1, sizeof(*shm));
110 if (!shm) {
111 ret = -ENOMEM;
112 goto cleanup_info;
113 }
114
115 /* Move the cras_shm_info params into the new cras_audio_shm object.
116 * The parameters are cleared, and the owner of cras_audio_shm is now
117 * responsible for closing the fds and unlinking any associated shm
118 * files using cras_audio_shm_destroy.
119 */
120 ret = cras_shm_info_move(header_info, &shm->header_info);
121 if (ret)
122 goto free_shm;
123
124 ret = cras_shm_info_move(samples_info, &shm->samples_info);
125 if (ret)
126 goto free_shm;
127
128 shm->header =
129 mmap(NULL, shm->header_info.length, PROT_READ | PROT_WRITE,
130 MAP_SHARED, shm->header_info.fd, 0);
131 if (shm->header == (struct cras_audio_shm_header *)-1) {
132 ret = -errno;
133 syslog(LOG_ERR, "cras_shm: mmap failed to map shm for header.");
134 goto free_shm;
135 }
136
137 shm->samples = mmap(NULL, shm->samples_info.length, samples_prot,
138 MAP_SHARED, shm->samples_info.fd, 0);
139 if (shm->samples == (uint8_t *)-1) {
140 ret = -errno;
141 syslog(LOG_ERR,
142 "cras_shm: mmap failed to map shm for samples.");
143 goto free_shm;
144 }
145
146 cras_shm_set_volume_scaler(shm, 1.0);
147
148 *shm_out = shm;
149 return 0;
150
151 free_shm:
152 cras_audio_shm_destroy(shm);
153 cleanup_info:
154 cras_shm_info_cleanup(samples_info);
155 cras_shm_info_cleanup(header_info);
156 return ret;
157 }
158
cras_audio_shm_destroy(struct cras_audio_shm * shm)159 void cras_audio_shm_destroy(struct cras_audio_shm *shm)
160 {
161 if (!shm)
162 return;
163
164 if (shm->samples != NULL && shm->samples != (uint8_t *)-1)
165 munmap(shm->samples, shm->samples_info.length);
166 cras_shm_info_cleanup(&shm->samples_info);
167 if (shm->header != NULL &&
168 shm->header != (struct cras_audio_shm_header *)-1)
169 munmap(shm->header, shm->header_info.length);
170 cras_shm_info_cleanup(&shm->header_info);
171 free(shm);
172 }
173
174 /* Set the correct SELinux label for SHM fds. */
cras_shm_restorecon(int fd)175 static void cras_shm_restorecon(int fd)
176 {
177 #ifdef CRAS_SELINUX
178 char fd_proc_path[64];
179
180 if (snprintf(fd_proc_path, sizeof(fd_proc_path), "/proc/self/fd/%d",
181 fd) < 0) {
182 syslog(LOG_WARNING,
183 "Couldn't construct proc symlink path of fd: %d", fd);
184 return;
185 }
186
187 /* Get the actual file-path for this fd. */
188 char *path = realpath(fd_proc_path, NULL);
189 if (path == NULL) {
190 syslog(LOG_WARNING, "Couldn't run realpath() for %s: %s",
191 fd_proc_path, strerror(errno));
192 return;
193 }
194
195 if (cras_selinux_restorecon(path) < 0) {
196 syslog(LOG_WARNING, "Restorecon on %s failed: %s", fd_proc_path,
197 strerror(errno));
198 }
199
200 free(path);
201 #endif
202 }
203
204 #ifdef __BIONIC__
205
cras_shm_open_rw(const char * name,size_t size)206 int cras_shm_open_rw(const char *name, size_t size)
207 {
208 int fd;
209
210 /* Eliminate the / in the shm_name. */
211 if (name[0] == '/')
212 name++;
213 fd = ashmem_create_region(name, size);
214 if (fd < 0) {
215 fd = -errno;
216 syslog(LOG_ERR, "failed to ashmem_create_region %s: %s\n", name,
217 strerror(-fd));
218 }
219 return fd;
220 }
221
cras_shm_reopen_ro(const char * name,int fd)222 int cras_shm_reopen_ro(const char *name, int fd)
223 {
224 /* After mmaping the ashmem read/write, change it's protection
225 bits to disallow further write access. */
226 if (ashmem_set_prot_region(fd, PROT_READ) != 0) {
227 fd = -errno;
228 syslog(LOG_ERR, "failed to ashmem_set_prot_region %s: %s\n",
229 name, strerror(-fd));
230 }
231 return fd;
232 }
233
cras_shm_close_unlink(const char * name,int fd)234 void cras_shm_close_unlink(const char *name, int fd)
235 {
236 close(fd);
237 }
238
239 #else
240
cras_shm_open_rw(const char * name,size_t size)241 int cras_shm_open_rw(const char *name, size_t size)
242 {
243 int fd;
244 int rc;
245
246 fd = shm_open(name, O_CREAT | O_EXCL | O_RDWR, 0600);
247 if (fd < 0) {
248 fd = -errno;
249 syslog(LOG_ERR, "failed to shm_open %s: %s\n", name,
250 strerror(-fd));
251 return fd;
252 }
253 rc = posix_fallocate(fd, 0, size);
254 if (rc) {
255 rc = -errno;
256 syslog(LOG_ERR, "failed to set size of shm %s: %s\n", name,
257 strerror(-rc));
258 return rc;
259 }
260
261 cras_shm_restorecon(fd);
262
263 return fd;
264 }
265
cras_shm_reopen_ro(const char * name,int fd)266 int cras_shm_reopen_ro(const char *name, int fd)
267 {
268 /* Open a read-only copy to dup and pass to clients. */
269 fd = shm_open(name, O_RDONLY, 0);
270 if (fd < 0) {
271 fd = -errno;
272 syslog(LOG_ERR,
273 "Failed to re-open shared memory '%s' read-only: %s",
274 name, strerror(-fd));
275 }
276 return fd;
277 }
278
cras_shm_close_unlink(const char * name,int fd)279 void cras_shm_close_unlink(const char *name, int fd)
280 {
281 shm_unlink(name);
282 close(fd);
283 }
284
285 #endif
286
cras_shm_setup(const char * name,size_t mmap_size,int * rw_fd_out,int * ro_fd_out)287 void *cras_shm_setup(const char *name, size_t mmap_size, int *rw_fd_out,
288 int *ro_fd_out)
289 {
290 int rw_shm_fd = cras_shm_open_rw(name, mmap_size);
291 if (rw_shm_fd < 0)
292 return NULL;
293
294 /* mmap shm. */
295 void *exp_state = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE,
296 MAP_SHARED, rw_shm_fd, 0);
297 if (exp_state == (void *)-1)
298 return NULL;
299
300 /* Open a read-only copy to dup and pass to clients. */
301 int ro_shm_fd = cras_shm_reopen_ro(name, rw_shm_fd);
302 if (ro_shm_fd < 0)
303 return NULL;
304
305 *rw_fd_out = rw_shm_fd;
306 *ro_fd_out = ro_shm_fd;
307
308 return exp_state;
309 }
310